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 token for the pipelinedevguardAn 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.
  • On your own machine devguard login opens 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/cli

A 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 init

Signing 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: 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, credentials and every EV number
devguard evidence push --dry-run  # runs everything, uploads nothing
devguard evidence push

Validating 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

CommandWhat it does
devguard loginOpens 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 logoutRevokes this machine's login for that address and forgets it.
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. 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.

MessageCause
Authentication failedThe 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 revokedThe browser login was disconnected, or went seven days without use. Run devguard login again.
EV-42 does not exist in this organizationCreate the evidence in devguard first.
This login has no membership in organizationUsually the slug was used where the organization id is required.
Access deniedThe account behind the credentials is 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