# cargo-deny configuration for gunnar — the "0 known CVE" gate.
#
# The deploy pipeline (`<ops>/deploy.sh` `door cve`) runs, on gunnar's own Cargo.lock:
# cargo deny --no-default-features check advisories
# cargo deny --no-default-features check bans
# and aborts the deploy if either goes red, exactly like a KVM boot door.
#
# Every `ignore` below is a triaged, dated decision — NOT a blanket mute.
# `yanked = "deny"` and the `bans` duplicate check are deliberately strict so a
# NEW yanked crate or a NEW advisory fails the door by name. Proven red-then-green
# 2026-08-31 (see `<ops>/deploy.sh` door history).
[advisories]
version = 2
# Fail the door on any crate the registry has yanked. A yanked crate is a warning,
# not a CVE, but the gate treats it as a hard stop so it cannot rot in silently.
yanked = "deny"
ignore = [
# ---------------------------------------------------------------------------
# RUSTSEC-2023-0071 — Marvin Attack, RSA private-key timing sidechannel.
# Crate: rsa 0.10.0-rc.18. No safe upstream version exists.
# Triaged 2026-08-31 (lane CVE-GATE):
# rsa enters ONLY through russh's default `rsa` feature (and the transitive
# ssh-key/rsa) — a capability of the SSH stack, not something gunnar calls.
# gunnar's SSH server (gunnar-ssh) mints and uses an ed25519 host key
# EXCLUSIVELY: gunnar-core::host_key::mint_ed25519 / Algorithm::Ed25519,
# with no code path that generates or signs with an RSA host key. The
# network-facing server therefore performs no RSA private-key operation.
# Verifying a client's RSA signature is not a private-key op and leaks no
# secret. The only path that could exercise an RSA private-key op is a
# gunnar CLIENT dialling with an operator-supplied ~/.ssh/id_rsa (client
# key search order: id_ed25519, id_ecdsa, id_rsa) — and its timing is
# observable only by the trusted remote it dials, not by an attacker.
# => The Marvin timing sidechannel is not reachable on gunnar's attack
# surface. Fully removing rsa from the tree is possible (russh
# default-features = false, drop `rsa`) but requires the same change in
# sibling nornir-git and pageant; tracked as a project decision.
{ id = "RUSTSEC-2023-0071", reason = "rsa is unreachable: gunnar's SSH server host key is ed25519-only (gunnar-core mint_ed25519); no RSA private-key op on any network-facing path. Triaged 2026-08-31. Full drop needs russh feature change in nornir-git+pageant (decision)." },
# ---------------------------------------------------------------------------
# RUSTSEC-2026-0222, RUSTSEC-2026-0269, RUSTSEC-2026-0316 (wasmtime 45.0.3):
# ignores REMOVED 2026-10-06 (round 4 triage). The lock carries wasmtime
# 49.0.2 only (nornir-build-thing moved off ^45), which no advisory names;
# cargo-deny warned "advisory was not encountered" for all three. A stale
# ignore would mute the next wasmtime advisory of the same id for nothing.
# ---------------------------------------------------------------------------
# RUSTSEC-2026-0173 — proc-macro-error2 is unmaintained (not a vulnerability).
# Crate: proc-macro-error2 2.0.1. No maintained release exists.
# Triaged 2026-10-06 (round 4):
# it enters ONLY as a PROC-MACRO of i18n-embed-fl, which `age` 0.11.5 needs
# unconditionally (no feature turns it off) for its CLI's localized messages;
# gunnar-crypt takes age with default-features = false, features ssh+armor
# (encrypted repositories). A proc-macro runs in the compiler and links into
# no binary: nothing of it is on any shipped gunnar's runtime path. The fix
# is upstream (age/i18n-embed-fl moving off it); re-check at every age bump.
{ id = "RUSTSEC-2026-0173", reason = "proc-macro-error2 2.0.1 is unmaintained (no vulnerability): a compile-time proc-macro of i18n-embed-fl, which age 0.11.5 needs unconditionally (gunnar-crypt: age ssh+armor). Links into no binary. No maintained release. Triaged 2026-10-06; re-check at every age bump." },
# ---------------------------------------------------------------------------
# RUSTSEC-2026-0374 — basic-toml is unmaintained (not a vulnerability).
# Crate: basic-toml 0.1.10. No safe upgrade exists.
# Triaged 2026-10-28 (lane OX-CVE):
# it enters ONLY through i18n-config 0.4.8 (the newest; 0.4.7+ moved from
# toml to basic-toml), which the PROC-MACROS i18n-embed-impl and
# i18n-embed-fl read i18n.toml with at compile time; `age` 0.11.5 needs them
# unconditionally (gunnar-crypt: age ssh+armor), the same path as
# RUSTSEC-2026-0173 above. It parses this build's own i18n.toml, never
# input from a user or the network, and links into no binary. It cannot be
# replaced from here (a third-party proc-macro's dependency); the fix is
# upstream (i18n-config moving to toml). Re-check at every age bump.
{ id = "RUSTSEC-2026-0374", reason = "basic-toml 0.1.10 is unmaintained (no vulnerability): only a compile-time dependency of i18n-config 0.4.8 (newest), read by the proc-macros i18n-embed-impl/i18n-embed-fl that age 0.11.5 needs unconditionally (gunnar-crypt). Parses the build's own i18n.toml, links into no binary. No maintained release. Triaged 2026-10-28; re-check at every age bump." },
]
[bans]
# Duplicate versions are a warning (the tree legitimately carries a few during
# the russh/ssh-key rc churn), but they are surfaced so they cannot pile up
# unseen. Flip to "deny" once the tree is consolidated.
multiple-versions = "warn"
wildcards = "allow"
# No crate is outright banned yet; add entries here to hard-fail a known-bad dep.
deny = []
[sources]
unknown-registry = "deny"
unknown-git = "allow"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
[licenses]
# Licenses are gated elsewhere; keep this permissive so `check advisories`/`bans`
# never trip on a license surprise. Left intentionally broad.
version = 2
allow = [
"MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-2-Clause",
"BSD-3-Clause", "ISC", "Unicode-3.0", "Unicode-DFS-2016", "Zlib",
"MPL-2.0", "CC0-1.0", "0BSD", "BSL-1.0",
]
confidence-threshold = 0.8