From 5ed572d8e9b19e77c092538d61fca4dba7337695 Mon Sep 17 00:00:00 2001 From: jadamcrain Date: Mon, 27 Jul 2026 09:24:12 -0700 Subject: [PATCH 1/2] Fix security audit result handling and trigger scope Run cargo audit exactly once and derive the verdict from the JSON report rather than the process exit status. The previous logic treated any nonzero exit as "vulnerabilities detected", so a network or advisory database failure produced a misleading job summary. It also invoked cargo audit twice, making the outcome depend on which of the two runs failed, and redirected stderr into audit.json, which meant the report was not necessarily valid JSON. Warnings (unmaintained, yanked, unsound) are now emitted as workflow annotations, since cargo-audit prints nothing in JSON mode and the second invocation was previously the only thing surfacing them. The report is also uploaded as an artifact. Drop the paths filter from the pull_request trigger so every PR gets a consistent audit verdict. The job only binstalls a prebuilt cargo-audit and reads Cargo.lock, so it is inexpensive relative to the rest of CI, and an unfiltered trigger is a prerequisite for making this a required status check, as a path-filtered workflow reports no status at all. Add a push trigger for main so a dependency change is audited against the advisory database as of the merge. main is not branch protected in this repository, so this also covers pushes that bypass a pull request entirely. Add an explicit read-only permissions block and pin cargo-binstall to a commit SHA instead of tracking a mutable branch. --- .github/workflows/security-audit.yml | 82 ++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 676de7af..428d2f9b 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -2,17 +2,30 @@ name: Security Audit on: schedule: - # Run daily at 2 AM UTC + # Run daily at 2 AM UTC. This is the trigger doing the real work: findings + # change when RustSec publishes an advisory, not when we push code. - cron: '0 2 * * *' # Allow manual trigger for testing workflow_dispatch: - # Run on PRs to test before merging - pull_request: + # Run on push to main so a newly introduced dependency is audited against the + # advisory database as of the merge, not as of whenever the PR last ran. main + # is not branch protected here, so this also covers direct pushes. + push: + branches: + - main paths: - '**/Cargo.toml' - '**/Cargo.lock' - '.github/workflows/security-audit.yml' - '.cargo/audit.toml' + # Run on PRs to test before merging. The job binstalls cargo-audit and reads + # Cargo.lock, so it's cheap enough to run unfiltered - and an unfiltered + # trigger is a prerequisite for ever making this a required status check, + # since a path-filtered workflow reports no status at all. + pull_request: + +permissions: + contents: read jobs: audit: @@ -22,22 +35,56 @@ jobs: uses: actions/checkout@v6 - name: Install cargo-binstall - uses: cargo-bins/cargo-binstall@main + uses: cargo-bins/cargo-binstall@e00d2c94cc0067b77737821097a62d91c0301baa # v1.21.1 - name: Install cargo-audit binary (fast) run: cargo binstall cargo-audit@0.22.1 --no-confirm + # Run cargo-audit exactly once. The exit status alone can't distinguish + # "vulnerabilities found" from "the tool or advisory DB fetch failed", so + # the verdict comes from parsing the JSON report instead. Unparseable + # output means the run itself failed and is reported as such. - name: Run security audit id: audit run: | - if cargo audit --json > audit.json 2>&1; then - echo "audit_failed=false" >> $GITHUB_OUTPUT - else + set +e + cargo audit --json > audit.json 2> audit.err + status=$? + set -e + + # Diagnostics and warnings go to stderr even in JSON mode + cat audit.err + + if ! jq -e . audit.json > /dev/null 2>&1; then + echo "::error::cargo audit failed to produce a report (exit ${status}); this is a tool or network failure, not a vulnerability finding" + exit 1 + fi + + # Warnings (unmaintained, yanked, unsound) don't fail the build, but + # cargo-audit prints nothing at all in JSON mode, so surface them here. + jq -r '.warnings | to_entries[] | .key as $kind | .value[] + | "::warning::\($kind): \(.advisory.id) \(.package.name) \(.package.version)"' audit.json + + count=$(jq -r '.vulnerabilities.count // 0' audit.json) + echo "count=${count}" >> $GITHUB_OUTPUT + + if [ "${count}" -gt 0 ]; then echo "audit_failed=true" >> $GITHUB_OUTPUT + jq -r '.vulnerabilities.list[] + | "\(.advisory.id) \(.package.name) \(.package.version): \(.advisory.title)"' audit.json + else + echo "audit_failed=false" >> $GITHUB_OUTPUT + echo "No vulnerable dependencies found." fi - # Always show the human-readable output - cargo audit || true + - name: Upload audit report + if: always() && hashFiles('audit.json') != '' + uses: actions/upload-artifact@v6 + with: + name: audit-report + path: | + audit.json + audit.err # Create a job summary that's visible in the Actions tab - name: Create job summary @@ -45,15 +92,18 @@ jobs: run: | echo "## 🚨 Security Vulnerabilities Detected" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "The security audit has detected vulnerabilities in the dependencies." >> $GITHUB_STEP_SUMMARY + echo "\`cargo audit\` found ${{ steps.audit.outputs.count }} vulnerable dependencies." >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "### Required Actions:" >> $GITHUB_STEP_SUMMARY - echo "1. Review the audit output above for details" >> $GITHUB_STEP_SUMMARY - echo "2. Run \`cargo audit\` locally to see the full report" >> $GITHUB_STEP_SUMMARY - echo "3. Update affected dependencies using \`cargo update\`" >> $GITHUB_STEP_SUMMARY - echo "4. Review if these vulnerabilities affect your production deployments" >> $GITHUB_STEP_SUMMARY + echo '| Advisory | Package | Version | Title |' >> $GITHUB_STEP_SUMMARY + echo '| --- | --- | --- | --- |' >> $GITHUB_STEP_SUMMARY + jq -r '.vulnerabilities.list[] + | "| [\(.advisory.id)](https://rustsec.org/advisories/\(.advisory.id)) | \(.package.name) | \(.package.version) | \(.advisory.title) |"' \ + audit.json >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "**Workflow run:** [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY + echo "### Required Actions:" >> $GITHUB_STEP_SUMMARY + echo "1. Run \`cargo audit\` locally to see the full report" >> $GITHUB_STEP_SUMMARY + echo "2. Update affected dependencies using \`cargo update\`" >> $GITHUB_STEP_SUMMARY + echo "3. Review if these vulnerabilities affect your production deployments" >> $GITHUB_STEP_SUMMARY # Fail the workflow if vulnerabilities were found - name: Check audit results From 7787a8a851f22d8c2db4ba687f9612bfa75afb62 Mon Sep 17 00:00:00 2001 From: jadamcrain Date: Mon, 27 Jul 2026 09:27:53 -0700 Subject: [PATCH 2/2] Update GitHub Actions to current major versions actions/checkout v6 -> v7, actions/upload-artifact v6 -> v7, and actions/download-artifact v7 -> v8. The upload and download artifact actions are bumped together because their major versions are a matched pair; v8 of the download action supports the direct (unzipped) uploads introduced in v7 of the upload action. Two behavior changes are worth noting. download-artifact v8 now fails the job on a digest mismatch rather than logging a warning, and it inspects the Content-Type header instead of unconditionally unzipping. Neither workflow uses the new archive parameter, so all artifacts remain zipped. checkout v7 blocks checking out fork pull requests under pull_request_target and workflow_run; neither trigger is used here. softprops/action-gh-release and crazy-max/ghaction-import-gpg are already on their latest major versions. --- .github/workflows/ci.yml | 50 ++++++++++++++-------------- .github/workflows/security-audit.yml | 4 +-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 07d571e0..14734027 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust ${{ matrix.rust }} uses: dtolnay/rust-toolchain@master with: @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust ${{ matrix.rust }} uses: dtolnay/rust-toolchain@stable - name: Build the workspace with the features @@ -57,7 +57,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust ${{ matrix.rust }} uses: dtolnay/rust-toolchain@master with: @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install doxygen 1.9.5 @@ -94,7 +94,7 @@ jobs: cp -a ffi/bindings/java/rodbus/target/apidocs ~/doc/java rm ffi/bindings/c/generated/logo.png ffi/bindings/c/generated/doxygen-awesome.css - name: Upload documentation - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: doc-api path: ~/doc @@ -111,7 +111,7 @@ jobs: test: false steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Create the FFI modules dir @@ -127,7 +127,7 @@ jobs: Copy-Item -Path ./target/${{ matrix.target }}/release/rodbus_ffi.dll.lib -Destination ffi-modules/${{ matrix.target }} Copy-Item -Path ./target/${{ matrix.target }}/release/rodbus_ffi_java.dll -Destination ffi-modules/${{ matrix.target }} - name: Upload compiled FFI modules - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ffi-modules-${{ matrix.target }} path: ffi-modules @@ -153,7 +153,7 @@ jobs: runs-on: ${{ matrix.runner }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Create FFI modules dir @@ -167,7 +167,7 @@ jobs: cp ./target/release/librodbus_ffi.dylib ./ffi-modules/${{ matrix.target }} cp ./target/release/librodbus_ffi_java.dylib ./ffi-modules/${{ matrix.target }} - name: Upload compiled FFI modules - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ffi-modules-${{ matrix.target }} path: ffi-modules @@ -205,7 +205,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Install Rust Cross @@ -221,7 +221,7 @@ jobs: cp ./target/${{ matrix.cases.target }}/release/librodbus_ffi.so ./ffi-modules/${{ matrix.cases.target }} cp ./target/${{ matrix.cases.target }}/release/librodbus_ffi_java.so ./ffi-modules/${{ matrix.cases.target }} - name: Upload compiled FFI modules - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: ffi-modules-${{ matrix.cases.target }} path: ffi-modules @@ -229,7 +229,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install dependencies working-directory: guide run: npm ci @@ -240,7 +240,7 @@ jobs: mkdir -p ~/doc/guide mv build/* ~/doc/guide - name: Upload guide - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: doc-guide path: ~/doc @@ -256,9 +256,9 @@ jobs: - name: Install custom allow-list tool run: cargo install --git https://github.com/stepfunc/bom-tools.git - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Download compiled FFI - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: ffi-modules-* path: ffi-modules @@ -283,22 +283,22 @@ jobs: - name: Package Java bindings run: cargo run --bin rodbus-bindings -- --java --package ./ffi-modules --options ./packaging.json -f third-party-licenses-java.txt - name: Upload C/C++ bindings - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: c-bindings path: ffi/bindings/c/generated/* - name: Upload .NET bindings - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: dotnet-bindings path: ffi/bindings/dotnet/nupkg/* - name: Upload Java bindings - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: java-bindings-jar path: ffi/bindings/java/rodbus/target/*.jar - name: Upload Java pom.xml - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: java-bindings-pom path: ffi/bindings/java/rodbus/pom.xml @@ -318,12 +318,12 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download documentation artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: path: artifacts pattern: doc-* - name: Checkout stepfunc/docs - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: stepfunc/docs ssh-key: ${{ secrets.SFIO_DOCS_SSH_KEY }} @@ -355,7 +355,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download Java artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: path: artifacts pattern: java-bindings-* @@ -447,7 +447,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download .NET artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: dotnet-bindings path: artifacts/dotnet-bindings @@ -466,7 +466,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Publish to crates.io @@ -492,7 +492,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: - name: Download all release artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: path: artifacts - name: Package C Bindings diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 428d2f9b..2ec83827 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@e00d2c94cc0067b77737821097a62d91c0301baa # v1.21.1 @@ -79,7 +79,7 @@ jobs: - name: Upload audit report if: always() && hashFiles('audit.json') != '' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: audit-report path: |