feat: add OpenAnt security scanning pipeline #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: OpenAnt Security Scan | |
| # Runs on every pull request and on pushes to main/master. | |
| # Fetches OpenAnt from santandersecurityresearch, builds it, scans this | |
| # repository, posts a summary comment on PRs, uploads SARIF to the | |
| # Security tab, and fails the check if confirmed vulnerabilities are found. | |
| on: | |
| pull_request: | |
| branches: [main, master] | |
| push: | |
| branches: [main, master] | |
| permissions: | |
| contents: read | |
| pull-requests: write # post PR comment | |
| security-events: write # upload SARIF | |
| jobs: | |
| scan: | |
| name: Vulnerability scan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| # ------------------------------------------------------------------ Checkout target repo | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| # ------------------------------------------------------------------ Fetch OpenAnt | |
| - name: Checkout OpenAnt | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| repository: santandersecurityresearch/OpenAnt | |
| ref: feat/github-actions-pipeline | |
| path: _openant | |
| # ------------------------------------------------------------------ Go | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: _openant/apps/openant-cli/go.mod | |
| cache-dependency-path: _openant/apps/openant-cli/go.sum | |
| # ------------------------------------------------------------------ Python | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| cache: pip | |
| cache-dependency-path: _openant/libs/openant-core/requirements.txt | |
| - name: Install Python dependencies | |
| run: pip install --quiet -r _openant/libs/openant-core/requirements.txt | |
| # ------------------------------------------------------------------ Build openant | |
| - name: Build openant CLI | |
| run: | | |
| cd _openant/apps/openant-cli | |
| go build -o bin/openant . | |
| echo "$(pwd)/bin" >> "$GITHUB_PATH" | |
| - name: Verify openant | |
| run: openant version | |
| # ------------------------------------------------------------------ Scan | |
| - name: Run security scan | |
| id: scan | |
| env: | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| mkdir -p /tmp/openant-results | |
| # Point openant at the checked-out source (GITHUB_WORKSPACE is the | |
| # vulpy root; _openant is a subdirectory we exclude via --language). | |
| openant scan "$GITHUB_WORKSPACE" \ | |
| --language python \ | |
| --output /tmp/openant-results \ | |
| --level reachable \ | |
| --verify \ | |
| --enhance-mode single-shot \ | |
| --model sonnet \ | |
| --limit 50 \ | |
| --no-report \ | |
| --workers 8 \ | |
| --json \ | |
| 2>/tmp/openant-stderr.log \ | |
| | tee /tmp/openant-stdout.json || true | |
| # ------------------------------------------------------------------ SARIF | |
| - name: Convert findings to SARIF | |
| if: always() | |
| run: | | |
| PIPELINE_OUTPUT="/tmp/openant-results/pipeline_output.json" | |
| if [ -f "$PIPELINE_OUTPUT" ]; then | |
| python _openant/tools/sarif_convert.py "$PIPELINE_OUTPUT" \ | |
| -o /tmp/openant-results/results.sarif | |
| else | |
| # Minimal valid empty SARIF so the upload step does not fail. | |
| cat > /tmp/openant-results/results.sarif <<'SARIF' | |
| {"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"OpenAnt","version":"1.0.0","rules":[]}},"results":[]}]} | |
| SARIF | |
| fi | |
| - name: Upload SARIF to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@v3 | |
| if: always() | |
| with: | |
| sarif_file: /tmp/openant-results/results.sarif | |
| category: openant | |
| # ------------------------------------------------------------------ PR comment | |
| - name: Post scan summary as PR comment | |
| if: always() && github.event_name == 'pull_request' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| STDOUT="/tmp/openant-stdout.json" | |
| PIPELINE_OUTPUT="/tmp/openant-results/pipeline_output.json" | |
| VULNERABLE=0; BYPASSABLE=0; INCONCLUSIVE=0; TOTAL=0; COST="N/A" | |
| if [ -f "$STDOUT" ]; then | |
| VULNERABLE=$(jq -r '.data.metrics.vulnerable // 0' "$STDOUT" 2>/dev/null || echo 0) | |
| BYPASSABLE=$(jq -r '.data.metrics.bypassable // 0' "$STDOUT" 2>/dev/null || echo 0) | |
| INCONCLUSIVE=$(jq -r '.data.metrics.inconclusive // 0' "$STDOUT" 2>/dev/null || echo 0) | |
| TOTAL=$(jq -r '.data.units_count // 0' "$STDOUT" 2>/dev/null || echo 0) | |
| RAW_COST=$(jq -r '.data.usage.total_cost_usd // 0' "$STDOUT" 2>/dev/null || echo 0) | |
| COST=$(printf '$%.2f' "$RAW_COST" 2>/dev/null || echo "N/A") | |
| fi | |
| if [ "$VULNERABLE" -gt 0 ] || [ "$BYPASSABLE" -gt 0 ]; then | |
| BADGE="🔴 **Vulnerabilities confirmed**" | |
| elif [ "$INCONCLUSIVE" -gt 0 ]; then | |
| BADGE="🟡 **Needs manual review**" | |
| else | |
| BADGE="🟢 **No confirmed vulnerabilities**" | |
| fi | |
| FINDING_LINES="" | |
| if [ -f "$PIPELINE_OUTPUT" ]; then | |
| FINDING_LINES=$(jq -r ' | |
| .findings[:10][] | |
| | "| `\(.location.file)` | \(.location.function) | \(.stage1_verdict | ascii_upcase) | \(.name) |" | |
| ' "$PIPELINE_OUTPUT" 2>/dev/null || echo "") | |
| fi | |
| FINDING_TABLE="" | |
| if [ -n "$FINDING_LINES" ]; then | |
| FINDING_TABLE="### Findings | |
| | File | Function | Verdict | Type | | |
| |------|----------|---------|------| | |
| ${FINDING_LINES}" | |
| fi | |
| cat > /tmp/pr-comment.md <<EOF | |
| ## OpenAnt Security Scan | |
| ${BADGE} | |
| | Metric | Value | | |
| |--------|-------| | |
| | Units scanned | ${TOTAL} | | |
| | Confirmed vulnerable | ${VULNERABLE} | | |
| | Bypassable controls | ${BYPASSABLE} | | |
| | Inconclusive | ${INCONCLUSIVE} | | |
| | LLM cost | ${COST} | | |
| ${FINDING_TABLE} | |
| > Stage 1 detection + Stage 2 attacker simulation via [OpenAnt](https://github.com/santandersecurityresearch/OpenAnt). | |
| > See the **Security > Code scanning** tab for annotated findings. | |
| <sub>commit \`${{ github.sha }}\`</sub> | |
| EOF | |
| gh pr comment "${{ github.event.pull_request.number }}" \ | |
| --repo "${{ github.repository }}" \ | |
| --body-file /tmp/pr-comment.md \ | |
| --edit-last \ | |
| || \ | |
| gh pr comment "${{ github.event.pull_request.number }}" \ | |
| --repo "${{ github.repository }}" \ | |
| --body-file /tmp/pr-comment.md | |
| # ------------------------------------------------------------------ Artifacts | |
| - name: Upload scan artifacts | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: openant-results-${{ github.sha }} | |
| path: | | |
| /tmp/openant-results/pipeline_output.json | |
| /tmp/openant-results/results_verified.json | |
| /tmp/openant-results/results.sarif | |
| /tmp/openant-stdout.json | |
| /tmp/openant-stderr.log | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| # ------------------------------------------------------------------ Fail gate | |
| - name: Fail if confirmed vulnerabilities found | |
| run: | | |
| PIPELINE_OUTPUT="/tmp/openant-results/pipeline_output.json" | |
| if [ ! -f "$PIPELINE_OUTPUT" ]; then | |
| echo "::warning::pipeline_output.json not found — scan may have errored. Check artifacts for stderr log." | |
| exit 0 | |
| fi | |
| VULNERABLE=$(jq '[.findings[] | select(.stage1_verdict == "vulnerable")] | length' \ | |
| "$PIPELINE_OUTPUT" 2>/dev/null || echo 0) | |
| BYPASSABLE=$(jq '[.findings[] | select(.stage1_verdict == "bypassable")] | length' \ | |
| "$PIPELINE_OUTPUT" 2>/dev/null || echo 0) | |
| echo "Confirmed vulnerable : ${VULNERABLE}" | |
| echo "Bypassable controls : ${BYPASSABLE}" | |
| if [ "$VULNERABLE" -gt 0 ]; then | |
| echo "::error::${VULNERABLE} confirmed $([ "$VULNERABLE" -eq 1 ] && echo vulnerability || echo vulnerabilities) found. Review the Security tab or PR comment." | |
| exit 1 | |
| fi | |
| if [ "$BYPASSABLE" -gt 0 ]; then | |
| echo "::warning::${BYPASSABLE} finding$([ "$BYPASSABLE" -eq 1 ] && echo '' || echo s) with bypassable controls — review before merging." | |
| fi |