diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml new file mode 100644 index 000000000..62a14184f --- /dev/null +++ b/.github/workflows/dependabot-auto-vet.yml @@ -0,0 +1,233 @@ +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" + + # Force CI-friendly output: + # - --mode=local avoids opening diff.rs in browser + # - pipe through cat to disable pager behavior reliably + # Run diff and capture output, but don't let -e kill the step yet + set +e + 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="${{ 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<<$delim" + 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 "$delim" + } >> "$GITHUB_OUTPUT" + + - name: Debug OPENAI key presence + shell: bash + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_KEY }} + 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})" + + # Persist for subsequent steps in this job + echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> "$GITHUB_ENV" + + - 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: ${{ env.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 audits + 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 != '' + run: cargo vet --locked + + - name: Commit audit changes + if: steps.vet_status.outputs.status != '0' && steps.codex.outcome == 'success' && steps.codex.outputs.response != '' + 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/Cargo.lock b/Cargo.lock index 8acc4d43c..5aa35d5ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7168,13 +7168,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "getrandom 0.3.3", "js-sys", - "serde", + "serde_core", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index b22be336e..3f95ccaa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ strum = { version = "=0.27.2", features = ["derive"] } quick_cache = "=0.6.18" sugars = "=3.0.1" thiserror = "=2.0.17" -uuid = { version = "=1.18.1", features = ["v7"]} +uuid = { version = "=1.19.0", features = ["v7"]} stratus_macros = { path = "./crates/stratus_macros" } libc = "=0.2.180"