Safeguard
Developer Tools•

Automate Security Checks With safeguard exec: Hooks, Nightly Runs, PRs

Use safeguard exec for unattended checks: a pre-push hook, a nightly report sent to your phone, and a PR summary, with scans deciding pass or fail.

Safeguard Security Team
21 min read

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:

  1. 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.
  2. 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.
  3. A pull request job where safeguard ci scan lets your platform policy decide, and safeguard review plus safeguard exec leave 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

FlagWhat it does (from --help)Use it when
-a, --permission-modesupervised, edits, plan, guarded, strict or autonomous; default strictYou 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
--allowedToolsallow rules in policy syntax, for example 'Bash(go test:*)'One command must be allowed under strict
--disallowedToolsdeny rules, for example 'Read(~/.ssh/**)'You want a hard no on paths or commands
--output-formattext, json or stream-jsonAnother step reads the result
--output-schema"a JSON Schema file the answer must conform to"You want fixed fields to parse
--max-budget-usdends the turn with limit_reached once that much is spentScheduled jobs that should never run away
-C, --cdrun in another directoryCron jobs and scripts
-n, --namenames the sessionYou want to find the run later
--privatedoes not record the run in the session storeThrowaway checks
--safe-modeignores local settings, hooks, skills, plugins and SAFEGUARD.mdReproducible runs on a shared machine
--sandboxconfines 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.

  1. Confirm the CLI is signed in and ready.

    safeguard whoami
    safeguard doctor
    

    doctor checks 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.

  2. Make sure jq is installed. The gate below reads vuln.json with jq.

  3. Create the hook. Save this as .git/hooks/pre-push in 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
    
  4. 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
    
  5. 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.

  6. Share it with the team (optional). Hooks live in .git/hooks by default, which is not part of what you commit. To share, commit the script to a folder such as .githooks/ and have each person run git 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.

  1. Sign in once on that machine. If it has no browser, use the device flow:

    safeguard login --no-browser
    

    It prints a URL and a code to enter on any other device. safeguard whoami later shows the account and when the token expires, which is the first thing to check if the nightly job stops working.

  2. Find your phone's device name.

    safeguard devices
    

    This 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.

  3. 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 Pixel with your device name or a unique prefix of it. The help for safeguard send gives 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.

  4. Schedule it. With cron on Linux or macOS, run crontab -e and 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.

  5. Or schedule it in GitHub Actions on a self-hosted runner. GitHub documents the schedule event 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. Because exec needs 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.txt
    

    The odd minute (17) keeps the job off the busy top of the hour. workflow_dispatch lets you run it by hand to test.

  6. 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.

  1. Let the platform decide. Create a secret key under Settings, Developer with the scans:trigger and scans:read scopes and store it as the CI secret SAFEGUARD_API_KEY. Then:

    safeguard ci scan --wait --fail-on-gate Release
    

    --fail-on-gate waits 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 high instead if you only want a severity threshold.

  2. Review the change itself. On a signed-in self-hosted runner:

    git fetch origin main
    safeguard review --base origin/main > review.txt
    

    safeguard review reviews changes "for correctness and security", and --base diffs against a git ref instead of the working tree.

  3. 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.json
    

    Open exec-result.json once by hand before another step depends on its fields.

  4. Keep the files. Upload review.txt and exec-result.json as 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 promptBetter 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

SymptomWhat to check
exec refuses to startYou are outside a git repository. cd into the repo, or pass -C with its path
"Not authenticated" or HTTP 401safeguard whoami for the account and token expiry, then safeguard logout and safeguard login --force
Sign-in will not open a browser on a serversafeguard 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 2Results were written but the vulnerability scan did not run; run safeguard doctor
JSON file has log lines mixed inRedirect stdout alone; notices and logs go to stderr
Network or proxy errorsSet HTTP_PROXY and HTTPS_PROXY; allow outbound HTTPS on 443
Certificate errorsSet SSL_CERT_FILE to your corporate CA bundle; do not disable verification
An old version runswhich -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

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.