name: PR Security Scan on: pull_request_target: types: [opened, reopened, synchronize, ready_for_review] permissions: contents: read pull-requests: write issues: write jobs: scan: name: Scan PR runs-on: ubuntu-24.04 timeout-minutes: 10 concurrency: group: brin-pr-${{ github.event.pull_request.number }} cancel-in-progress: true steps: - name: Scan PR with Brin id: scan shell: bash env: REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -euo pipefail RESPONSE=$(curl -sfL --max-time 300 \ "https://api.brin.sh/pr/${REPO}/${PR_NUMBER}?details=true&mode=full&tolerance=conservative" \ || echo '{}') SCORE=$(jq -r '.score // empty' <<<"$RESPONSE") VERDICT=$(jq -r '.verdict // "unknown"' <<<"$RESPONSE") PENDING=$(jq -r '.pending_deep_scan // false' <<<"$RESPONSE") if [ -z "$SCORE" ] || [ "$PENDING" = "true" ]; then echo "status=inconclusive" >> "$GITHUB_OUTPUT" echo "should_fail=false" >> "$GITHUB_OUTPUT" exit 0 fi echo "score=${SCORE}" >> "$GITHUB_OUTPUT" echo "verdict=${VERDICT}" >> "$GITHUB_OUTPUT" if [ "$VERDICT" = "dangerous" ] || [ "$SCORE" -lt 30 ]; then echo "status=blocking" >> "$GITHUB_OUTPUT" echo "should_fail=true" >> "$GITHUB_OUTPUT" elif [ "$VERDICT" = "suspicious" ]; then echo "status=review" >> "$GITHUB_OUTPUT" echo "should_fail=false" >> "$GITHUB_OUTPUT" else echo "status=clean" >> "$GITHUB_OUTPUT" echo "should_fail=false" >> "$GITHUB_OUTPUT" fi THREATS=$(jq -r '(.threats // [])[] | "- \(.type): \(.detail)"' <<<"$RESPONSE") { echo "threats<> "$GITHUB_OUTPUT" - name: Comment on flagged PR if: steps.scan.outputs.status == 'blocking' || steps.scan.outputs.status == 'review' uses: actions/github-script@v7 env: SCORE: ${{ steps.scan.outputs.score }} VERDICT: ${{ steps.scan.outputs.verdict }} STATUS: ${{ steps.scan.outputs.status }} THREATS: ${{ steps.scan.outputs.threats }} with: script: | const marker = ""; const { owner, repo } = context.repo; const issue_number = context.payload.pull_request.number; const headline = process.env.STATUS === "blocking" ? "This PR has findings that should block merge." : "This PR has findings that should be reviewed."; let body = `${marker}\n### Brin PR Security Scan\n\n`; body += `${headline}\n\n`; body += `- **Score:** ${process.env.SCORE}/100\n`; body += `- **Verdict:** ${process.env.VERDICT}\n\n`; if (process.env.THREATS) { body += `**Findings:**\n${process.env.THREATS}\n\n`; } body += `Analyzed by [Brin](https://brin.sh)`; const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number, per_page: 100, }); const existing = comments.find((c) => c.body?.includes(marker)); if (existing) { await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); } else { await github.rest.issues.createComment({ owner, repo, issue_number, body }); } - name: Delete old comment when clean if: steps.scan.outputs.status == 'clean' uses: actions/github-script@v7 with: script: | const marker = ""; const { owner, repo } = context.repo; const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: context.payload.pull_request.number, per_page: 100, }); const existing = comments.find((c) => c.body?.includes(marker)); if (existing) { await github.rest.issues.deleteComment({ owner, repo, comment_id: existing.id }); } - name: Fail if blocking if: steps.scan.outputs.should_fail == 'true' run: | echo "::error::Brin flagged this PR as dangerous or scoring below 30" exit 1