diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml new file mode 100644 index 000000000..4a57bcebc --- /dev/null +++ b/.github/workflows/dependabot-auto-vet.yml @@ -0,0 +1,274 @@ +name: Dependabot Cargo Vet + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - "*" + workflow_dispatch: + +jobs: + vet-dependabot: + if: github.actor == 'bronzelle-cw' || 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.89 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: 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: | + 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 + + # 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: | + 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' + run: echo "Vetting complete; no audits needed." + + - name: Collect unvetted dependency and crate diff + id: collect_unvetted_and_diff + if: steps.vet_status.outputs.status != '0' + run: | + set -euo pipefail + + if [ ! -s recommended.diff.cmd ]; then + echo "No recommended diff found" >&2 + exit 1 + fi + + 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" + } >> "$GITHUB_OUTPUT" + + - name: Build prompt for Codex agent + id: build_prompt + if: steps.vet_status.outputs.status != '0' + run: | + ctx="$(cat vetting-context.md)" + 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<<$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" + 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 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@v1 + 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.codex.outcome == 'failure' || steps.codex.outputs.response == '') + 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 msg = [ + 'Cargo vet still needs audits and no agent result was applied.', + '', + `Unvetted dependency: ${crate} ${version}`, + '', + '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, + repo: context.repo.repo, + issue_number: context.issue.number, + body: msg + }); + core.setFailed('Agent step failed or was not configured.') + + - 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 -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.apply_agent_decision.outputs.status == 'vetted' + 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..a457318ac 100644 --- a/VETTING_CONTEXT.md +++ b/VETTING_CONTEXT.md @@ -1,146 +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 +- Infer crate ownership, maintenance status, or ecosystem reputation unless shown in diff + +--- -If a concern is purely functional or performance-related, it must be ignored. +## 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. --- -## 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 +### 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 + +Any new or expanded build-time behavior is **POTENTIAL RISK** unless clearly inert. --- -## Risk Classification +### 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 -For each category above, classify as: +Any new outbound communication is **HIGH RISK** unless clearly documented and narrowly scoped in the diff. -- **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**. +### 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 + +Unexpected or expanded file/system access is **POTENTIAL RISK**. --- -## Output Requirements (MANDATORY) +### 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**. + +--- -Your final response **must**: +### 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 -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 +Unclear or unjustified scope expansion is **POTENTIAL RISK**. --- -## Required Output Format +## Risk Classification Rules -```text -Supply-Chain Security Vetting Summary +You must classify the overall result as: -Code Injection / Execution Risk: -- No risk detected. No evidence of unsafe execution paths, build-time abuse, or arbitrary code execution. +- **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 -Network & Exfiltration Risk: -- No risk detected. No network communication, telemetry, or external data transfer observed. +- **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 -File System & Output Risk: -- No risk detected. No unexpected file reads/writes or artifact generation. +When in doubt, choose **unvetted**. -Data Leakage Risk: -- No risk detected. No handling or exposure of sensitive data observed. +--- + +## Output Requirements (MANDATORY) -External Control / Seizure Risk: -- No risk detected. No plugins, dynamic loading, or externally influenced execution paths found. +You must respond with **JSON only**. +Do **not** include prose, markdown, or code fences. -Supply-Chain Integrity Risk: -- No risk detected. No suspicious ownership changes, scope expansion, or dependency anomalies identified. +### Required JSON Format -Conclusion: -Based on the available evidence, this dependency update does not introduce observable supply-chain or security risks within the evaluated scope. +```json +{ + "status": "vetted" | "unvetted", + "description": "Concise explanation of the assessment, explicitly referencing what was (or was not) observed in the diff." +} ``` -## Final Instruction +### Description Guidelines + +- Be factual and evidence-based -This analysis is advisory, not authoritative. -When in doubt, prefer caution and recommend human review rather than assuming safety. +- 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