Safeguard
Developer Tools•

Safeguard CLI for monorepos: scan, fix and gate each package

Scan each package in a monorepo with safeguard scan --dir, fix with approval where the lockfile lives, and gate every package in CI with ci scan --local.

Safeguard Security Team
19 min read

To use the Safeguard CLI in a monorepo, scan the whole repository once for a full picture, then scan each package folder with safeguard scan --dir so results stay separate. Fix inside the package, but run installs where the lockfile lives. Start the assistant in the package folder, approve each edit and command, and gate each package in CI.

You keep what you already pay for. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. If you would rather stay in the chat app you use today, connect Claude or ChatGPT to the Safeguard MCP server and ask about your findings from there.

What you will have at the end of this guide:

  • A map of where each workspace tool in your repository keeps its manifests and lockfiles, and what that means for scanning.
  • A root scan and per-package scans, each writing to its own output folder.
  • One package fixed by the assistant, with every command and edit approved by you, tests passing and nothing pushed.
  • A CI job that scans only the packages a change touched and fails on high severity findings.
  • A short list of the mistakes that trip up most monorepo teams, and how to get out of them.

The examples use a sample repository called acme-platform with three parts: services/payments-api (an npm workspace), services/ledger (a Go module) and libs/java-common (a Maven module). Swap in your own paths.

How a monorepo looks to a scanner

A monorepo is one repository with many packages. The hard part is not finding the packages. It is knowing which file is the source of truth for each one's dependencies, because that decides where a fix has to land. Each workspace tool answers that differently, and the answer comes from the tool's own docs, not from Safeguard.

Workspace toolHow packages are declaredWhere the lockfile livesHow to target one package
npm workspacesworkspaces field in the root package.jsonOne package-lock.json at the rootnpm run test -w services/payments-api
pnpm workspacespackages globs in pnpm-workspace.yamlOne pnpm-lock.yaml at the root by defaultpnpm --filter "./services/payments-api" test
Go workspacesuse lines in go.workEach module keeps its own go.mod and go.sumRun go commands inside the module folder
Maven multi-modulemodules in the parent pom.xmlNo lockfile; versions live in each pom.xml and the parentmvn -pl libs/java-common -am test
Gradle multi-projectinclude() in settings.gradle or settings.gradle.ktsPer project build files./gradlew :services:api:test

Three things in that table shape everything that follows.

npm and pnpm keep one lockfile at the root. The npm workspaces docs show a single package-lock.json beside the root package.json, with each workspace symlinked into the root node_modules. pnpm's docs say it "creates a single pnpm-lock.yaml file in the root of the workspace" unless you turn off sharedWorkspaceLockfile. So when you upgrade a dependency in one JavaScript package, the change shows up in the root lockfile, and the install must run from the root.

Go modules stay independent. A go.work file lists modules with use, and each module keeps its own go.mod. The Go modules reference adds a caution: a checked-in go.work "may cause a continuous integration (CI) system to select and thus test the wrong versions of a module's dependencies." For security checks in CI, you usually want each module judged on its own go.mod.

Maven and Gradle build in dependency order. Maven's reactor sorts modules so each one builds before anything that needs it. -pl picks modules, and -am "also build projects required by the list". Gradle does the same with project paths, so ./gradlew :api:compileJava builds shared first if api depends on it. When you fix a shared library, test the modules that depend on it too, with -amd in Maven.

Root scan or per-package scan?

Do both, for different reasons.

  • A root scan answers "what is in this repository?" It is the view a customer or auditor asks for, and it catches packages nobody remembered to register.
  • Per-package scans answer "what does this team need to fix?" They map to owners, they make CI faster because you only scan what changed, and they keep one noisy package from hiding another's problems.

safeguard scan builds an SBOM for whatever folder you give it and matches it against vulnerability databases. It writes JSON results, such as vuln.json and license.json, to an output folder that defaults to ./output. In a monorepo, always pass --output so per-package results do not overwrite each other.

Do the work properly with the Safeguard CLI

The Safeguard CLI scans code, container images and SBOMs on your own machine, sends only the results to the platform, and includes an assistant that can fix what it finds. It also writes code, not just reports, so the same session that finds a problem in services/payments-api can fix it, run the tests and hand you a commit message.

Install

On macOS and Linux:

curl -fsSL https://cli.safeguard.sh/install | bash
safeguard --version

On Windows, in PowerShell:

irm https://cli.safeguard.sh/install.ps1 | iex

It is a single static binary, so there is no Node, Python or JVM to install first. It runs on macOS, Linux and Windows, on x86_64 or arm64 (Windows is x86_64 only). If you want a step by step for your platform, see installing the Safeguard CLI on macOS.

First session, in the right folder

Where you start the CLI matters more in a monorepo than anywhere else. The @ file picker attaches files from the folder you started in, and /resume picks up the last session in that folder. So:

cd ~/code/acme-platform/services/payments-api
safeguard

Inside the interactive terminal, run /login to sign in through the browser, then /scan . to scan this package. You now have a session that belongs to payments-api. Start another one in services/ledger for the Go module, and each package keeps its own history.

For questions about the whole repository, start a separate session at the root. That keeps cross-cutting work, such as "which packages still pull in the old lodash version?", apart from package fixes.

Writing and fixing code

You talk to the assistant in plain language. It has tools for your local files and for your Safeguard data, and it picks whichever the question needs. Typical monorepo prompts:

  • "fix the lodash CVE in this repo"
  • "which workspaces depend on the vulnerable version, and which one pulls it in directly?"
  • "upgrade the dependency in this package only, then run this package's tests"
  • "explain what changed in package-lock.json after the install"

Writing a file, editing code and running a command all ask before they act, so you see each step before it happens.

Here is a real session recorded on 21 October 2026 against the sample payments-api project. It installs the CLI with the curl command (checksum verified), checks safeguard --version, opens the interactive terminal and asks "fix the lodash CVE in this repo". The assistant reads the finding, the files and the advisory, then asks before each command. It edits package.json to lodash 4.17.21, runs npm install and npm test with approval, and prints a summary with a suggested commit message. It pushes nothing. The session ends with /permissions, /model and /scan ..

Click through it at your own pace, or open it full screen.

Using your Claude or ChatGPT plan

As covered at the top, the plan you already pay for comes with you, at no extra charge from Safeguard. Use /model to see or switch the model for the session, and see the CLI docs for connecting a model. Every package session, JavaScript or Go or Java, runs on that same plan.

Approvals that fit a monorepo

/permissions sets how much runs without a prompt. The modes are:

  • Ask. Reads run; edits and commands ask first.
  • Accept edits. Edits in this folder and read-only commands run; other commands ask.
  • Plan. Read and plan only.
  • Auto. Read-only commands and what the policy allows run; anything else asks. This is the default.
  • Strict. Pre-approved actions only, for CI.

Accept edits is where the starting folder pays off. Start in services/payments-api and edits there run without a prompt, while installs and tests still ask. Start at the root and the same mode covers every package. Use Plan when you first open an unfamiliar package and want a reading of its dependency tree with no changes. There is also a --yolo flag that approves everything. Keep it for throwaway sandboxes only, never a working copy of a shared monorepo.

CI with safeguard exec

safeguard exec "summarise the critical findings in this repository" runs one non-interactive assistant turn, which suits CI. Its default permission mode is strict, so anything not pre-approved by the policy is denied. It refuses to run outside a git repository, because there would be no way to undo its changes. In a monorepo, -C (or --cd) runs it in a package folder, and --add-dir lets it read another folder, such as a shared library. More on CI below.

Step by step: scan, fix and verify one package

This walkthrough fixes a high severity dependency finding in services/payments-api, an npm workspace. The Go and Maven variants follow.

  1. Start clean. From the root, check git status and commit or stash anything in progress. Create a branch:

    cd ~/code/acme-platform
    git switch -c fix/payments-api-deps
    
  2. Scan the whole repository once.

    safeguard scan --dir . --output output/root
    

    Open the results and check that every lockfile you expect is represented. If a package is missing, note it now; it usually means a lockfile was never committed.

  3. Scan each package into its own folder.

    safeguard scan --dir services/payments-api --output output/payments-api
    safeguard scan --dir services/ledger --output output/ledger
    safeguard scan --dir libs/java-common --output output/java-common
    

    For a quick local look that sends nothing anywhere, safeguard findings sca --dir services/payments-api runs the dependency scanner on this machine and prints a normalized list. No project is registered and nothing is uploaded, even when you are signed in.

  4. Preview a fix without changing anything.

    safeguard fix --manifest services/payments-api/package.json --severity high --dry-run
    

    --dry-run previews changes without applying them. Add --patch-only if you only want patch upgrades, or --package lodash to limit the fix to one package.

  5. Hand the fix to the assistant, in the package folder.

    cd services/payments-api
    safeguard
    

    Ask: "fix the high severity dependency findings in this package. The lockfile is at the repository root, so run installs from there. Run this package's tests after." The assistant asks before each command. Read each one. For an npm workspace, a correct install looks like this:

    cd ../.. && npm install lodash@4.17.21 -w services/payments-api
    

    If it proposes npm install inside the package folder instead, press n and tell it why.

  6. Check the diff. You should see the package's package.json and the root package-lock.json change, and nothing else. If another workspace's manifest changed, ask the assistant to explain before you go further.

  7. Run the package's tests, then its dependents' tests.

    npm run test -w services/payments-api
    npm run test --workspaces --if-present
    

    The second line runs every workspace that has a test script. It is slower, but a shared dependency upgrade can break a neighbour.

  8. Scan again. Run /scan . in the session, or safeguard scan --dir services/payments-api --output output/payments-api from the root. The finding should be gone, and no new high severity finding should have arrived with the upgrade.

  9. Commit with the suggested message. The assistant prints a summary and a commit message. It does not push. You review, commit and open the pull request yourself.

If something goes wrong in step 5, you have a way back. The CLI snapshots files before the assistant writes or edits them, and /rewind returns to an earlier point. From the shell, safeguard checkpoints list --session with the session id shows them, and safeguard checkpoints restore with --dry-run previews a restore as a diff. Anything a shell command wrote, such as the lockfile from npm install, is not snapshotted, which is why step 1 says commit first.

The same fix in a Go module

Go modules each keep their own go.mod, so work inside the module:

cd ~/code/acme-platform/services/ledger
safeguard

Ask the assistant to upgrade the affected module and tidy. The commands it should propose look like:

go get example.com/somemodule@v1.2.3
go mod tidy
go test ./...

The Go reference says go get updates the require directives in the main module's go.mod, and it may raise other requirements too, so read the go.mod diff carefully. If other modules in the workspace use the same dependency, go work sync pushes the workspace's build list back into each module.

The same fix in a Maven module

Maven has no lockfile, so the version lives in a pom.xml, often the parent's dependencyManagement. Ask the assistant where the version is declared before it edits anything. Then test the module and everything that depends on it:

mvn -pl libs/java-common -amd test

-amd builds "projects that depend on projects on the list", which is the set a shared library change can break. For Gradle, the equivalent is to run the task by project path, such as ./gradlew :libs:java-common:test, and then the consumers' tests.

Sending work from the desktop app

You do not have to sit at the terminal to do this. A CLI session is live: it shows in the web app, the desktop app and the phone app, and those devices can send work to a computer running the CLI. You pick the computer and the folder, which in a monorepo means picking the package. In this recorded desktop session, the CLI session appears in the conversation list, "2 connected" shows the computer, the browser and Safeguard cloud, and a request for work on the computer asks which computer and folder. You pick the folder, then Approve or Deny each command and file edit. It ends with the upgrade done and the tests passing.

Click through it at your own pace, or open it full screen.

The desktop app guide covers installing it on Windows, macOS and Linux.

Gate each package in CI

The aim is simple: when a pull request touches services/payments-api, scan that package and fail the job if it now has a high severity finding. Leave the other packages alone.

safeguard ci scan is built for this. With --local it scans the checkout on the CI runner over --path and uploads only the results, never the source. It needs no repository integration and works for private and self-hosted repositories. It scans dependencies (SBOM and vulnerabilities); SAST runs only in platform mode. Authenticate with a secret key from Settings, Secret Keys, that has the scans:trigger scope, and scans:read for --wait, stored as the CI secret SAFEGUARD_API_KEY.

A GitHub Actions job for one package:

name: payments-api security
on:
  pull_request:
    paths:
      - "services/payments-api/**"
      - "package-lock.json"

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Install Safeguard CLI
        run: curl -fsSL https://cli.safeguard.sh/install | bash
      - name: Scan payments-api
        env:
          SAFEGUARD_API_KEY: ${{ secrets.SAFEGUARD_API_KEY }}
        run: |
          safeguard ci scan --local --path services/payments-api \
            --project payments-api --wait --fail-on high

Note the package-lock.json path filter. In an npm or pnpm workspace, a dependency change for this package can land only in the root lockfile, so a filter on the package folder alone would miss it.

Copy the job per package, changing the path, the project name and the path filter. For Go modules, add GOWORK: "off" to the job's env so each module is built from its own go.mod, following the Go reference's advice that CI should generally not use go.work.

If your team runs policies, --fail-on-gate Release (a gate name, an id or all) waits for the gate's policies to be evaluated against the scan and fails the step when the gate fails. Policies and gates are evaluated in the platform, not by the CLI, so set them up there first.

For a written summary in the job log, add one assistant turn per package:

safeguard exec -C services/payments-api \
  "summarise the critical and high findings in this package and name the owning files"

The guide to running safeguard exec in CI covers authentication, output formats and permission flags in depth.

Common mistakes and troubleshooting

Common mistakes

Running installs inside a JavaScript workspace folder. With npm and pnpm the lockfile is at the root. An install run elsewhere can leave the root lockfile stale, and npm ci in CI then fails, because the npm docs say it exits with an error when the lock and package.json do not match.

Letting per-package scans overwrite each other. safeguard scan writes to ./output unless told otherwise. Two scans in a row without --output leave you with only the second package's results.

Fixing a shared library and testing only the library. In Maven use -amd, in Gradle run the consumers' tests, in npm run --workspaces. The bug from a shared upgrade usually shows up one package away.

Starting the assistant at the root for a one-package fix. It works, but the @ picker shows the whole repository, Accept edits covers every package and /resume mixes sessions. Start in the package.

Path filters that miss the root lockfile. As in the CI example, include the root lockfile in any path filter for JavaScript workspaces.

Using --yolo on a real checkout. Keep it for throwaway sandboxes.

Troubleshooting

A package is missing from the root scan. Check that its lockfile is committed. Then scan the folder on its own with safeguard findings sca --dir and that path to see what the scanner finds there.

The assistant edits the wrong package. Press n at the prompt, say which folder it should work in, and restart the CLI in that folder if needed. If an edit already happened, /rewind takes the files back.

Go tests pass locally and fail in CI. Your local go.work may be pointing at a sibling module's local copy. Run the tests with GOWORK=off locally to reproduce what CI sees.

Maven says a module cannot be found with -pl. The Maven CLI reference says a project can be given as groupId:artifactId or by its relative path. Use the path from the root, such as libs/java-common.

Something about the CLI itself looks wrong. safeguard doctor checks the environment, sign in, profile, policy and tools, prints what is wrong in order, and changes nothing.

FAQ

Should I scan the monorepo root or each package?

Both. The root scan gives the full inventory for the repository. Per-package scans, each with its own --output folder, map findings to the team that owns them and keep CI fast.

Can I use my Claude or ChatGPT subscription for this?

Yes. Bring the Claude, ChatGPT or Codex plan you already pay for and use it inside the Safeguard CLI. Safeguard adds no extra charge for that usage; your plan's own usage limits and terms still apply. You can also connect Claude or ChatGPT to the Safeguard MCP server; see connecting Claude to the Safeguard MCP server.

Does my source code leave my machine?

When the local runner runs platform scan jobs, your source code never leaves the machine; only SBOMs and findings are uploaded. safeguard findings uploads nothing at all.

Will the assistant change files in other packages?

Only with your approval. Edits and commands ask before they act unless your /permissions mode allows them. Starting the CLI in the package folder keeps the file picker and Accept edits scoped to that package.

How do I undo a change the assistant made?

Use /rewind in the session, or safeguard checkpoints restore from the shell, with --dry-run first to see the diff. Files written by shell commands, such as a lockfile from an install, are not snapshotted, so commit before you start.

Does the CLI enforce our security policies?

No. Policies and gates are evaluated in the platform. The CLI can wait for that result in CI with safeguard ci scan --wait --fail-on-gate.

Can I generate a separate SBOM for each package?

Yes. safeguard sbom --dir services/payments-api --output payments-api-sbom.json writes an SPDX JSON SBOM for that folder, and safeguard validate checks CycloneDX, SPDX and VEX documents before you share them.

Can I start the work from my phone?

Yes. The same conversations appear in the phone app, and "Work on my computer" sends work to a computer running the CLI, where each command waits for your approval.

Try it yourself

This walkthrough sets up the runner on your own machine: pick the platform, start it, approve it with its device code, then use the same CLI to scan from the terminal.

Click through it at your own pace, or open it full screen.

When you are ready, get the CLI, the desktop app and the phone apps from safeguard.sh/download, and keep the CLI docs open for your first monorepo session. If agents write some of your pull requests, our post on checking coding agent changes with the Safeguard CLI pairs well with this one.

Quick check

In an npm workspace, where should the install run after you upgrade a dependency in one package?

From the repository root, because npm workspaces keep one package-lock.json there. Running it inside the package folder can leave the root lockfile out of date.

Why give each per-package scan its own output folder?

safeguard scan writes to ./output by default, so without a separate output folder each package scan overwrites the previous one's results.

Which permission mode lets edits in the package folder run while commands still ask?

Accept edits. Edits in the folder you started in and read-only commands run; other commands ask first.

Sources

Last reviewed: 21 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.