-
Notifications
You must be signed in to change notification settings - Fork 0
120 lines (107 loc) · 5.13 KB
/
security-review.yml
File metadata and controls
120 lines (107 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
name: Security review gate
# Path-filter gate for the security-sensitive surfaces. Runs on every PR that
# touches auth, crypto, audit, key handling, or tenant-boundary code and
# leaves an annotated comment listing the touched paths + the named subagent
# the human reviewer must invoke locally.
#
# Why this isn't an in-CI security scan: the security-reviewer subagent
# (`.claude/agents/security-reviewer.md`) runs on Opus with full repo context
# and produces a structured findings report. Running that inside GHA would
# require Claude API access from CI, which isn't wired and isn't billed
# centrally. So this workflow is a *forcing function* — it makes skipping
# the manual subagent run impossible to miss in the PR conversation.
#
# Closes the Week 1 discipline gap noted in qa-log/W01-engineering-annex.md.
on:
pull_request:
paths:
- 'src/services/zkp.ts'
- 'src/services/identity.ts'
- 'src/services/api-keys.ts'
- 'src/services/jwt.ts'
- 'src/services/platform.ts'
- 'src/middleware/auth.ts'
- 'src/middleware/tenant-auth.ts'
- 'src/routes/auth.ts'
- 'src/routes/console.ts'
- 'src/routes/v1/**'
- 'src/routes/zkp.ts'
- 'src/routes/saml.ts'
- 'src/routes/oidc.ts'
- 'circuits/**'
- 'contracts/**'
- 'verifier/src/audit-log.ts'
- 'verifier/src/server.ts'
permissions:
contents: read
pull-requests: write
# `issues: write` is required because the script calls
# github.rest.issues.{listComments,createComment,updateComment} —
# those endpoints sit under /repos/{owner}/{repo}/issues/{n}/comments
# even when {n} is a pull-request number, and the `issues` scope is
# the one that gates them. Without this, GitHub returns 404 on the
# listComments call (it conceals access denial as not-found). The
# `pull-requests` scope alone is not sufficient for issue-comment
# CRUD on PR conversations.
issues: write
jobs:
flag:
name: Flag for security-reviewer subagent
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Collect touched security paths
id: paths
run: |
base="${{ github.event.pull_request.base.sha }}"
head="${{ github.event.pull_request.head.sha }}"
# Recompute the same path set the workflow `paths:` clause matches
# so the comment lists the *exact* files that triggered this run.
touched=$(git diff --name-only "$base" "$head" | grep -E '^(src/services/(zkp|identity|api-keys|jwt|platform)\.ts|src/middleware/(auth|tenant-auth)\.ts|src/routes/(auth|console|zkp|saml|oidc)\.ts|src/routes/v1/.+|circuits/.+|contracts/.+|verifier/src/(audit-log|server)\.ts)$' || true)
# Newline-escape for GHA multi-line output
{
echo "touched<<EOF"
echo "$touched"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Annotate PR with subagent invocation reminder
uses: actions/github-script@v8
with:
script: |
const touched = `${{ steps.paths.outputs.touched }}`.trim();
if (!touched) {
core.info('No security-sensitive paths touched; nothing to flag.');
return;
}
const list = touched.split('\n').map(p => `- \`${p}\``).join('\n');
const marker = '<!-- security-review-gate -->';
const body = `${marker}
## 🔒 Security review required
This PR touches security-sensitive surfaces. Per [CLAUDE.md §4](../blob/main/CLAUDE.md#standing-instructions), the \`security-reviewer\` subagent ([\`.claude/agents/security-reviewer.md\`](../blob/main/.claude/agents/security-reviewer.md)) must be invoked locally before merge.
**Touched paths:**
${list}
**How to run the review:**
\`\`\`
# In Claude Code, after pulling this branch:
@security-reviewer review the changes on this branch
\`\`\`
Reply on this PR with the structured findings report (or a "no findings" confirmation) before requesting merge. Block merge if any Critical / High finding lands without a tracked carve-out.
_This comment is posted automatically by \`.github/workflows/security-review.yml\` and updated on every push to keep the touched-paths list current._`;
const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;
const existing = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number: prNumber }
);
const prior = existing.find(c => c.body && c.body.startsWith(marker));
if (prior) {
await github.rest.issues.updateComment({ owner, repo, comment_id: prior.id, body });
core.info(`Updated existing security-review comment ${prior.id}`);
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
core.info('Posted new security-review comment');
}