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 for the pipeline | 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.
- On your own machine
devguard loginopens a browser and stores a login bound to one organization. A pipeline cannot open a browser, so it carries an API token instead. - Only an owner or an admin can create a token, and only an owner or an admin can push. A member's credentials authenticate and are 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.
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 for the pipeline
A token is the pipeline's credential, because nothing there can open a browser. On your own machine you can skip this step: signing in below opens a browser for you.
Create the token under Account, 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/cliA runner without Node can use the standalone Linux binaries (x64 and arm64) attached to each GitHub release instead, verified against the SHA256SUMS file published beside them.
devguard login
devguard initSigning in opens your browser: you pick the organization this machine may act in and allow the connection, and nothing is pasted into the terminal. The login is bound to that one organization, holds no permanent secret, and appears under Account, then Connected apps, where you can revoke it. devguard logout revokes it and forgets it here.
On a host that cannot open a browser, devguard login --key asks for an API token with echo suppressed, and devguard login --key dvg_... or DEVGUARD_API_KEY hands one over without a prompt.
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, credentials and every EV number
devguard evidence push --dry-run # runs everything, uploads nothing
devguard evidence pushValidating reads the configuration, checks your credentials 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 | Opens your browser to sign in and stores the login for the target address. --key dvg_... verifies and stores an API token instead, and the bare devguard login --key prompts for one. |
devguard logout | Revokes this machine's login for that address and forgets it. |
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. A browser login is bound to one organization, so a configuration naming another one is refused before anything leaves the machine.
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, and the owner of the evidence is notified. A file still inside its window never appears there, so a healthy schedule stays off the page.
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. Fix DEVGUARD_API_KEY in the pipeline, or run devguard login --key. |
This login for ... has expired or was revoked | The browser login was disconnected, or went seven days without use. Run devguard login again. |
EV-42 does not exist in this organization | Create the evidence in devguard first. |
This login has no membership in organization | Usually the slug was used where the organization id is required. |
Access denied | The account behind the credentials is 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?