← All docs

Signet — CLI Specification (Design v0.1)

The signet CLI: how researchers and pipelines create, sign, submit, and query attestations. Rust binary (crates/signet-cli) built on signet-bundle; parity with the Spring server's Java implementation is enforced by the shared conformance vectors (backend.md §9). Related: attestation-schema.md, reputation-trust-model.md, architecture.md

1. Design Principles

  1. Scriptable first. Every command works non-interactively with flags/stdin;

interactive prompts are sugar on top, disabled by --no-input or when no TTY.

  1. Deterministic exit codes. CI gates branch on exit codes, not parsed prose.
  2. Local truth. Hashes and commit SHAs are computed from the working tree /

git — never typed by hand. The CLI refuses to attest a dirty file (uncommitted changes ⇒ the hash doesn't match any commit).

  1. Keys never travel. Signing is local (or keyless via short-lived certs);

no command accepts private key material as an argument value.

  1. Offline-capable reads. Everything verifiable is verifiable without the

hosted registry (bundles are self-contained; mirrors are first-class).


2. Command Tree

signet
├── auth        login | logout | status          # identity
├── keys        generate | add | list | delegate # self-managed profile + agent delegation
├── attest                                       # create + sign + submit audit
├── fp          add                              # false-positive entry helper
├── verify-record <urn>                          # staked co-signature
├── flag <urn>                                   # challenge an attestation (staked)
├── resolve <flag>                               # resolve a dispute (upheld | rejected)
├── disputes                                     # open flags + advisory candidates
├── vouch <identity>                             # T3 endorsement
├── profile     set                             # self-asserted display name + links
├── coverage                                     # read path query
├── deps                                         # CI dependency-status check (lockfile scan)
├── gate                                         # CI skip-scan gate
├── watch       add | remove | test              # webhook subscriptions (manage-token gated)
├── targets     list | claim | done | skip       # auditor work queue
├── ops         replay | grant | revoke | list   # operator surface: signed privileged
│               | triggers                       #   calls (PD-8); grant/revoke need the
│                                                #   root admin token; `triggers` evaluates
│                                                #   the decision log's revisit conditions
│                                                #   (exit 1 when one fires — cron it)
├── fetch <urn>                                  # get + verify one record
├── inspect <file>                               # verify + pretty-print a bundle
├── whoami | score                               # own identity / reputation
└── mirror      sync | verify | serve            # replica operations

Global flags: --registry <url> (default https://api.signet.dev, env SIGNET_REGISTRY), --json, --no-input, -q/--quiet.


3. Identity & Signing Workflow

3.1 Keyless (recommended, humans and CI)

$ signet auth login                 # opens browser → OIDC → cached session
$ signet auth login --provider github

In CI, no login step: ambient OIDC tokens (GitHub Actions id-token: write, GitLab CI) are detected automatically; each signature mints a fresh Fulcio cert. Nothing long-lived is stored.

3.2 Self-managed keys

$ signet keys generate              # ed25519 → OS keychain / encrypted file
$ signet keys add ssh:~/.ssh/id_ed25519.pub   # register existing SSH/PGP key
$ signet attest ... --key ssh      # sign with agent-held key (via ssh-agent)

3.3 Agent mode (trust model §3 operator binding)

Agents sign with their own key and must attach an operator co-signature. Two supported shapes:

  • Online co-sign: operator runs `signet keys delegate --to <agent-key>

--expires 30d`, producing a delegation record the agent embeds; or

  • Two-pass: agent produces the bundle (--output), operator co-signs

(signet inspect --cosign bundle.json) and submits.

signet attest --auditor-type agent fails closed if no operator material is present (schema doc §8 rule 6 enforced client-side too).


4. Writing: signet attest

Run from inside the audited repo checkout — target binding is derived, not typed:

$ signet attest \
    --scope lib/router/index.js:1-150 \
    --claim vetted-clean:CWE-79,CWE-89 \
    --tool manual-code-review \
    --tool semgrep@1.79.0:p/owasp-top-ten \
    --notes "Input validation at line 42 sanitizes all query parameters." \
    --submit

Behavior:

  1. Resolves repo_url from origin, commit_sha from HEAD; **errors if the

scoped files have uncommitted changes** (principle 3).

  1. Hashes every scoped file (dir scopes are expanded and pinned per schema §5.1).
  2. Builds the in-toto statement, JCS-canonicalizes, signs (keyless or --key),

assembles the bundle.

  1. --submit POSTs it; otherwise writes ./signet-attestation.json for review

or two-pass co-signing. --dry-run prints the statement and stops before signing.

Repeatable flags compose: multiple --scope, --claim status:CWEs, --tool. For non-trivial audits, --from audit.yaml accepts the whole predicate as a reviewed file — the expected path for AI agents and IDE integrations.

4.1 False positives: signet fp add

Purpose-built for the FR-1 false-positive registry — it ingests scanner output directly instead of asking the user to re-describe the finding:

$ semgrep scan --json -o findings.json
$ signet fp add --from-semgrep findings.json --finding 3 \
    --category sanitized-elsewhere \
    --justification "Redirect target allowlist-validated in lib/utils.js:88." \
    --evidence lib/utils.js:88

Extracts rule ID, tool version, location; hashes the flagged file; appends the entry to a pending attestation (or creates one). --from-sarif covers every SARIF-emitting tool (CodeQL, Snyk) with one importer.

4.2 Social records

$ signet verify-record urn:signet:att:sha256:ab12... --depth reviewed \
    --notes "Reproduced the semgrep run; agree with FP classification."
$ signet flag urn:signet:att:sha256:ab12... --reason incorrect \
    --evidence ./poc.md
$ signet vouch github:somebody --context "Colleague; 5y AppSec, reviewed their work."

verify-record prints a stake warning before signing (--depth reviewed carries slashing exposure — trust model §5) and shows the target's current dispute status so nobody co-signs a flagged record unknowingly.


5. Reading: signet coverage

$ signet coverage --repo . --min-confidence 0.8          # current checkout @ HEAD
$ signet coverage --repo . --path lib/router/index.js    # one file (per-file skip check)
$ signet coverage --purl pkg:npm/express@4.18.2
$ signet coverage --sbom sbom.cdx.json --min-confidence 0.9 --json

Human output is a per-file table (status, confidence, CWEs covered, disputes) preceded by the answer's trust context: a recorded mutation incident (!! REGISTRY TAMPERING … / ! release tag repointed …), the mapping grade (verified | attested-tag), the effective monorepo scope, and the AI pre-screen verdict with a [STALE …] marker when it predates verified tampering — a human sees exactly what the gate acts on. --json returns the API response verbatim (architecture §5). SBOM mode (CycloneDX + SPDX) fans out through the bulk endpoint; its table marks rows [TAMPERED], [repointed], and [stale-ai].

Exit codes (used by scripts, distinct from the gate below): 0 query succeeded · 10 network/registry error · 12 invalid input.


6. The CI Gate: signet gate

The skip-scan contract (FR-2) as one command — turns coverage into scanner exclusions and a pass/fail:

$ signet gate --sbom sbom.cdx.json \
    --min-confidence 0.9 \
    --require-cwe CWE-79,CWE-89 \
    --emit-excludes semgrep > .semgrep-excludes

Behavior:

  • Queries bulk coverage for every component; a file counts as covered only if

confidence ≥ threshold and the required CWE classes are all claimed (per-CWE coverage, trust model §6.2) and no open dispute.

  • --emit-excludes semgrep|sarif|paths writes the covered file list in the

target scanner's exclusion format — the scanner then runs only on what's actually unvetted (the entire product value in one flag).

  • --fail-on disputed|vuln-found (default vuln-found): fail the build if

coverage reveals an active-vulnerability-found claim on a dependency — the registry doubles as an advisory feed for exact commits.

  • Mutation incidents (PD-2/PD-3 follow-through): a purl answer may carry a

recorded mutation — the registry's persisted evidence that the package's (repo, commit) mapping CHANGED. A verified-grade mutation means the immutable registry artifact itself changed (registry tampering): the gate drops that target's files from the skip-scan list unconditionally and **fails the build under any --fail-on mode except none — tampering outranks both existing categories. An attested-tag** mutation (a release tag was repointed) warns on stderr but does not fail: the coverage in the answer already hangs off the current mapping. Both land in the SARIF log (signet/registry-tampering as error, signet/tag-repointed as warning).

Gate exit codes: 0 gate passed · 1 --fail-on condition hit · 2 policy error (bad flags) · 10 registry unreachable (pipelines choose --offline-ok to degrade to "scan everything" instead of failing).

The lighter, lockfile-level sibling is **signet deps** — no SBOM needed: it autodetects requirements.txt / package-lock.json / Cargo.lock and reports each dependency's audit + AI status, gating on --fail-on none|vuln-found|disputed|ai-findings|ai-high (vuln-found fails when a signed attestation reports an active vulnerability at the pinned version). Mutation incidents follow the gate's contract: a verified-grade mapping change (registry tampering) on any pinned dependency fails every gating mode except none and marks the row [TAMPERED]; a tag repoint warns and marks it [repointed]. --sarif <path> writes a SARIF 2.1.0 log with the same rule ids as the coverage gate at dependency granularity (vulnerabilities and tampering as errors, disputes and repoints as warnings). See ci-integration.md for both in a pipeline.

GitHub Actions sketch:

- uses: signet-dev/setup-signet@v1
- run: signet gate --sbom sbom.cdx.json --min-confidence 0.9 \
         --emit-excludes semgrep > .semgrep-excludes
- run: semgrep scan --exclude-file .semgrep-excludes

7. Verification & Mirrors

  • signet fetch <urn> downloads a bundle and fully verifies locally

(signature, cert chain, log inclusion proof, subject hashes vs. --against a local checkout if given) — trust output, not transport.

  • signet inspect bundle.json does the same for a file (pre-submission review,

two-pass co-signing).

  • signet mirror sync implements architecture §8: signed manifest → partitions →

per-bundle verification → local Postgres rebuild. signet mirror verify audits an existing replica. signet mirror serve runs the API binary against the local copy (--seeds my-seeds.json recomputes trust with your anchors).

8. Configuration

Precedence: flags > env (SIGNET_*) > repo .signet.yaml > user config. Repo-level .signet.yaml pins team policy so CI and laptops agree:

registry: https://api.signet.dev
gate:
  min_confidence: 0.9
  require_cwe: [CWE-79, CWE-89]
  fail_on: vuln-found

9. Security Considerations

  • Session/OIDC tokens in OS keychain, never plaintext dotfiles; --registry

changes to a non-configured host print a warning (token-theft phishing).

  • All timestamps in bundles come from Rekor inclusion, not the local clock.
  • attest warns when scope includes files matching secret patterns (don't leak

paths like secrets/prod.env into a public registry — scope metadata is public).

  • Rate-limit responses (429 + tier info) surface as actionable messages, not

retries-by-default (NFR-4 spam posture applies client-side too).

10. MVP Mapping & Open Questions

Milestone fit (architecture §9): M1 ships auth, attest, coverage, fetch, inspect; M2 adds fp, verify-record, flag, vouch, gate; M3 adds keys (self-managed), mirror, delegation.

  1. Exclusion-format coverage. --emit-excludes starts with semgrep + generic

path list + SARIF suppressions. Which formats next (Snyk policy, CodeQL filters) should be driven by user demand, not speculation.

  1. Line-range UX. --scope file:1-150 is easy to get subtly wrong after a

rebase. Consider --scope-from-diff <range> (attest exactly what a PR touched) as the safer primary workflow.

  1. Delegation record format. Operator→agent delegation (§3.3) needs its own

small predicate (delegation/v1) — draft alongside M2 social records.

  1. Editor integration. A signet lsp or VS Code extension surfacing

coverage/FP data inline is high-leverage but out of scope for this spec.