devguard

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.

StepWhereWho
Create the evidence and link its controlsdevguardWhoever owns the evidence
Create an API tokendevguardAn owner or an admin
Install the tool and sign inA terminalWhoever runs the build pipeline
Commit the configurationThe repositoryWhoever runs the build pipeline
Push once by handA terminalWhoever runs the build pipeline
Schedule the jobThe pipelineWhoever 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/cli
devguard login
devguard init

Signing 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: 7d

Fields

FieldRequiredDefaultNotes
nameyesLowercase slug. Identifies the file, so renaming starts a new series.
commandyesOmitted when using uses: scan.
evidenceyes42 or EV-42. Must already exist.
outputnostdoutA file path when the command writes to disk.
expiresAtno30dFreshness window, or never.
timeoutno10m
allowExitCodesno[0]Set [0, 1] for scanners that exit 1 on findings.
allowSecretsnofalsePer 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 push

Validating 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

CommandWhat it does
devguard loginVerifies a token and stores it for the target address.
devguard initInteractive wizard that writes a first devguard.yml.
devguard evidence validateRead-only preflight. Nothing runs, nothing uploads.
devguard evidence pushRuns collectors, scans for secrets, uploads and replaces.
devguard evidence push --dry-runThe whole pipeline as a rehearsal.
devguard scan --evidence 42Runs 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.

MessageCause
Authentication failedThe token is wrong, expired or not set. Run devguard login.
EV-42 does not exist in this organizationCreate the evidence in devguard first.
This API key has no membership in organizationUsually the slug was used where the organization id is required.
Access deniedThe token belongs to a member. Only owners and admins can push.
command produced no output on stdoutThe command wrote nothing. Empty artifacts are refused.
exit code 1 on a scannerAdd allowExitCodes: [0, 1] to that collector.

How is this guide?

On this page