CLI
Install the devguard CLI and have it push evidence on a schedule.
Overview
The devguard CLI runs commands you declare and writes their output into an evidence record. Each collector replaces its own previous file on every push, so the record holds the current artifact instead of a growing pile. A file that passes its freshness window appears on Deadlines.
Who does what
The work splits across two people, and the order matters.
| Step | Where | Who |
|---|---|---|
| Create the evidence and link its controls | devguard | Whoever owns the evidence |
| Create an API token | devguard | An owner or an admin |
| Install the tool and sign in | A terminal | Whoever runs the build pipeline |
| Commit the configuration | The repository | Whoever runs the build pipeline |
| Push once by hand | A terminal | Whoever runs the build pipeline |
| Schedule the job | The pipeline | Whoever runs the build pipeline |
Budget about half an hour of engineering time if the check already runs in your pipeline, or half a day for the first one if nothing is scripted yet, because that work is getting the tool running rather than connecting it here. Every collector after the first is a few lines.
Before you start
- The CLI writes into evidence records that already exist and never creates them. A push against a number that is not there fails before any command runs.
- Only an owner or an admin can create a token, and only an owner or an admin can push. A member's token authenticates and is then refused at upload.
- An API token carries your full permissions in every organization you belong to. Store it as a masked secret in your pipeline and never in the repository.
- Pushing a file does not change control coverage and does not reset the evidence review date. Both stay in your hands.
- The whole setup is one file in your repository and one scheduled job, so nothing lives in the head of the person who built it. If they leave and the job stops, the file passes its window and shows up on Deadlines within days.
1. Create the evidence to keep current
Create the record in Evidence and link it to its controls. The number in the corner, such as EV-42, is what the configuration points at.
2. Create an API token
Create one under Settings, then Tokens. Pick a long expiry, or Never, for a pipeline. The default of 90 days stops the job three months later with no warning.
Only an owner or an admin can create a token, and only an owner or an admin can push. A member's token authenticates but is refused at upload, after the collectors have already run.
A devguard API token carries your full permissions in every organization you belong to. There is no read-only or single-organization token today. Store it as a masked secret in your pipeline, never in the repository, and replace it if it is ever exposed.
3. Install and sign in
Node 20 or newer is required.
npm install -g @devguardch/clidevguard login
devguard initSigning in verifies the token, lists your organizations and stores the one you pick for this address. The init command then asks what to collect and writes a first configuration. It needs a terminal, so run it on your own machine rather than in the pipeline.
Note the organization id that signing in prints. It is not the slug from the address bar, and it is the value your pipeline needs.
4. Describe what to collect
Commit a devguard.yml next to your project.
# CI runners have no stored login, so name the organization here.
# devguard login prints the id; it is not the slug from the URL.
organization: <your organization id>
collectors:
# The name is the file's identity. Every push replaces this
# collector's previous file, so the record holds one current file.
- name: npm-audit
command: npm audit --json
evidence: 42
# npm audit exits 1 as soon as it finds anything, so accept it.
allowExitCodes: [0, 1]
expiresAt: 7d
# The recipe form picks an installed scanner and fills in the
# command, the output file and the exit codes for you.
- name: vulnerability-scan
uses: scan
evidence: 57
expiresAt: 7dFields
| Field | Required | Default | Notes |
|---|---|---|---|
name | yes | Lowercase slug. Identifies the file, so renaming starts a new series. | |
command | yes | Omitted when using uses: scan. | |
evidence | yes | 42 or EV-42. Must already exist. | |
output | no | stdout | A file path when the command writes to disk. |
expiresAt | no | 30d | Freshness window, or never. |
timeout | no | 10m | |
allowExitCodes | no | [0] | Set [0, 1] for scanners that exit 1 on findings. |
allowSecrets | no | false | Per collector, never global. |
Unknown fields are rejected rather than ignored, so a typo fails immediately instead of quietly doing nothing. A collector uses either command or uses: scan, never both.
Durations accept seconds, minutes, hours, days and weeks. Use 12w for a quarter, because months and years are not units the CLI understands.
Add the .devguard folder to your gitignore. It holds the artifacts while they are being staged.
Recipes
uses: scan picks the first installed scanner, optionally forced with scanner: set to trivy, osv-scanner, grype or npm-audit. It fills in the command, the output file and the exit codes. The scanner has to be installed on the machine already.
5. Push once by hand
devguard evidence validate # config, token and every EV number
devguard evidence push --dry-run # runs everything, uploads nothing
devguard evidence pushValidating reads the configuration, checks the token and confirms every evidence number resolves, without running anything. A dry run executes the collectors and the secret scan for real while uploading nothing.
6. Let it run on a schedule
Anywhere you can run a scheduled command works, whether that is a CI provider, a server cron job or a scheduled task. GitHub Actions is only the shortest example.
name: evidence
on:
schedule: [{ cron: '17 3 * * *' }]
workflow_dispatch:
jobs:
push-evidence:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npx @devguardch/cli evidence push
env:
DEVGUARD_API_KEY: ${{ secrets.DEVGUARD_API_KEY }}Keep the freshness window longer than the gap between runs, or the file lapses between two healthy pushes. The runner needs to reach both devguard and its file storage, because the artifact uploads directly to storage rather than through the application.
Commands
| Command | What it does |
|---|---|
devguard login | Verifies a token and stores it for the target address. |
devguard init | Interactive wizard that writes a first devguard.yml. |
devguard evidence validate | Read-only preflight. Nothing runs, nothing uploads. |
devguard evidence push | Runs collectors, scans for secrets, uploads and replaces. |
devguard evidence push --dry-run | The whole pipeline as a rehearsal. |
devguard scan --evidence 42 | Runs an installed scanner and pushes its report. |
Connection settings resolve highest-first: the --url and --key flags, then DEVGUARD_API_URL and DEVGUARD_API_KEY, then the stored login, then https://app.devguard.ch.
The organization resolves from the --org flag, then organization: in the configuration, then the default stored at login. For push, validate and scan this must be the organization id. The slug is accepted only by login and init.
What leaves your machine
The command runs on your machine or on your runner, never on our servers. Only the output file you name in the collector is uploaded, so your repository, your cloud accounts and your credentials stay where they are.
Every artifact is checked against a built-in pattern set before it is uploaded, covering cloud keys, private keys, tokens and high entropy values. A match blocks the push and prints a masked report, so a stray credential cannot end up in something an auditor later opens. A collector whose output legitimately matches can set allowSecrets: true on that one entry, which stays visible in the change that introduces it.
Replacement and freshness
Each collector's name is the file's identity. Every push replaces that collector's previous file, so an evidence record holds one current file per collector. Files uploaded by hand, and files from other collectors, are never touched.
Replacing is permanent. The previous artifact is removed rather than archived, so there is no version history behind a collector. Renaming a collector starts a new series and leaves the old file behind, where nothing will refresh it.
Each file carries a freshness window. When it lapses the file appears on Deadlines under Automated Evidence, the same way an overdue review does. No notification and no email is sent for it today, so treat the Deadlines page as where you find out.
Pushing a file does not change control coverage and does not reset the evidence review date. Both stay in your hands.
When a push fails
Nothing is sent when a push fails and the previous file stays in place, so a pipeline that quietly stopped running shows up as a lapsed file rather than as silence.
| Message | Cause |
|---|---|
Authentication failed | The token is wrong, expired or not set. Run devguard login. |
EV-42 does not exist in this organization | Create the evidence in devguard first. |
This API key has no membership in organization | Usually the slug was used where the organization id is required. |
Access denied | The token belongs to a member. Only owners and admins can push. |
command produced no output on stdout | The command wrote nothing. Empty artifacts are refused. |
exit code 1 on a scanner | Add allowExitCodes: [0, 1] to that collector. |
How is this guide?