To automate security checks with safeguard exec, let a deterministic scan decide pass or fail, then run safeguard exec for one non-interactive assistant turn that explains the result. Run it in a git hook, on a schedule or in a pull request job, on a signed-in machine. Its default strict mode asks nothing and denies anything not pre-approved.
You do not have to give up the AI plan you already use. 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 your team works in a chat app, you can also connect Claude or ChatGPT to the Safeguard MCP server and ask about the same findings there.
This is a hands-on guide. Every flag below comes from safeguard exec --help and the other command help pages in CLI v11.11.0, or from the Safeguard docs listed at the end.
What you will have at the end
Work through the three walkthroughs and you will have:
- A pre-push hook on your laptop that scans the repository, blocks the push when a critical vulnerability appears, and prints a short explanation written by the assistant.
- A nightly report that runs on a signed-in machine, writes a plain summary of new risk, and sends it to your phone with
safeguard send. - A pull request job where
safeguard ci scanlets your platform policy decide, andsafeguard reviewplussafeguard execleave a readable note for the reviewer.
You will also know which flags keep an unattended run safe (--restricted, --tools, --disallowedTools, --max-budget-usd), how to get machine-readable output, and what to check when a run fails. If you only want the pipeline basics, read Run the Safeguard CLI in CI/CD first. This guide goes further into automation patterns.
How safeguard exec works
safeguard exec runs one agent turn without prompting, prints the final result and exits. You pass the prompt as an argument, or pass - to read it from standard input:
safeguard exec "summarise the critical findings in this repository"
echo "which critical findings have a fixed version available" | safeguard exec -
Four rules shape everything else in this guide.
Nothing is asked. The help text says: "The default permission mode is strict: nothing is asked, and anything not pre-approved by the policy file is denied." There is no human at a cron job or a hook, so the safe default is to refuse.
It wants a git repository. exec refuses to run outside one, "because there would be no way to undo what it changes." --skip-git-repo-check accepts that risk. For automation, keep the check on.
It runs on a signed-in machine. The CLI authorises a machine by signing in once. The docs are explicit that there is no API key environment variable for the CLI itself; the token stays on the machine that signed in. That makes your own laptop, a build server or a self-hosted CI runner the natural home for exec. (safeguard ci scan is different: it uses a SAFEGUARD_API_KEY secret.)
Scans decide, the assistant explains. A scan result is the same every time you run it on the same code. A language model answer is not. So let safeguard scan or safeguard ci scan set the exit code, and use exec to turn the result into something a person can act on.
The flags that matter for automation
| Flag | What it does (from --help) | Use it when |
|---|---|---|
-a, --permission-mode | supervised, edits, plan, guarded, strict or autonomous; default strict | You want to keep the default, or loosen it on purpose |
--restricted | "read-only tool set: no writes, no shell, no network" | Any report or summary job |
--tools | "the only tools this run may use" | You want a short, explicit tool list |
--allowedTools | allow rules in policy syntax, for example 'Bash(go test:*)' | One command must be allowed under strict |
--disallowedTools | deny rules, for example 'Read(~/.ssh/**)' | You want a hard no on paths or commands |
--output-format | text, json or stream-json | Another step reads the result |
--output-schema | "a JSON Schema file the answer must conform to" | You want fixed fields to parse |
--max-budget-usd | ends the turn with limit_reached once that much is spent | Scheduled jobs that should never run away |
-C, --cd | run in another directory | Cron jobs and scripts |
-n, --name | names the session | You want to find the run later |
--private | does not record the run in the session store | Throwaway checks |
--safe-mode | ignores local settings, hooks, skills, plugins and SAFEGUARD.md | Reproducible runs on a shared machine |
--sandbox | confines writes to the workspace at kernel level (Linux, Landlock) | Linux build machines |
Two more are worth knowing. --yolo is an alias for --dangerously-skip-permissions, which allows autonomous mode outside a sandbox runner. Keep it for throwaway sandboxes only, never for a job that touches a real repository. And --background runs the turn detached and returns at once, which suits long reports you will read later.
Do the work properly with the Safeguard CLI
Automation only works if the interactive side is solid, because that is where you fix what the automated checks find. 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 writes code, not only scans it.
Install
On macOS and Linux:
curl -fsSL https://cli.safeguard.sh/install | bash
On Windows, in PowerShell:
irm https://cli.safeguard.sh/install.ps1 | iex
It is a single static binary with no Node, Python or JVM to install. It runs on macOS, Linux and Windows, on x86_64 or arm64 (Windows is x86_64 only). Each release ships a SHA256SUMS file that the installer verifies. Platform notes are in the guides for Linux and the other systems.
First session
cd ~/projects/payments-api
safeguard --version
safeguard
With no arguments, safeguard opens the interactive terminal. Type /login and approve the session in your browser. Type /scan . to scan the current folder. The first run also downloads the scanner tools and the vulnerability database, which matters for automation: a hook or cron job that runs before setup has finished will fail with a missing scanner.
Writing and fixing code
The assistant has tools for your local files and for your Safeguard data, and picks whichever the question needs. Ask "fix the lodash CVE in this repo" and it reads the finding, the files and the advisory, then edits code and runs commands with your approval. This recorded session shows exactly that on a sample project called payments-api: install with the curl command (checksum verified), safeguard --version, the interactive terminal, the fix request, a prompt before each command where you press y or n, the edit to lodash 4.17.21 in package.json, npm install and npm test with approval, and a summary with a suggested commit message. It pushes nothing. It ends with /permissions, /model and /scan ..
Click through it at your own pace, or open it full screen.
Your Claude, ChatGPT or Codex plan
As covered at the top, you can bring the plan you already pay for into the CLI at no extra charge from Safeguard, under your plan's own limits and terms. /model shows or switches the model, and the CLI docs cover connecting one. In exec, -m picks the model tier for a run and --fallback-model retries a failed model call on another tier.
Approvals
In the interactive terminal, /permissions offers five modes:
- 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.
Strict is the mode exec starts in. The --yolo flag approves everything; use it only in a throwaway sandbox.
CI with safeguard exec
The pattern for the rest of this guide: a scan sets the exit code, exec --restricted writes the explanation, and code changes happen back in the interactive terminal, where you approve them.
Walkthrough 1: a pre-push check on your laptop
A pre-push hook runs every time you git push. The git docs say that if the hook "exits with a non-zero status, git push will abort without pushing anything," and that a reason can go to standard error. That is exactly the shape we want.
-
Confirm the CLI is signed in and ready.
safeguard whoami safeguard doctordoctorchecks environment, sign-in, tools and policy and changes nothing. Its exit code is 0 whenever the checks ran, so read the lines, not the status. -
Make sure jq is installed. The gate below reads
vuln.jsonwith jq. -
Create the hook. Save this as
.git/hooks/pre-pushin your repository:#!/usr/bin/env bash set -u out="$(mktemp -d)" safeguard scan --dir . --output "$out" >/dev/null status=$? if [ "$status" -ne 0 ]; then echo "safeguard scan exited with $status; push blocked. Run safeguard doctor." >&2 exit 1 fi if ! jq -e '[.data.matches[].vulnerability.severity | ascii_downcase] | index("critical") | not' "$out"/*/vuln.json >/dev/null; then echo "Critical vulnerability found. Push blocked." >&2 safeguard exec --restricted --private \ "A scan just found at least one critical vulnerability in this repository. In under 120 words, name each critical finding, the package and version, and the fixed version if one exists." >&2 exit 1 fi echo "Safeguard: no critical findings." >&2 -
Make it executable. The git docs note that "hooks that don't have the executable bit set are ignored."
chmod +x .git/hooks/pre-push -
Test it. Push a branch. On a clean project you see the one-line pass message. To see the block, try it on a branch where you pinned an old, vulnerable version of a dependency.
-
Share it with the team (optional). Hooks live in
.git/hooksby default, which is not part of what you commit. To share, commit the script to a folder such as.githooks/and have each person rungit config core.hooksPath .githooks, the setting the git docs name for changing the hooks directory.
A few notes on this design. The scan sets the outcome; the assistant never decides whether you may push. Exit code 2 from safeguard scan means results were written but the vulnerability scan did not run, and the hook treats any non-zero exit as a block, so a broken toolchain cannot quietly pass. --restricted means the explanation step has no writes, no shell and no network. --private keeps hundreds of hook runs out of your session list.
If you want a check where nothing leaves the machine at all, swap the scan for safeguard findings sca --dir . --json. The findings command runs one scanner locally and prints a normalized list (id, severity, title, CVE or rule, package and version, fixed version), and its help says "no project is registered and no finding or file is uploaded, even when this CLI is signed in." The JSON is a stream of progress lines followed by the result, so run it once by hand and look at the last line before you script against its fields.
Walkthrough 2: a nightly report on your phone
The second pattern runs once a day on a machine that stays on and is signed in: a workstation, a build server or a small Linux box.
-
Sign in once on that machine. If it has no browser, use the device flow:
safeguard login --no-browserIt prints a URL and a code to enter on any other device.
safeguard whoamilater shows the account and when the token expires, which is the first thing to check if the nightly job stops working. -
Find your phone's device name.
safeguard devicesThis lists your signed-in devices that can send and receive. Install the Safeguard app on your phone first and sign in with the same account.
-
Write the report script. Save it as
~/bin/sg-nightly.sh:#!/usr/bin/env bash set -u repo="$HOME/projects/payments-api" out="$HOME/sg-nightly/$(date +%F)" mkdir -p "$out" cd "$repo" && git pull --ff-only --quiet safeguard scan --dir "$repo" --output "$out/scan" >/dev/null 2>"$out/scan.log" safeguard exec -C "$repo" --restricted --max-budget-usd 1 \ --name "nightly-$(date +%F)" \ "Summarise the critical and high findings in this repository for a developer reading on a phone. Lead with anything that has a fixed version available. Keep it under 150 words." \ > "$out/summary.txt" 2>"$out/exec.log" safeguard send --to "Pixel" --stdin < "$out/summary.txt"Replace
Pixelwith your device name or a unique prefix of it. The help forsafeguard sendgives 8,000 characters as the limit for text, and a short answer reads better on a phone anyway, which is why the prompt sets a word count. JSON and results go to stdout and notices go to stderr, so the script redirects them separately. -
Schedule it. With cron on Linux or macOS, run
crontab -eand add a line such as:30 6 * * 1-5 /bin/bash "$HOME/bin/sg-nightly.sh"That runs at 06:30 on weekdays, in the machine's local time.
-
Or schedule it in GitHub Actions on a self-hosted runner. GitHub documents the
scheduleevent with five-field cron syntax, running in UTC by default, only on the default branch, at most once every five minutes, and possibly delayed at busy times such as the top of the hour. Becauseexecneeds a signed-in machine, point the job at a self-hosted runner you signed in once. GitHub notes that self-hosted runners are machines you manage, and that they do not reset between jobs, which is what keeps the sign-in in place.name: safeguard-nightly on: schedule: - cron: "17 6 * * 1-5" workflow_dispatch: jobs: report: runs-on: self-hosted steps: - uses: actions/checkout@v7 - name: Scan run: safeguard scan --dir . --output out/ - name: Summarise run: | safeguard exec --restricted --max-budget-usd 1 \ "Summarise the critical and high findings in this repository in under 150 words." > summary.txt - name: Send to phone run: safeguard send --to "Pixel" --stdin < summary.txtThe odd minute (17) keeps the job off the busy top of the hour.
workflow_dispatchlets you run it by hand to test. -
Read it in the morning. The summary arrives in the app. Anything that needs a code change goes back to a computer, which is where the next demo comes in.
Acting on the report from the desktop app
When the report flags something, you do not need to open a terminal to start the fix. This recorded desktop session shows the CLI session appearing in the desktop app's conversation list, "2 connected" showing the computer, the browser and Safeguard cloud, and a request for work on your computer. The app asks which computer and folder, you pick the folder, then you Approve or Deny each command and file edit. It ends with the upgrade done and tests passing.
Click through it at your own pace, or open it full screen.
The desktop app guide covers installation on Windows, macOS and Linux.
Walkthrough 3: a pull request summary for reviewers
The third pattern adds a reviewer's note to every pull request. Two different tools do two different jobs here.
-
Let the platform decide. Create a secret key under Settings, Developer with the
scans:triggerandscans:readscopes and store it as the CI secretSAFEGUARD_API_KEY. Then:safeguard ci scan --wait --fail-on-gate Release--fail-on-gatewaits for the gate's policies to be evaluated against this scan. The step fails when the gate is Failed, Error or Not evaluated, and passes on Warning or Passed, printing any failed warn and notify policies. Policies and gates are evaluated in the platform, not by the CLI, so this is where your organisation's rules apply. Use--fail-on highinstead if you only want a severity threshold. -
Review the change itself. On a signed-in self-hosted runner:
git fetch origin main safeguard review --base origin/main > review.txtsafeguard reviewreviews changes "for correctness and security", and--basediffs against a git ref instead of the working tree. -
Write a short note for the reviewer.
safeguard exec --restricted --output-format json \ "Read the diff against origin/main. In under 100 words, list any new or upgraded dependencies and say whether any of them has a known vulnerability." \ > exec-result.jsonOpen
exec-result.jsononce by hand before another step depends on its fields. -
Keep the files. Upload
review.txtandexec-result.jsonas build artifacts, or post them as a comment with whatever tooling your CI already uses.
Two cautions. Pull requests from forks receive no secrets, so the ci scan step fails there; decide up front whether fork PRs skip it. And never let a pull request job run exec with write access on untrusted code. --restricted is the right default for anything triggered by someone else's branch. For more on this step, see reviewing AI generated code before merge.
Prompts, schemas and limits
Good unattended prompts share a shape: one question, a word limit, and a clear reader.
| Weak prompt | Better prompt |
|---|---|
| "Check security" | "List each critical finding with package, version and fixed version, under 120 words" |
| "Fix everything" | Do not automate this. Run it interactively and approve each step |
| "What changed?" | "Compare today's findings with the SBOM in ./output/previous and list new critical or high items" |
Fixed fields with an output schema
When a script needs to read the answer, give it a schema. Save this as summary.schema.json:
{
"type": "object",
"required": ["critical_count", "fixable", "summary"],
"properties": {
"critical_count": { "type": "integer" },
"fixable": { "type": "array", "items": { "type": "string" } },
"summary": { "type": "string" }
}
}
Then:
safeguard exec --restricted --output-schema summary.schema.json \
"Report the critical findings in this repository." > summary.json
Use the fields for display and routing, for example to decide who gets the message. Keep the pass or fail decision on the scan.
Allow one command, deny the rest
Sometimes a check needs to run one command, such as the test suite. Instead of loosening the whole mode, allow just that command:
safeguard exec --allowedTools 'Bash(npm test:*)' \
--disallowedTools 'Read(~/.ssh/**)' \
"Run the tests and tell me whether any failure mentions a dependency."
Everything else stays denied under strict.
Troubleshooting
| Symptom | What to check |
|---|---|
exec refuses to start | You are outside a git repository. cd into the repo, or pass -C with its path |
| "Not authenticated" or HTTP 401 | safeguard whoami for the account and token expiry, then safeguard logout and safeguard login --force |
| Sign-in will not open a browser on a server | safeguard login --no-browser, open the URL elsewhere and enter the code |
| "syft binary not found" | The scanners download on first use. Run safeguard once interactively and let setup finish |
| Scan exits 2 | Results were written but the vulnerability scan did not run; run safeguard doctor |
| JSON file has log lines mixed in | Redirect stdout alone; notices and logs go to stderr |
| Network or proxy errors | Set HTTP_PROXY and HTTPS_PROXY; allow outbound HTTPS on 443 |
| Certificate errors | Set SSL_CERT_FILE to your corporate CA bundle; do not disable verification |
| An old version runs | which -a safeguard to find every copy, then safeguard self-update |
Logs are in ~/.sg/logs/safeguard.log. For a support request, collect safeguard --version and safeguard doctor > doctor.txt.
Common mistakes
Letting the model decide pass or fail. Gate on vuln.json, safeguard ci scan --fail-on or --fail-on-gate. Use exec to explain.
Treating scan exit code 0 as clean. It means the scan ran. The findings are in the files.
Turning on autonomous mode to make a job "just work". If a job needs one command, allow that command with --allowedTools. Keep --yolo for throwaway sandboxes.
Running exec on hosted runners that were never signed in. The CLI does not read an API key from the environment. Use a signed-in machine or a self-hosted runner.
Forgetting the token expires. Put safeguard whoami at the start of a nightly script so the log shows sign-in problems first.
Writing long prompts with no limit. A report nobody reads is noise. Ask for a word count and a reader.
Asking exec to change code. Unattended changes are changes nobody reviewed. Do the fix in the interactive terminal or the desktop app, approve each step, and let the automated check confirm it.
FAQ
Can I keep my Claude, ChatGPT or Codex plan?
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. See /model or the CLI docs for connecting a model. You can also connect Claude or ChatGPT to the Safeguard MCP server.
What is the difference between safeguard exec and safeguard agent?
exec runs one turn non-interactively, defaults to strict and exits. safeguard agent runs one agent turn in the current workspace and, with --conversation, attaches to a server-side conversation that the web, desktop, mobile and IDE clients can see.
Does safeguard exec upload my source code?
The CLI scans on your machine and sends results to the platform. When the local runner runs platform scan jobs, only SBOMs and findings are uploaded, never source. For a check where nothing at all is uploaded, use safeguard findings.
Can exec fix vulnerabilities on its own?
Only if you loosen its permissions, and we recommend you do not. Fixes belong in an interactive session where you approve each command and edit. Autonomous remediation with fix PRs is a separate platform feature, off by default.
How do I cap what a scheduled run can spend?
--max-budget-usd ends the turn with limit_reached once that amount has been spent. Your plan's own limits still apply on top.
Can I run exec outside a git repository?
--skip-git-repo-check allows it, but then there is no way to undo what a run changes. Keep the check on, or combine it with --restricted so nothing can change.
Where do I see what an automated run did?
Name runs with --name, and check safeguard checkpoints list --session with the session id for any files the agent changed. Runs started with --private or --no-session-persistence are not stored.
Which CI systems work?
Anything that can run a shell command. The CI docs give examples for GitHub Actions, GitLab CI/CD, Bitbucket Pipelines, Jenkins and Azure Pipelines, on Linux or macOS runners with bash and curl.
Try it yourself
Start on your own machine. This walkthrough sets up the runner, picks the platform, starts it, approves it with its device code, then uses the same CLI to scan from the terminal.
Click through it at your own pace, or open it full screen.
Then download the Safeguard CLI, add the pre-push hook from Walkthrough 1 to one repository, and push. When it behaves the way you expect, add the nightly report. The full reference is in the CLI docs.
Quick check
What is the default permission mode for safeguard exec?
Strict. Nothing is asked, and anything the policy file has not pre-approved is denied.
Should the assistant decide whether a push or build fails?
No. Let the scan decide, using vuln.json, --fail-on or --fail-on-gate, and use safeguard exec to explain the result.
Which flag gives exec no writes, no shell and no network?
--restricted.
Sources
- Safeguard CLI overview
- Safeguard CLI usage guide
- Safeguard CLI interactive terminal
- Safeguard CLI slash commands
- Safeguard CLI troubleshooting
- Safeguard CI/CD integration
- Safeguard local runner
- Safeguard downloads
- Git hooks documentation
- GitHub Actions: events that trigger workflows
- GitHub Actions: about self-hosted runners
Last reviewed: 21 October 2026.