Skip to content

feat: add OpenAnt security scanning pipeline #1

feat: add OpenAnt security scanning pipeline

feat: add OpenAnt security scanning pipeline #1

Workflow file for this run

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