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 676de7af..2ec83827 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -2,42 +2,89 @@ 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: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - 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@v7 + 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