Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,47 @@ on:

permissions:
contents: read
issues: write # so a failed scan can open/refresh a tracking issue (#82)

# One audit at a time: a manual workflow_dispatch overlapping the 07:00 cron must not race the
# search-then-create issue de-dup below into duplicate trackers. (#82)
concurrency:
group: supply-chain-audit
cancel-in-progress: false

jobs:
advisories:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
- id: deny
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check advisories
# Make the daily watch LOUD (#82): if the ADVISORY SCAN itself failed (the `steps.deny.outcome`
# guard, so a checkout / action-infra error does NOT masquerade as an advisory), a new RUSTSEC
# advisory or a yanked crate was found — the nightly keeps `yanked = "deny"` even though PRs only
# warn. A red scheduled run just emails admins by default, easy to miss, so open a tracking issue
# (or comment on the open one). `gh` is preinstalled: no third-party action added.
- name: Open or refresh advisory tracking issue on failure
if: failure() && steps.deny.outcome == 'failure'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
title="Daily cargo-deny advisory scan is failing"
run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
body=$(printf '%s\n\n%s\n\n%s' \
"The nightly \`cargo deny check advisories\` run failed — a new RustSec advisory or a yanked dependency was found. See the run for the crate and RUSTSEC id." \
"Failed run: ${run_url}" \
"Triage in \`deny.toml\`: bump the dependency, or add a justified \`ignore\` entry. Close this issue once the nightly scan is green again.")
# Tolerate a transient list failure (|| true) so a flaky API can't swallow the alert, and
# match the title EXACTLY (gh search is tokenized/fuzzy) so we never comment on a look-alike.
existing=$(gh issue list -R "$REPO" --state open --search "${title} in:title" \
--json number,title --jq "map(select(.title == \"${title}\")) | .[0].number // empty" || true)
if [ -n "${existing}" ]; then
gh issue comment "${existing}" -R "$REPO" --body "${body}"
else
gh issue create -R "$REPO" --title "${title}" --body "${body}"
fi
19 changes: 13 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ name: CI
#
# quick (fmt, ~1 min) ──┬── linux : full build + clippy + test --workspace (+ anvil/Foundry)
# ├── macos : build the shipping binaries + lint the macOS-only tray path
# ├── cargo-deny-advisories : security advisories gate (BLOCKING)
# ├── cargo-deny-advisories : security advisories gate (BLOCKING; a bare yank only warns on PRs, #82)
# └── cargo-deny-supply-chain : bans/licenses/sources gate (BLOCKING)
#
# The `quick` gate fails cheap on the most common trivial mistake (unformatted code) before the two
Expand Down Expand Up @@ -108,11 +108,14 @@ jobs:
- run: cargo build --locked -p deckard-app --features tray
- run: cargo clippy --locked -p deckard-app --all-targets --features tray -- -D warnings

# Supply-chain SECURITY gate — advisories only. BLOCKING. Config: deny.toml. Every advisory the
# RustSec DB reports is either fixed or carries a written, justified `ignore` in deny.toml; a new,
# untriaged advisory fails this check and blocks the merge until a human triages it. A daily
# re-scan against the committed Cargo.lock runs from .github/workflows/audit.yml.
# Rationale: docs/AGENTIC-ENGINEERING.md §4.
# Supply-chain SECURITY gate — advisories only. BLOCKING on real RUSTSEC advisories. Config:
# deny.toml. Every advisory the RustSec DB reports is either fixed or carries a written, justified
# `ignore` in deny.toml; a new, untriaged advisory fails this check and blocks the merge until a
# human triages it. deny.toml sets `yanked = "deny"`; ON PULL REQUESTS ONLY we downgrade a bare
# crates.io yank to a warning (`--warn yanked`) so outside-world drift can't block an unrelated PR
# (#82). At release (workflow_call → event_name=push), on push to main, and in the nightly audit,
# yanks still block. A daily re-scan runs from .github/workflows/audit.yml (which opens a tracking
# issue on failure). Rationale: docs/AGENTIC-ENGINEERING.md §4.
cargo-deny-advisories:
needs: quick
runs-on: ubuntu-latest
Expand All @@ -121,6 +124,10 @@ jobs:
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check advisories
# PR-only yank downgrade (warn, not block). Empty elsewhere → deny.toml's `yanked = "deny"`
# stands, so release/push/nightly still block yanks. cargo-deny-action appends
# command-arguments after the command: `cargo-deny … check advisories --warn yanked`. (#82)
command-arguments: ${{ github.event_name == 'pull_request' && '--warn yanked' || '' }}

# Supply-chain HYGIENE — bans / licenses / sources. BLOCKING. The license allow-list and bans
# policy are now fully seeded (deny.toml): permissive licenses allowed, GPL-3.0 scoped per-crate to
Expand Down
18 changes: 14 additions & 4 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
# Run locally with: cargo install cargo-deny && cargo deny check (or `cargo deny check advisories`).
#
# CI splits this into two jobs (ci.yml):
# • cargo-deny-advisories — `check advisories` → BLOCKING. Every advisory is fixed or
# carries a justified `ignore` below.
# • cargo-deny-supply-chain — `check bans sources licenses` → non-blocking until seeded (issue #40).
# • cargo-deny-advisories — `check advisories` → BLOCKING on real advisories. Every advisory
# is fixed or carries a justified `ignore`
# below. On PRs a bare yank only warns;
# release + nightly still block yanks (#82).
# • cargo-deny-supply-chain — `check bans sources licenses` → BLOCKING (required PR check).
# A daily `check advisories` re-scan runs from .github/workflows/audit.yml.

[graph]
Expand All @@ -17,7 +19,15 @@ all-features = true

[advisories]
# RustSec advisory DB. Vulnerabilities and unmaintained crates are denied by default in cargo-deny
# v2; we additionally refuse yanked crates. Add to `ignore` ONLY with a written justification.
# v2 — a real advisory blocks PRs AND releases until it is fixed or given a justified `ignore` below.
#
# `yanked = "deny"` is the BASELINE: releases, pushes to main, and the nightly audit (audit.yml) all
# refuse a yanked crate. The PR-time `cargo-deny-advisories` job in ci.yml alone downgrades this to a
# warning (`--warn yanked`, gated on `pull_request`) so a crates.io "yank" — non-deterministic
# outside-world drift that can flip with no change on our side, like the `bitcoin_hashes` yank that
# blocked unrelated PR #81 — stays visible on the PR without gating it. We keep the release boundary
# strict (not assume "a security yank always carries a RUSTSEC advisory") because a security-motivated
# yank can PRECEDE its advisory. Add to `ignore` ONLY with a written justification. (#82)
#
# Every entry below is a TRANSITIVE advisory with NO in-tree fix; removing them is tracked in #40.
# Re-evaluate the whole list on each `just bump-gpui` / Helios / alloy bump — drop any that resolve.
Expand Down
28 changes: 27 additions & 1 deletion docs/AGENTIC-ENGINEERING.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ disallowed licenses, and untrusted sources. New `deny.toml`:

```toml
[advisories]
yanked = "deny"
yanked = "deny" # baseline; the PR-time job alone downgrades to warn — see "Advisory policy" below (#82)
ignore = [] # add { id = "RUSTSEC-…", reason = "…" } only with written justification

[licenses]
Expand Down Expand Up @@ -182,6 +182,32 @@ licenses`), and `multiple-versions` must stay `warn` because the git gpui tree l
duplicates crates. We therefore land the CI job **non-blocking** and promote it after one green run
(see Rollout).

**Advisory policy — what gates a PR vs what only informs (#82).** Split the gate by *determinism*.
`bans` / `licenses` / `sources` are deterministic — they only change when *we* change dependencies —
so they stay **required, blocking PR gates**. The advisory check is different: it re-reads the live
RustSec database and crates.io yank status on every run, so a dependency yank or a freshly-published
CVE can turn a PR red with **no change on our side**, blocking work that never touched that
dependency (this is what bit PR #81). We handle that by *kind*, and by *where*:

- **A real RUSTSEC advisory always blocks** — on PRs, at release, on push to main, and in the daily
scan. A known vulnerability stops the line until it is fixed or given a justified `ignore`.
- **A bare crates.io yank blocks everywhere EXCEPT pull requests.** `deny.toml` keeps
`yanked = "deny"`; the PR-time `cargo-deny-advisories` job alone downgrades it to a warning
(`--warn yanked`, gated on `pull_request`), so a yank stays visible on the PR without blocking
unrelated work — while the release boundary, push-to-main, and the nightly audit still refuse a
yanked dependency. A yank is usually a pulled publish, not a vulnerability; but because a
security-motivated yank can *precede* its RustSec advisory, we keep the release boundary strict
rather than assume the advisory already exists.

The safety net is three things, not the PR gate: continuous **daily detection**
(`.github/workflows/audit.yml`, which opens a tracking issue on failure so a new advisory or yank is
loud, not just an email), a **hard gate at the release boundary** (the reusable `ci.yml` runs the
advisory check — yanks included — blocking when invoked from `release.yml`, so a known-vulnerable or
yanked tree can never ship), and the `ignore` list as the **deliberate, reviewed** escape hatch for
transitive advisories with no in-tree fix. This is the mainstream Rust-OSS posture (schedule the
non-deterministic check, gate the deterministic ones), tuned so "we take security seriously" means
*never ship a vulnerable build*, not *block every contributor on outside-world drift*.

### 5. Close the CI gaps

**What.** Our CI currently runs only `cargo build`, `cargo build --features tray`, and
Expand Down
Loading