Skip to content
Merged
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
76 changes: 75 additions & 1 deletion .github/workflows/action-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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 |

---
Expand Down
27 changes: 23 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }}"

Expand All @@ -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

Expand Down Expand Up @@ -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
5 changes: 5 additions & 0 deletions src/ux/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:"));
}

Expand All @@ -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]
Expand All @@ -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"));
}
}
Loading