From f21b72877bd3761abac4dc71b52ea73c72009c45 Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Thu, 22 Jan 2026 10:39:30 -0300 Subject: [PATCH 1/6] feat(vetting): add Dependabot Cargo Vet workflow --- .github/workflows/dependabot-auto-vet.yml | 158 ++++++++++++++++++++++ VETTING_CONTEXT.md | 9 ++ 2 files changed, 167 insertions(+) create mode 100644 .github/workflows/dependabot-auto-vet.yml diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml new file mode 100644 index 000000000..705f54ec9 --- /dev/null +++ b/.github/workflows/dependabot-auto-vet.yml @@ -0,0 +1,158 @@ +name: Dependabot Cargo Vet + +on: + pull_request_target: + types: [opened, synchronize, reopened] + branches: + - "*" + +jobs: + vet-dependabot: + if: github.actor == 'dependabot[bot]' + name: Vet Dependabot Updates + runs-on: ubuntu-22.04 + permissions: + contents: write + pull-requests: write + env: + CARGO_VET_VERSION: 0.10.0 + + steps: + - name: Checkout PR head + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: true + + - name: Set up Rust + run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.88 + + - name: Install cargo-vet + run: cargo install cargo-vet + + - name: Initial cargo vet --locked + id: vet_locked_initial + run: | + set +e + cargo vet --locked > vet-locked.log 2>&1 + echo "status=$?" >> "$GITHUB_OUTPUT" + + - name: Try importing audits + if: steps.vet_locked_initial.outputs.status != '0' + run: | + cargo vet > vet-import.log 2>&1 || true + set +e + cargo vet --locked > vet-locked-final.log 2>&1 + echo $? > vet-locked-final.status + + - name: Derive vet status + id: vet_status + run: | + if [ -f vet-locked-final.status ]; then + status="$(cat vet-locked-final.status)" + else + status="${{ steps.vet_locked_initial.outputs.status }}" + fi + echo "status=$status" >> "$GITHUB_OUTPUT" + + - name: Early exit when fully vetted + if: steps.vet_status.outputs.status == '0' + run: echo "Vetting complete; no audits needed." + + - name: Collect unvetted dependencies + if: steps.vet_status.outputs.status != '0' + run: | + logfile="vet-locked-final.log" + if [ ! -f "$logfile" ]; then + logfile="vet-locked.log" + fi + + cp VETTING_CONTEXT.md vetting-context.md + + - name: Invoke vetting agent (optional) + id: agent + if: steps.vet_status.outputs.status != '0' + continue-on-error: true + env: + AGENT_CMD: ${{ secrets.VET_AGENT_COMMAND }} + run: | + set -eo pipefail + if [ -z "$AGENT_CMD" ]; then + echo "Agent command not configured (set secrets.VET_AGENT_COMMAND)" >&2 + exit 42 + fi + # Expected agent contract: + # - Consumes unvetted.json and vetting-context.md + # - Emits agent-audits.json with entries: + # [{"crate":"name","version":"x.y.z","criteria":"safe-to-deploy","who":"Agent Name ","notes":"..."}] + $AGENT_CMD --context vetting-context.md --unvetted unvetted.json --out agent-audits.json + echo "status=$?" >> "$GITHUB_OUTPUT" + + - name: Comment when agent step failed or missing + if: steps.vet_status.outputs.status != '0' && (steps.agent.outcome == 'failure' || steps.agent.outputs.status != '0') + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const unvetted = fs.existsSync('unvetted.json') ? fs.readFileSync('unvetted.json', 'utf8') : '[]'; + const msg = [ + 'Cargo vet still needs audits and no agent result was applied.', + '', + 'Unvetted dependencies:', + '```json', + unvetted, + '```', + '', + 'Agent command not configured or failed. Configure secrets.VET_AGENT_COMMAND to integrate the vetting agent. See VETTING_CONTEXT.md for audit instructions.' + ].join('\n'); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: msg + }); + core.setFailed('Agent step failed or was not configured.') + + - name: Apply agent audits + if: steps.vet_status.outputs.status != '0' && steps.agent.outcome == 'success' && steps.agent.outputs.status == '0' + run: | + set -eo pipefail + if [ ! -f agent-audits.json ]; then + echo "agent-audits.json missing" >&2 + exit 1 + fi + audits="$(cat agent-audits.json)" + echo "Agent audits: $audits" + jq -c '.[]' agent-audits.json | while read -r row; do + crate="$(echo "$row" | jq -r '.crate')" + version="$(echo "$row" | jq -r '.version')" + criteria="$(echo "$row" | jq -r '.criteria')" + who="$(echo "$row" | jq -r '.who')" + notes="$(echo "$row" | jq -r '.notes')" + if [ -z "$crate" ] || [ -z "$version" ] || [ -z "$criteria" ] || [ -z "$who" ]; then + echo "Incomplete audit entry: $row" >&2 + exit 1 + fi + cargo vet certify "$crate" "$version" --criteria "$criteria" --who "$who" --notes "$notes" --accept-all + echo "$who" > .agent-who + done + + - name: Verify cargo vet after agent audits + if: steps.vet_status.outputs.status != '0' && steps.agent.outcome == 'success' && steps.agent.outputs.status == '0' + run: cargo vet --locked + + - name: Commit audit changes + if: steps.vet_status.outputs.status != '0' && steps.agent.outcome == 'success' && steps.agent.outputs.status == '0' + run: | + set -eo pipefail + if git diff --quiet; then + echo "No changes to commit." + exit 0 + fi + + who="$(cat .agent-who || true)" + git config user.name "${who:-dependabot-vet-bot}" + git config user.email "actions@github.com" + git add supply-chain/audits.toml supply-chain/imports.lock || true + git commit -m "chore(vet): apply agent audits" + git push origin "HEAD:${{ github.event.pull_request.head.ref }}" diff --git a/VETTING_CONTEXT.md b/VETTING_CONTEXT.md index 7a26799d5..3b4d5255a 100644 --- a/VETTING_CONTEXT.md +++ b/VETTING_CONTEXT.md @@ -123,6 +123,15 @@ Based on the available evidence, this dependency update does not introduce obser This analysis is advisory, not authoritative. When in doubt, prefer caution and recommend human review rather than assuming safety. +If the audit results are acceptable, **record the audit** using `cargo vet certify` (with an appropriate `--criteria`, `--who`, and `--notes`) so the dependency is marked as vetted. Only skip certification if explicitly disapproved by the user. + +When writing audit notes, lead with the safety posture (I/O, network, build/proc-macro, unsafe) and call out any new dependencies or feature gates. Close with the main change in one sentence so reviewers see the key impact. Example structure: +- Safety: no new I/O/network; no build.rs/proc-macro; unsafe unchanged. +- Dependencies/features: new optional feature X adds dependency Y; inert when disabled. +- Main change: short summary of what changed. + +Always create audits via `cargo vet certify ... --accept-all --notes ...` and do not pre-edit `audits.toml`. Keep a guard that fails if multiple `[[audits.]]` entries exist for the same version to prevent duplicates. + ## Cargo Vet Tool Usage Guidelines The `cargo vet inspect` command can be interactive, opening a browser and an editor. This can cause issues in non-interactive environments. From 46326217bea786caee05e6245a1820a78ebb8f7e Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Fri, 23 Jan 2026 16:18:40 -0300 Subject: [PATCH 2/6] feat(vetting): enhance Dependabot vetting workflow with improved crate diff handling and Codex integration --- .github/workflows/dependabot-auto-vet.yml | 96 +++++++++++++++-------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index 705f54ec9..065f8ec3a 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -8,7 +8,7 @@ on: jobs: vet-dependabot: - if: github.actor == 'dependabot[bot]' + if: github.actor == 'bronzelle-cw' || github.actor == 'dependabot[bot]' name: Vet Dependabot Updates runs-on: ubuntu-22.04 permissions: @@ -59,51 +59,82 @@ jobs: if: steps.vet_status.outputs.status == '0' run: echo "Vetting complete; no audits needed." - - name: Collect unvetted dependencies + - name: Collect unvetted dependency and crate diff + id: collect_unvetted_and_diff if: steps.vet_status.outputs.status != '0' run: | + # Expect a single unvetted crate from dependabot PRs; parse the line "crate:old -> new" logfile="vet-locked-final.log" if [ ! -f "$logfile" ]; then logfile="vet-locked.log" fi - + line="$(grep -m1 'unvetted dependencies:' -A2 "$logfile" | tail -n1 | tr -d '[:space:]')" + crate="${line%%:*}" + vers="${line#*:}" + old="${vers%->*}" + new="${vers#*->}" + if [ -z "$crate" ] || [ -z "$old" ] || [ -z "$new" ]; then + echo "Failed to parse unvetted crate/version from vet output" >&2 + exit 1 + fi + cargo vet diff "$crate" "$old" "$new" --mode local --output-format=human > crate-diff.txt || true + { + echo "crate=$crate" + echo "version=$new" + echo "diff<<'EOF'" + cat crate-diff.txt + echo "EOF" + } >> "$GITHUB_OUTPUT" cp VETTING_CONTEXT.md vetting-context.md - - name: Invoke vetting agent (optional) - id: agent + - name: Build prompt for Codex agent + id: build_prompt if: steps.vet_status.outputs.status != '0' - continue-on-error: true - env: - AGENT_CMD: ${{ secrets.VET_AGENT_COMMAND }} run: | - set -eo pipefail - if [ -z "$AGENT_CMD" ]; then - echo "Agent command not configured (set secrets.VET_AGENT_COMMAND)" >&2 - exit 42 - fi - # Expected agent contract: - # - Consumes unvetted.json and vetting-context.md - # - Emits agent-audits.json with entries: - # [{"crate":"name","version":"x.y.z","criteria":"safe-to-deploy","who":"Agent Name ","notes":"..."}] - $AGENT_CMD --context vetting-context.md --unvetted unvetted.json --out agent-audits.json - echo "status=$?" >> "$GITHUB_OUTPUT" + ctx="$(cat vetting-context.md)" + diff="${{ steps.collect_unvetted_and_diff.outputs.diff }}" + crate="${{ steps.collect_unvetted_and_diff.outputs.crate }}" + version="${{ steps.collect_unvetted_and_diff.outputs.version }}" + { + echo "prompt<<'EOF'" + echo "You are a Rust supply-chain security auditor. Follow VETTING_CONTEXT strictly and emit an audit for the unvetted crate." + echo + echo "VETTING_CONTEXT:" + echo "$ctx" + echo + echo "Unvetted dependency: $crate $version" + echo + echo "Diff between previous and bumped version (cargo vet diff):" + echo "$diff" + echo + echo "Respond ONLY with JSON (no prose, no code fences) matching:" + echo '[{\"crate\":\"name\",\"version\":\"x.y.z\",\"criteria\":\"safe-to-deploy\",\"who\":\"Agent Name \",\"notes\":\"concise safety-focused notes per VETTING_CONTEXT\"}]' + echo "If you cannot complete the audit, respond with an empty array: []" + echo "EOF" + } >> "$GITHUB_OUTPUT" + + - name: 🤖 Analyze and Fix Issue with Codex + id: codex + if: steps.vet_status.outputs.status != '0' + uses: openai/codex-action@main + with: + openai-api-key: ${{ secrets.OPENAI_API_KEY }} + model: gpt-5-codex + prompt: ${{ steps.build_prompt.outputs.prompt }} - name: Comment when agent step failed or missing - if: steps.vet_status.outputs.status != '0' && (steps.agent.outcome == 'failure' || steps.agent.outputs.status != '0') + if: steps.vet_status.outputs.status != '0' && (steps.codex.outcome == 'failure' || steps.codex.outputs.response == '') uses: actions/github-script@v7 with: script: | - const fs = require('fs'); - const unvetted = fs.existsSync('unvetted.json') ? fs.readFileSync('unvetted.json', 'utf8') : '[]'; + const crate = '${{ steps.collect_unvetted_and_diff.outputs.crate }}'; + const version = '${{ steps.collect_unvetted_and_diff.outputs.version }}'; const msg = [ 'Cargo vet still needs audits and no agent result was applied.', '', - 'Unvetted dependencies:', - '```json', - unvetted, - '```', + `Unvetted dependency: ${crate} ${version}`, '', - 'Agent command not configured or failed. Configure secrets.VET_AGENT_COMMAND to integrate the vetting agent. See VETTING_CONTEXT.md for audit instructions.' + 'Codex agent was not configured or did not return a response. Ensure OPENAI_API_KEY is set and the prompt is valid.' ].join('\n'); await github.rest.issues.createComment({ owner: context.repo.owner, @@ -114,13 +145,10 @@ jobs: core.setFailed('Agent step failed or was not configured.') - name: Apply agent audits - if: steps.vet_status.outputs.status != '0' && steps.agent.outcome == 'success' && steps.agent.outputs.status == '0' + if: steps.vet_status.outputs.status != '0' && steps.codex.outcome == 'success' && steps.codex.outputs.response != '' run: | set -eo pipefail - if [ ! -f agent-audits.json ]; then - echo "agent-audits.json missing" >&2 - exit 1 - fi + echo '${{ steps.codex.outputs.response }}' > agent-audits.json audits="$(cat agent-audits.json)" echo "Agent audits: $audits" jq -c '.[]' agent-audits.json | while read -r row; do @@ -138,11 +166,11 @@ jobs: done - name: Verify cargo vet after agent audits - if: steps.vet_status.outputs.status != '0' && steps.agent.outcome == 'success' && steps.agent.outputs.status == '0' + if: steps.vet_status.outputs.status != '0' && steps.codex.outcome == 'success' && steps.codex.outputs.response != '' run: cargo vet --locked - name: Commit audit changes - if: steps.vet_status.outputs.status != '0' && steps.agent.outcome == 'success' && steps.agent.outputs.status == '0' + if: steps.vet_status.outputs.status != '0' && steps.codex.outcome == 'success' && steps.codex.outputs.response != '' run: | set -eo pipefail if git diff --quiet; then From 031927ff39ea452e1533e0ca68e6899252360f0d Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Fri, 23 Jan 2026 16:51:25 -0300 Subject: [PATCH 3/6] wip: test run manually --- .github/workflows/dependabot-auto-vet.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index 065f8ec3a..f9b169b92 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -5,6 +5,7 @@ on: types: [opened, synchronize, reopened] branches: - "*" + workflow_dispatch: jobs: vet-dependabot: From 22fc9c12f0f460933818b38f0ce733da6887a4f7 Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Fri, 23 Jan 2026 17:31:31 -0300 Subject: [PATCH 4/6] fix(vetting): ensure cargo vet command continues on error for better logging --- .github/workflows/dependabot-auto-vet.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index f9b169b92..e6b0c5dd9 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -33,9 +33,9 @@ jobs: - name: Initial cargo vet --locked id: vet_locked_initial + continue-on-error: true run: | - set +e - cargo vet --locked > vet-locked.log 2>&1 + cargo vet --locked > vet-locked.log 2>&1 || true echo "status=$?" >> "$GITHUB_OUTPUT" - name: Try importing audits From 95c50b429faa5272df74849dd36ddd979b10b99a Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Fri, 23 Jan 2026 17:48:43 -0300 Subject: [PATCH 5/6] wip: testing --- .github/workflows/dependabot-auto-vet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index e6b0c5dd9..e6f612718 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -1,7 +1,7 @@ name: Dependabot Cargo Vet on: - pull_request_target: + pull_request: types: [opened, synchronize, reopened] branches: - "*" From 8920b2326aa8f7dfcaedb81a22b69e6d9275b2d5 Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Tue, 27 Jan 2026 10:20:53 -0300 Subject: [PATCH 6/6] feat(vetting): update vetting context and improve cargo vet workflow for better risk assessment --- .github/workflows/dependabot-auto-vet.yml | 205 ++++++++++++++------ VETTING_CONTEXT.md | 225 +++++++++++----------- 2 files changed, 262 insertions(+), 168 deletions(-) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index e6f612718..4a57bcebc 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -26,35 +26,48 @@ jobs: persist-credentials: true - name: Set up Rust - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.88 + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.89 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Install cargo-vet - run: cargo install cargo-vet + run: | + source "$HOME/.cargo/env" + cargo install --locked --version $CARGO_VET_VERSION cargo-vet - name: Initial cargo vet --locked id: vet_locked_initial continue-on-error: true run: | - cargo vet --locked > vet-locked.log 2>&1 || true - echo "status=$?" >> "$GITHUB_OUTPUT" + set +e + cargo vet --locked > vet-locked.log 2>&1 + status=$? + echo "status=$status" >> "$GITHUB_OUTPUT" + exit 0 - name: Try importing audits if: steps.vet_locked_initial.outputs.status != '0' run: | cargo vet > vet-import.log 2>&1 || true - set +e - cargo vet --locked > vet-locked-final.log 2>&1 - echo $? > vet-locked-final.status + + # Grab the first recommended diff line and keep the first 6 tokens: + # cargo vet diff + line="$(grep -m1 -E '^\s*cargo vet diff ' vet-import.log | sed -E 's/^\s+//')" + echo "Recommended line: $line" + echo "$line" | awk '{print $1,$2,$3,$4,$5,$6}' > recommended.diff.cmd || true + + echo "recommended.diff.cmd:" + cat recommended.diff.cmd || true - name: Derive vet status id: vet_status run: | - if [ -f vet-locked-final.status ]; then - status="$(cat vet-locked-final.status)" - else - status="${{ steps.vet_locked_initial.outputs.status }}" - fi + status="${{ steps.vet_locked_initial.outputs.status }}" echo "status=$status" >> "$GITHUB_OUTPUT" + echo "vet-locked.log:" + cat vet-locked.log || true + echo "vet-import.log:" + cat vet-import.log || true - name: Early exit when fully vetted if: steps.vet_status.outputs.status == '0' @@ -64,41 +77,56 @@ jobs: id: collect_unvetted_and_diff if: steps.vet_status.outputs.status != '0' run: | - # Expect a single unvetted crate from dependabot PRs; parse the line "crate:old -> new" - logfile="vet-locked-final.log" - if [ ! -f "$logfile" ]; then - logfile="vet-locked.log" - fi - line="$(grep -m1 'unvetted dependencies:' -A2 "$logfile" | tail -n1 | tr -d '[:space:]')" - crate="${line%%:*}" - vers="${line#*:}" - old="${vers%->*}" - new="${vers#*->}" - if [ -z "$crate" ] || [ -z "$old" ] || [ -z "$new" ]; then - echo "Failed to parse unvetted crate/version from vet output" >&2 + set -euo pipefail + + if [ ! -s recommended.diff.cmd ]; then + echo "No recommended diff found" >&2 exit 1 fi - cargo vet diff "$crate" "$old" "$new" --mode local --output-format=human > crate-diff.txt || true + + diff_cmd="$(cat recommended.diff.cmd)" + echo "Using recommended diff: $diff_cmd" + + # diff_cmd tokens: cargo vet diff + set -- $diff_cmd + crate="$4" + old="$5" + new="$6" + + # Run diff and capture output, but don't let -e kill the step yet + set +e + PAGER=cat GIT_PAGER=cat cargo vet diff "$crate" "$old" "$new" --mode=local --output-format=human \ + > crate-diff.txt 2> crate-diff.stderr + status=$? + set -e + + echo "cargo vet diff exit status: $status" + + # 0 = no diff, 1 = diff exists (expected), 2 = trouble (real failure) + if [ "$status" -gt 1 ]; then + echo "cargo vet diff failed (status=$status). stderr:" + sed -n '1,200p' crate-diff.stderr + exit "$status" + fi + + cp VETTING_CONTEXT.md vetting-context.md { echo "crate=$crate" echo "version=$new" - echo "diff<<'EOF'" - cat crate-diff.txt - echo "EOF" } >> "$GITHUB_OUTPUT" - cp VETTING_CONTEXT.md vetting-context.md - name: Build prompt for Codex agent id: build_prompt if: steps.vet_status.outputs.status != '0' run: | ctx="$(cat vetting-context.md)" - diff="${{ steps.collect_unvetted_and_diff.outputs.diff }}" + diff="$(head -c 180000 crate-diff.txt)" crate="${{ steps.collect_unvetted_and_diff.outputs.crate }}" version="${{ steps.collect_unvetted_and_diff.outputs.version }}" + delim="PROMPT_$(date +%s%N)" { - echo "prompt<<'EOF'" - echo "You are a Rust supply-chain security auditor. Follow VETTING_CONTEXT strictly and emit an audit for the unvetted crate." + echo "prompt<<$delim" + echo "You are a Rust supply-chain security auditor. Follow VETTING_CONTEXT strictly and assess the diff only." echo echo "VETTING_CONTEXT:" echo "$ctx" @@ -108,16 +136,26 @@ jobs: echo "Diff between previous and bumped version (cargo vet diff):" echo "$diff" echo - echo "Respond ONLY with JSON (no prose, no code fences) matching:" - echo '[{\"crate\":\"name\",\"version\":\"x.y.z\",\"criteria\":\"safe-to-deploy\",\"who\":\"Agent Name \",\"notes\":\"concise safety-focused notes per VETTING_CONTEXT\"}]' - echo "If you cannot complete the audit, respond with an empty array: []" - echo "EOF" + echo "Respond ONLY with JSON (no prose, no code fences) matching exactly:" + echo '{"status":"vetted|unvetted","description":"THE DESCRIPTION OF ITS ASSESSTMENT"}' + echo "$delim" } >> "$GITHUB_OUTPUT" + - name: Debug OPENAI key presence + if: steps.vet_status.outputs.status != '0' + run: | + if [ -z "$OPENAI_API_KEY" ]; then + echo "OPENAI_API_KEY is empty/missing" + exit 1 + fi + echo "OPENAI_API_KEY present (length: ${#OPENAI_API_KEY})" + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + - name: 🤖 Analyze and Fix Issue with Codex id: codex if: steps.vet_status.outputs.status != '0' - uses: openai/codex-action@main + uses: openai/codex-action@v1 with: openai-api-key: ${{ secrets.OPENAI_API_KEY }} model: gpt-5-codex @@ -145,33 +183,82 @@ jobs: }); core.setFailed('Agent step failed or was not configured.') - - name: Apply agent audits + - name: Apply agent decision + id: apply_agent_decision if: steps.vet_status.outputs.status != '0' && steps.codex.outcome == 'success' && steps.codex.outputs.response != '' run: | - set -eo pipefail - echo '${{ steps.codex.outputs.response }}' > agent-audits.json - audits="$(cat agent-audits.json)" - echo "Agent audits: $audits" - jq -c '.[]' agent-audits.json | while read -r row; do - crate="$(echo "$row" | jq -r '.crate')" - version="$(echo "$row" | jq -r '.version')" - criteria="$(echo "$row" | jq -r '.criteria')" - who="$(echo "$row" | jq -r '.who')" - notes="$(echo "$row" | jq -r '.notes')" - if [ -z "$crate" ] || [ -z "$version" ] || [ -z "$criteria" ] || [ -z "$who" ]; then - echo "Incomplete audit entry: $row" >&2 - exit 1 - fi - cargo vet certify "$crate" "$version" --criteria "$criteria" --who "$who" --notes "$notes" --accept-all - echo "$who" > .agent-who - done - - - name: Verify cargo vet after agent audits - if: steps.vet_status.outputs.status != '0' && steps.codex.outcome == 'success' && steps.codex.outputs.response != '' + set -euo pipefail + + crate="${{ steps.collect_unvetted_and_diff.outputs.crate }}" + version="${{ steps.collect_unvetted_and_diff.outputs.version }}" + + echo '${{ steps.codex.outputs.response }}' > agent-decision.json + + # Validate JSON shape + jq -e ' + (type=="object") + and (.status | type=="string") + and (.description | type=="string") + and (.status=="vetted" or .status=="unvetted") + ' agent-decision.json > /dev/null + + status="$(jq -r '.status' agent-decision.json)" + desc="$(jq -r '.description' agent-decision.json)" + + # Export for later steps (commenting) + delim="DESC_$(date +%s%N)" + { + echo "status=$status" + echo "description<<$delim" + echo "$desc" + echo "$delim" + } >> "$GITHUB_OUTPUT" + + if [ "$status" = "unvetted" ]; then + echo "Agent marked as unvetted; will not certify." + exit 0 + fi + + who="dependabot-vet-bot " + notes_one_line="$(printf "%s" "$desc" | tr '\n' ' ' | tr -s ' ')" + + cargo vet certify "$crate" "$version" \ + --criteria "safe-to-deploy" \ + --who "$who" \ + --notes "$notes_one_line" \ + --accept-all + + echo "$who" > .agent-who + + - name: Comment when agent refuses to vet + if: steps.vet_status.outputs.status != '0' && steps.apply_agent_decision.outputs.status == 'unvetted' + uses: actions/github-script@v7 + with: + script: | + const crate = '${{ steps.collect_unvetted_and_diff.outputs.crate }}'; + const version = '${{ steps.collect_unvetted_and_diff.outputs.version }}'; + const desc = `${{ steps.apply_agent_decision.outputs.description }}`; + const msg = [ + 'Cargo vet still needs audits, and the automated diff review **did not approve** this update.', + '', + `**Dependency:** \`${crate} ${version}\``, + '', + '**Assessment:**', + desc + ].join('\n'); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: msg + }); + + - name: Verify cargo vet after agent decision + if: steps.vet_status.outputs.status != '0' && steps.apply_agent_decision.outputs.status == 'vetted' run: cargo vet --locked - name: Commit audit changes - if: steps.vet_status.outputs.status != '0' && steps.codex.outcome == 'success' && steps.codex.outputs.response != '' + if: steps.vet_status.outputs.status != '0' && steps.apply_agent_decision.outputs.status == 'vetted' run: | set -eo pipefail if git diff --quiet; then diff --git a/VETTING_CONTEXT.md b/VETTING_CONTEXT.md index 3b4d5255a..a457318ac 100644 --- a/VETTING_CONTEXT.md +++ b/VETTING_CONTEXT.md @@ -1,155 +1,162 @@ -# AI Agent Context — Vetting Supply-Chain Risk Assessment +# AI Agent Context — Dependency Diff Vetting (Diff-Only) ## Role -You are acting as a **Rust supply-chain security auditor**. -Your task is to assess **risk introduced by dependency version changes** detected by `cargo vet`, not to evaluate functional correctness or performance. +You are acting as a **Rust supply-chain security auditor**. -This assessment is **risk-oriented only** and must be conservative. +Your task is to assess **security and supply-chain risk introduced *only* by the code changes shown in the provided diff** between two versions of a dependency. + +You **do not** have access to: +- The full repository +- Cargo metadata +- Crate registry information +- Dependency graphs +- External tooling (`cargo vet`, `cargo metadata`, etc.) + +You must base your assessment **exclusively on the diff text provided**. + +Be conservative. If the diff is insufficient to confidently assess safety, you must mark it as **unvetted**. --- -## Explicitly Out of Scope (Do NOT assess) +## Explicitly Out of Scope (DO NOT assess) -You must **NOT**: -- Validate whether the code works -- Validate correctness or logic -- Evaluate performance, memory usage, or benchmarks -- Review API design or developer ergonomics -- Judge code quality or style +You must **not**: +- Evaluate functional correctness or bugs +- Evaluate performance or benchmarks +- Judge code quality, style, or refactors +- Assume intent beyond what is shown +- Assume safety based on reputation, popularity, or prior versions - Assume test coverage implies safety - -If a concern is purely functional or performance-related, it must be ignored. +- Infer crate ownership, maintenance status, or ecosystem reputation unless shown in diff --- -## In-Scope: What You MUST Assess - -You are assessing **supply-chain and security risks only**, focusing on whether a dependency could: - -### 1. Code Injection / Execution Risk -- Introduce unsafe code paths -- Execute arbitrary code (build scripts, proc-macros, runtime execution) -- Abuse `unsafe` in a way that could allow privilege escalation -- Modify build output or compilation behavior unexpectedly - -### 2. Network & Exfiltration Risk -- Open network connections (HTTP, TCP, UDP, DNS, WebSocket, etc.) -- Send telemetry, metrics, logs, or identifiers externally -- Depend on crates whose purpose includes networking without clear justification -- Introduce hidden or undocumented remote calls - -### 3. File System & Output Risk -- Read or write files unexpectedly -- Modify configuration, credentials, or runtime state -- Create artifacts, logs, or cache files that could leak data -- Access environment variables in a suspicious way - -### 4. Data Leakage Risk -- Access sensitive data (environment variables, keys, tokens, user data) -- Serialize or log potentially sensitive information -- Expand attack surface for accidental or malicious leakage - -### 5. External Control / Seizure Risk -- Introduce plugin systems, dynamic loading, or scripting engines -- Enable runtime extensibility that could be externally influenced -- Add hooks, callbacks, or IPC mechanisms not strictly required -- Depend on crates that execute externally supplied input - -### 6. Supply-Chain Integrity Risk (Additional) -You must also consider: -- Crate ownership changes -- Sudden large increases in code size or scope -- New transitive dependencies with unclear purpose -- Build-time code execution (`build.rs`, proc-macros) -- License changes that could affect compliance -- Crates with known prior security incidents or abandoned maintenance +## In Scope: What You MUST Assess (Based on the Diff Only) + +Assess whether the **changes introduced by the diff** add or increase supply-chain or security risk. + +### 1. Code Execution & Unsafe Behavior +Check whether the diff introduces or expands: +- `unsafe` blocks or functions +- Raw pointer manipulation +- FFI (`extern`, `libc`, bindings) +- Dynamic code execution +- Build-time execution (`build.rs`) +- Procedural macros or macro expansion that executes code + +If new `unsafe` code is added or existing unsafe code is expanded, this is at least **POTENTIAL RISK** unless clearly constrained and justified by the diff. --- -## Risk Classification +### 2. Build-Time or Compile-Time Execution +Check for: +- New or modified `build.rs` +- Changes to build scripts +- New compile-time code execution paths +- Environment variable access during build -For each category above, classify as: +Any new or expanded build-time behavior is **POTENTIAL RISK** unless clearly inert. -- **NO RISK DETECTED** – no indicators of concern -- **POTENTIAL RISK** – requires human review -- **HIGH RISK** – strong indicators of malicious or unsafe behavior +--- -If you cannot confidently determine safety, **default to POTENTIAL RISK**. +### 3. Network or IPC Behavior +Check for: +- New networking code (HTTP, TCP, UDP, DNS, WebSocket) +- New dependencies or modules related to networking +- Telemetry, metrics, logging to external endpoints +- IPC, sockets, or OS-level communication + +Any new outbound communication is **HIGH RISK** unless clearly documented and narrowly scoped in the diff. --- -## Output Requirements (MANDATORY) +### 4. File System & Environment Access +Check for: +- New file reads/writes +- Access to configuration files, credentials, or runtime state +- Use of environment variables +- Creation of logs, caches, or artifacts -Your final response **must**: +Unexpected or expanded file/system access is **POTENTIAL RISK**. -1. Explicitly mention **each risk category** -2. State clearly whether **risk was found or not** -3. Use **plain, factual language** -4. Avoid speculation beyond evidence -5. Include a short concluding summary +--- + +### 5. Data Exposure & Leakage +Check for: +- Serialization or logging of internal data +- Debug output that could expose sensitive values +- Expansion of public APIs that expose internal state + +If sensitive data could plausibly be exposed, mark as **POTENTIAL RISK**. --- -## Required Output Format +### 6. Scope Expansion & Attack Surface +Check for: +- Large increases in code size unrelated to the stated change +- New modules, features, or entry points +- New dependencies introduced in the diff +- New feature flags that enable risky behavior -```text -Supply-Chain Security Vetting Summary +Unclear or unjustified scope expansion is **POTENTIAL RISK**. -Code Injection / Execution Risk: -- No risk detected. No evidence of unsafe execution paths, build-time abuse, or arbitrary code execution. +--- -Network & Exfiltration Risk: -- No risk detected. No network communication, telemetry, or external data transfer observed. +## Risk Classification Rules -File System & Output Risk: -- No risk detected. No unexpected file reads/writes or artifact generation. +You must classify the overall result as: -Data Leakage Risk: -- No risk detected. No handling or exposure of sensitive data observed. +- **vetted** + - No new unsafe behavior + - No new execution paths (build, runtime, network) + - No expanded I/O, environment, or attack surface + - Changes are narrow, mechanical, or clearly constrained -External Control / Seizure Risk: -- No risk detected. No plugins, dynamic loading, or externally influenced execution paths found. +- **unvetted** + - Any **POTENTIAL RISK** or **HIGH RISK** + - Insufficient information in the diff to confidently assess safety + - Large or complex changes whose impact cannot be determined from the diff alone -Supply-Chain Integrity Risk: -- No risk detected. No suspicious ownership changes, scope expansion, or dependency anomalies identified. +When in doubt, choose **unvetted**. -Conclusion: -Based on the available evidence, this dependency update does not introduce observable supply-chain or security risks within the evaluated scope. -``` +--- + +## Output Requirements (MANDATORY) -## Final Instruction +You must respond with **JSON only**. +Do **not** include prose, markdown, or code fences. -This analysis is advisory, not authoritative. -When in doubt, prefer caution and recommend human review rather than assuming safety. +### Required JSON Format + +```json +{ + "status": "vetted" | "unvetted", + "description": "Concise explanation of the assessment, explicitly referencing what was (or was not) observed in the diff." +} +``` -If the audit results are acceptable, **record the audit** using `cargo vet certify` (with an appropriate `--criteria`, `--who`, and `--notes`) so the dependency is marked as vetted. Only skip certification if explicitly disapproved by the user. +### Description Guidelines -When writing audit notes, lead with the safety posture (I/O, network, build/proc-macro, unsafe) and call out any new dependencies or feature gates. Close with the main change in one sentence so reviewers see the key impact. Example structure: -- Safety: no new I/O/network; no build.rs/proc-macro; unsafe unchanged. -- Dependencies/features: new optional feature X adds dependency Y; inert when disabled. -- Main change: short summary of what changed. +- Be factual and evidence-based -Always create audits via `cargo vet certify ... --accept-all --notes ...` and do not pre-edit `audits.toml`. Keep a guard that fails if multiple `[[audits.]]` entries exist for the same version to prevent duplicates. +- Refer only to what is visible in the diff -## Cargo Vet Tool Usage Guidelines +- If unvetted due to uncertainty, state why the diff was insufficient -The `cargo vet inspect` command can be interactive, opening a browser and an editor. This can cause issues in non-interactive environments. +- Mention concrete indicators (e.g. “new unsafe block added”, “no new I/O or networking observed”) -For non-interactive auditing and certification, use the following approaches: +### Final Instruction -- **`cargo vet diff`**: This command is challenging for clean programmatic use in non-interactive environments. While its output can be successfully redirected to a file (thus avoiding an interactive pager), it still often includes human-readable introductory messages (potentially on `stderr` or interleaved with `stdout`) even when `--output-format=json` is specified. Furthermore, its JSON output may contain embedded error objects (e.g., `{"error": {"message": "unsupported",...}}`) which can lead to non-zero exit codes. This combination of verbose, non-standard JSON output and potential errors makes it difficult to reliably parse programmatically in automated CI/CD pipelines. For automated diff analysis, direct parsing of `cargo vet diff`'s output is not recommended without robust error handling and text processing to strip extraneous information. +This assessment is advisory and conservative. - To obtain the raw diff content for manual review, you can redirect the output, avoiding pagers: - `cargo vet diff --mode local --output-format=text | cat` +You are not approving functionality — only judging whether the diff introduces observable supply-chain or security risk. -- **`cargo vet certify`**: To certify a crate non-interactively, use the `--accept-all` flag. You can provide notes directly using the `--notes` argument. This bypasses the interactive diff entirely and allows for direct certification based on a summary of changes. - Example: `cargo vet certify serde 1.0.189 --criteria safe-to-deploy --who "Alice Example " --notes "Routine dependency bump; no unsafe code changes" --accept-all` +If the diff does not provide enough evidence to confidently mark the change as safe, return: - **Important Note for `audits.toml`**: When providing multi-line notes, ensure you use *real breaklines* within the TOML string, enclosed in triple *double* quotes (`"""..."""`), instead of `\n` escape sequences. For example: - ```toml - notes = """Line 1 - Line 2 - Line 3""" - ``` - This ensures proper formatting and readability in the generated `audits.toml`. +```json +{ + "status": "unvetted", + "description": "Insufficient information in the diff to confidently assess supply-chain risk." +} +``` \ No newline at end of file