From 644e741269f22755b129683b22872c3e6ef45f78 Mon Sep 17 00:00:00 2001 From: Ronaldo Martins Date: Fri, 24 Jul 2026 12:25:00 -0300 Subject: [PATCH] feat(action): add configurable no-adapter strictness --- .github/workflows/action-e2e.yml | 76 +++++++++++++++++++++++++++++++- CLAUDE.md | 3 +- README.md | 3 +- action.yml | 27 ++++++++++-- src/ux/ci.rs | 5 +++ 5 files changed, 107 insertions(+), 7 deletions(-) diff --git a/.github/workflows/action-e2e.yml b/.github/workflows/action-e2e.yml index 75be43c..3870173 100644 --- a/.github/workflows/action-e2e.yml +++ b/.github/workflows/action-e2e.yml @@ -248,6 +248,79 @@ jobs: print('PASS: composite action used root config and suppressed excluded metadata findings') " + - name: "Test 9a: Prepare strict-mode fixtures" + shell: bash + run: | + mkdir -p "${{ runner.temp }}/agentshield-no-adapter" + + - name: "Test 9b: strict=false permits no-adapter scans" + id: no-adapter-permissive + uses: ./ + with: + binary-path: ${{ runner.temp }}/agentshield/agentshield + path: ${{ runner.temp }}/agentshield-no-adapter + format: console + upload-sarif: false + strict: false + + - name: "Test 9c: Verify permissive no-adapter result" + shell: bash + run: | + if [ "${{ steps.no-adapter-permissive.outputs.exit-code }}" != "2" ]; then + echo "::error::strict=false should preserve scanner exit code 2, got ${{ steps.no-adapter-permissive.outputs.exit-code }}" + exit 1 + fi + echo "PASS: strict=false made only the no-adapter check non-blocking" + + - name: "Test 9d: strict=true blocks no-adapter scans" + id: no-adapter-strict + continue-on-error: true + uses: ./ + with: + binary-path: ${{ runner.temp }}/agentshield/agentshield + path: ${{ runner.temp }}/agentshield-no-adapter + format: console + upload-sarif: false + strict: true + + - name: "Test 9e: Verify strict no-adapter failure" + shell: bash + run: | + if [ "${{ steps.no-adapter-strict.outcome }}" != "failure" ]; then + echo "::error::strict=true should fail a no-adapter scan, got ${{ steps.no-adapter-strict.outcome }}" + exit 1 + fi + if [ "${{ steps.no-adapter-strict.outputs.exit-code }}" != "2" ]; then + echo "::error::strict=true should preserve scanner exit code 2, got ${{ steps.no-adapter-strict.outputs.exit-code }}" + exit 1 + fi + echo "PASS: strict=true kept no-adapter scans blocking" + + - name: "Test 9f: strict=false still blocks invalid config" + id: invalid-config-permissive + continue-on-error: true + uses: ./ + with: + binary-path: ${{ runner.temp }}/agentshield/agentshield + path: tests/fixtures/mcp_servers/safe_calculator + config: tests/fixtures + format: console + upload-sarif: false + strict: false + + - name: "Test 9g: Verify genuine scan errors remain blocking" + shell: bash + run: | + if [ "${{ steps.invalid-config-permissive.outcome }}" != "failure" ]; then + echo "::error::strict=false must not suppress invalid config errors, got ${{ steps.invalid-config-permissive.outcome }}" + exit 1 + fi + if [ "${{ steps.invalid-config-permissive.outputs.exit-code }}" != "2" ]; then + echo "::error::invalid config should preserve scanner exit code 2, got ${{ steps.invalid-config-permissive.outputs.exit-code }}" + exit 1 + fi + echo "PASS: strict=false did not suppress a genuine scan error" + # --- Upload SARIF for real Code Scanning integration --- - name: Upload SARIF to Code Scanning if: always() && matrix.os == 'ubuntu-latest' @@ -272,4 +345,5 @@ jobs: echo "| 6 | HTML output format | Passed |" >> $GITHUB_STEP_SUMMARY echo "| 7 | list-rules (18 detectors) | Passed |" >> $GITHUB_STEP_SUMMARY echo "| 8 | Subdirectory path filters | Passed |" >> $GITHUB_STEP_SUMMARY - echo "| 9 | SARIF upload to Code Scanning | Attempted |" >> $GITHUB_STEP_SUMMARY + echo "| 9 | Action strict-mode behavior | Passed |" >> $GITHUB_STEP_SUMMARY + echo "| 10 | SARIF upload to Code Scanning | Attempted |" >> $GITHUB_STEP_SUMMARY diff --git a/CLAUDE.md b/CLAUDE.md index cb39450..fb4120e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -208,7 +208,8 @@ The `apply_cross_file_sanitization()` function: The `--ignore-tests` flag skips test files at the file-walking stage (before parsing). Available via: - **CLI:** `agentshield scan . --ignore-tests` - **Config:** `[scan] ignore_tests = true` in `.agentshield.toml` -- **GitHub Action:** `ignore-tests: true` input +- **GitHub Action:** `ignore-tests: true` and `strict: true` (default), where `strict: false` makes only + no-adapter discovery errors non-blocking; other scan errors still fail the check - **Library:** `ScanOptions { ignore_tests: true, .. }` CLI flag overrides config (`options.ignore_tests || config.scan.ignore_tests`). diff --git a/README.md b/README.md index e80b81a..fed555c 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ jobs: path: '.' fail-on: 'high' ignore-tests: true + strict: true # set false only when no supported adapter layout is expected upload-sarif: true ``` @@ -379,7 +380,7 @@ such as `legacy/` as matching that directory's contents. |------|---------| | 0 | Scan passed with no findings above threshold | | 1 | Scan failed with findings above threshold | -| 2 | Scan error, such as invalid config or no supported adapter found | +| 2 | Scan error (e.g., invalid config); no supported adapter found is surfaced as scanner error and can be controlled by action `strict` mode | | 3 | Runtime guard blocked or failed closed on invalid runtime input | --- diff --git a/action.yml b/action.yml index 85ac3fc..c3720dc 100644 --- a/action.yml +++ b/action.yml @@ -35,6 +35,10 @@ inputs: description: 'Skip test files (test/, tests/, __tests__/, *.test.ts, *.spec.ts, etc.)' required: false default: 'false' + strict: + description: 'Fail when no supported adapter is found; false makes only that discovery error non-blocking' + required: false + default: 'true' version: description: 'AgentShield version to use (default: latest). Accepts a version with or without a "v" prefix, e.g. v0.8.0 or 0.8.0.' required: false @@ -147,6 +151,7 @@ runs: id: scan shell: bash run: | + SCAN_LOG="${{ runner.temp }}/agentshield-scan.log" ARGS="scan ${{ inputs.path }}" ARGS="$ARGS --fail-on ${{ inputs.fail-on }}" @@ -171,10 +176,16 @@ runs: fi set +e - agentshield $ARGS - EXIT_CODE=$? + agentshield $ARGS 2>&1 | tee "$SCAN_LOG" + EXIT_CODE=${PIPESTATUS[0]} set -e + if grep -Fq "No suitable adapter found for directory:" "$SCAN_LOG"; then + echo "no_adapter=true" >> $GITHUB_OUTPUT + else + echo "no_adapter=false" >> $GITHUB_OUTPUT + fi + echo "exit-code=$EXIT_CODE" >> $GITHUB_OUTPUT echo "sarif-file=$SARIF_FILE" >> $GITHUB_OUTPUT @@ -204,6 +215,14 @@ runs: echo "::error::AgentShield found findings above the '${{ inputs.fail-on }}' threshold" exit 1 elif [ "$AGENTSHIELD_EXIT" = "2" ]; then - echo "::error::AgentShield encountered a scan error" - exit 2 + if [ "${{ steps.scan.outputs.no_adapter }}" = "true" ] && [ "${{ inputs.strict }}" = "false" ]; then + echo "::warning::AgentShield found no supported adapter for the scanned path; non-blocking due strict=false" + exit 0 + elif [ "${{ steps.scan.outputs.no_adapter }}" = "true" ]; then + echo "::error::AgentShield encountered a scan error: No suitable adapter found" + exit 2 + else + echo "::error::AgentShield encountered a scan error" + exit 2 + fi fi diff --git a/src/ux/ci.rs b/src/ux/ci.rs index dd99ec6..e0cf49a 100644 --- a/src/ux/ci.rs +++ b/src/ux/ci.rs @@ -36,6 +36,7 @@ jobs: fail-on: "{fail_on}" ignore-tests: {ignore_tests} {baseline_input} upload-sarif: {upload_sarif} + strict: true "#, scan_path = options.scan_path, fail_on = options.fail_on, @@ -109,6 +110,7 @@ jobs: fail-on: "{fail_on}" ignore-tests: {ignore_tests} {baseline_input} upload-sarif: {upload_sarif} + strict: true "#, scan_path = options.scan_path, fail_on = options.fail_on, @@ -136,6 +138,7 @@ mod tests { assert!(workflow.contains("fail-on: \"high\"")); assert!(workflow.contains("ignore-tests: true")); assert!(workflow.contains("upload-sarif: true")); + assert!(workflow.contains("strict: true")); assert!(!workflow.contains("baseline:")); } @@ -151,6 +154,7 @@ mod tests { assert!(workflow.contains("baseline: \".agentshield-baseline.json\"")); assert!(workflow.contains("upload-sarif: true")); + assert!(workflow.contains("strict: true")); } #[test] @@ -169,5 +173,6 @@ mod tests { assert!(workflow.contains("semgrep scan --config auto")); assert!(workflow.contains("uses: aiconnai/agentshield@main")); assert!(workflow.contains("baseline: \".agentshield-baseline.json\"")); + assert!(workflow.contains("strict: true")); } }