Skip to content
Draft
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
233 changes: 233 additions & 0 deletions .github/workflows/dependabot-auto-vet.yml
Original file line number Diff line number Diff line change
@@ -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 <crate> <old> <new>
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 <crate> <old> <new>
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 <email>","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 }}"
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Loading