forgedock 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +244 -0
  2. package/README.md +251 -0
  3. package/bin/forgedock.mjs +232 -0
  4. package/commands/analytics.md +777 -0
  5. package/commands/audit-agents.md +410 -0
  6. package/commands/audit.md +410 -0
  7. package/commands/autopilot.md +432 -0
  8. package/commands/cleanup.md +264 -0
  9. package/commands/deploy-info.md +292 -0
  10. package/commands/failure-recon.md +375 -0
  11. package/commands/forge-stats.md +183 -0
  12. package/commands/geo-audit.md +343 -0
  13. package/commands/incident-response.md +384 -0
  14. package/commands/issue.md +506 -0
  15. package/commands/milestone.md +599 -0
  16. package/commands/orchestrate.md +1044 -0
  17. package/commands/pipeline-health.md +2343 -0
  18. package/commands/qa-sweep.md +237 -0
  19. package/commands/quality-gate.md +777 -0
  20. package/commands/review-pr-agents.md +1711 -0
  21. package/commands/review-pr-staging.md +325 -0
  22. package/commands/review-pr.md +888 -0
  23. package/commands/rollback.md +253 -0
  24. package/commands/security-audit.md +529 -0
  25. package/commands/sync-ecosystem.md +527 -0
  26. package/commands/validate.md +131 -0
  27. package/commands/work-on/build/architect.md +291 -0
  28. package/commands/work-on/build/context.md +349 -0
  29. package/commands/work-on/build/implement.md +280 -0
  30. package/commands/work-on/build/validate.md +292 -0
  31. package/commands/work-on/build.md +262 -0
  32. package/commands/work-on/close.md +350 -0
  33. package/commands/work-on/decompose.md +254 -0
  34. package/commands/work-on/investigate.md +437 -0
  35. package/commands/work-on/review.md +245 -0
  36. package/commands/work-on-monolithic.md +298 -0
  37. package/commands/work-on.md +1089 -0
  38. package/docs/CODEX.md +92 -0
  39. package/docs/WORKFLOW.md +104 -0
  40. package/package.json +45 -0
  41. package/scripts/verify-env-vars.sh +201 -0
  42. package/scripts/verify-host-headers.sh +117 -0
  43. package/scripts/verify-route-registration.sh +118 -0
  44. package/scripts/verify-sops-chain.sh +163 -0
@@ -0,0 +1,529 @@
1
+ ---
2
+ description: Periodic security posture audit — runs a scripted 4-phase checklist against repo files (not diffs), creates GitHub issues for confirmed findings
3
+ argument-hint: [--repo alterlab|mcp|n8n] [--phase 1|2|3|4|all] [--dry-run]
4
+ ---
5
+
6
+ # /security-audit — Periodic Security Posture Audit
7
+
8
+ **Input**: $ARGUMENTS
9
+
10
+ You are the security posture auditor. Run a scripted 4-phase checklist against the **current state** of the target repository (not a PR diff). Each check uses bash/grep against repo files. Confirmed findings are posted as GitHub issues in the target repo, labeled `security,audit-finding,P1/P2/P3`.
11
+
12
+ This command is designed to be run:
13
+ - **Manually** after a pen test engagement
14
+ - **Quarterly** as a scheduled posture review
15
+ - **After major infrastructure changes** (new services, docker-compose rewrites, auth changes)
16
+
17
+ This is NOT a PR review — it does not approve or block. It creates issues for each gap found.
18
+
19
+ **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
20
+ **NEVER use plan mode (EnterPlanMode).**
21
+
22
+ ---
23
+
24
+ ## Multi-Repo Support
25
+
26
+ | Input Pattern | Target Repo | GH_REPO | REPO_PATH |
27
+ |---------------|-------------|---------|-----------|
28
+ | `--repo alterlab` or default | AlterLab | `RapierCraft/AlterLab` | `/home/user/projects/alterlab` |
29
+ | `--repo mcp` | MCP Server | `RapierCraft/alterlab-mcp-server` | `/home/user/projects/alterlab-mcp-server` |
30
+ | `--repo n8n` | n8n Node | `RapierCraft/n8n-nodes-alterlab` | `/home/user/projects/n8n-nodes-alterlab` |
31
+
32
+ Parse `$ARGUMENTS` for:
33
+ - **`--repo <name>`**: target repo (default: `alterlab`)
34
+ - **`--phase <N|all>`**: run only a specific phase, or all (default: `all`)
35
+ - **`--dry-run`**: print findings but do NOT create GitHub issues
36
+
37
+ Set context variables from the table above. If `REPO_PATH` is not found locally, clone from GitHub:
38
+ ```bash
39
+ gh repo clone {GH_REPO} {REPO_PATH} -- --depth=1 2>/dev/null || true
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Severity Classification
45
+
46
+ | Severity | Condition |
47
+ |----------|-----------|
48
+ | **P1** | Direct exploitation path with no mitigation; publicly reachable; auth bypass |
49
+ | **P2** | Exploitable under specific conditions; security control absent but other mitigations present |
50
+ | **P3** | Defense-in-depth gap; best-practice deviation; no active exploitability shown |
51
+
52
+ ---
53
+
54
+ ## Phase 1: Infrastructure Posture
55
+
56
+ ### 1A: Docker Compose Port Bindings
57
+
58
+ **What**: Non-web services bound to `0.0.0.0` expose internal ports to all network interfaces, including public ones when deployed without an external firewall.
59
+
60
+ **Check**:
61
+ ```bash
62
+ DOCKER_COMPOSE_FILES=$(find "$REPO_PATH" -name "docker-compose*.yml" -not -path "*/.git/*" 2>/dev/null)
63
+ for f in $DOCKER_COMPOSE_FILES; do
64
+ HITS=$(grep -n '0\.0\.0\.0:[0-9]' "$f" 2>/dev/null | grep -v '^\s*#' || true)
65
+ if [ -n "$HITS" ]; then
66
+ echo "FINDING [1A] $f:"
67
+ echo "$HITS"
68
+ fi
69
+ done
70
+ ```
71
+
72
+ **Confirm**: For each hit, verify the port is NOT a web-facing port (80, 443, 8080, 3000). Ports like 6432 (PgBouncer), 6379 (Redis), 5432 (Postgres), 27017 (MongoDB) bound to `0.0.0.0` are confirmed findings.
73
+
74
+ **Severity**: P1 if no external firewall documented; P2 otherwise.
75
+
76
+ **Issue title**: `security: {SERVICE} port {PORT} bound to 0.0.0.0 — exposed beyond localhost`
77
+
78
+ ---
79
+
80
+ ### 1B: Dockerfile USER Directive
81
+
82
+ **What**: Containers running as root violate least-privilege. A compromised container running as root has full host access if namespace isolation is broken.
83
+
84
+ **Check**:
85
+ ```bash
86
+ DOCKERFILES=$(find "$REPO_PATH" -name "Dockerfile*" -not -path "*/.git/*" 2>/dev/null)
87
+ for f in $DOCKERFILES; do
88
+ # Skip if USER directive is present (anywhere in the file, case-insensitive)
89
+ if ! grep -iq '^\s*USER\s' "$f" 2>/dev/null; then
90
+ echo "FINDING [1B] $f: no USER directive — container runs as root"
91
+ fi
92
+ done
93
+ ```
94
+
95
+ **Confirm**: Check if the Dockerfile is for a long-running service (not a one-shot build image). If service container with no USER → confirmed finding.
96
+
97
+ **Severity**: P2 (standard finding; exploitability depends on runtime isolation).
98
+
99
+ **Issue title**: `security: {SERVICE} Dockerfile has no USER directive — runs as root`
100
+
101
+ ---
102
+
103
+ ### 1C: Dev Service Authentication
104
+
105
+ **What**: Dev databases or caches with no password are fine locally but become a critical gap if port bindings change or the compose file is used in non-dev environments.
106
+
107
+ **Check**:
108
+ ```bash
109
+ DOCKER_COMPOSE_FILES=$(find "$REPO_PATH" -name "docker-compose*.yml" -not -path "*/.git/*" 2>/dev/null)
110
+ for f in $DOCKER_COMPOSE_FILES; do
111
+ # Redis without requirepass
112
+ REDIS_NO_AUTH=$(grep -A5 'image:.*redis' "$f" 2>/dev/null | grep -v 'requirepass\|password' | grep -c 'redis' || true)
113
+ # Postgres without POSTGRES_PASSWORD
114
+ PG_NO_AUTH=$(awk '/image:.*postgres/{found=1} found && /POSTGRES_PASSWORD/{found=0} found && /^[[:space:]]*-[[:space:]]*[A-Z]/{print}' "$f" 2>/dev/null | grep -v 'POSTGRES_PASSWORD' | head -3 || true)
115
+
116
+ [ -n "$PG_NO_AUTH" ] && echo "FINDING [1C] $f: Postgres service may lack POSTGRES_PASSWORD"
117
+ done
118
+ ```
119
+
120
+ **Manual review**: Check for `POSTGRES_PASSWORD` and `requirepass` in compose environment sections. Flag any service using a blank/trivial password as a documented value.
121
+
122
+ **Severity**: P3 for dev compose; P1 if the compose file is used in production.
123
+
124
+ **Issue title**: `security: {SERVICE} in {FILE} has no authentication configured`
125
+
126
+ ---
127
+
128
+ ## Phase 2: CI/CD Security Gating
129
+
130
+ ### 2A: Security Scanner continue-on-error
131
+
132
+ **What**: Security scanners (Trivy, Snyk, OWASP dependency check) with `continue-on-error: true` silently pass even when vulnerabilities are detected. The job green-lights the pipeline while the scanner's findings are ignored.
133
+
134
+ **Check**:
135
+ ```bash
136
+ WORKFLOW_FILES=$(find "$REPO_PATH/.github/workflows" -name "*.yml" 2>/dev/null)
137
+ for f in $WORKFLOW_FILES; do
138
+ # Find security scanner steps then check if continue-on-error follows within 5 lines
139
+ awk '
140
+ /trivy|snyk|owasp|dependency.check|gitleaks|semgrep|gosec|bandit/ {
141
+ scanner_line = NR
142
+ scanner_name = $0
143
+ }
144
+ /continue-on-error:\s*true/ && NR - scanner_line <= 5 && scanner_line > 0 {
145
+ print FILENAME ":" NR ": continue-on-error: true near security scanner at line " scanner_line
146
+ }
147
+ ' FILENAME="$f" "$f" 2>/dev/null || true
148
+ done
149
+ ```
150
+
151
+ **Confirm**: Verify the flagged step is indeed a security scanner step (not a notification or deploy step). Confirmed if scanner produces failure exit codes that are being swallowed.
152
+
153
+ **Severity**: P2 (scanner present but findings are silently ignored).
154
+
155
+ **Issue title**: `security: {SCANNER} step in {WORKFLOW} has continue-on-error: true — findings silently ignored`
156
+
157
+ ---
158
+
159
+ ### 2B: Commented-Out Access Controls
160
+
161
+ **What**: Access control rules (nginx `deny`, `allow`, IP ACLs, auth guards) that are commented out may have been disabled for debugging and never re-enabled.
162
+
163
+ **Check**:
164
+ ```bash
165
+ CONFIG_FILES=$(find "$REPO_PATH/infra" "$REPO_PATH/services" -name "*.conf" -o -name "*.yml" -o -name "nginx.conf" 2>/dev/null | grep -v '.git')
166
+ for f in $CONFIG_FILES; do
167
+ HITS=$(grep -n '^\s*#.*\(deny\|allow\s\|auth_basic\|auth_request\|satisfy all\|limit_req\|access_by_lua\)' "$f" 2>/dev/null || true)
168
+ [ -n "$HITS" ] && echo "FINDING [2B] $f:" && echo "$HITS"
169
+ done
170
+ ```
171
+
172
+ **Confirm**: Distinguish intentional comments (documentation) from disabled access control rules. Confirmed if the commented line is a functional directive that would restrict access.
173
+
174
+ **Severity**: P1 if the endpoint is publicly accessible; P2 otherwise.
175
+
176
+ **Issue title**: `security: commented-out access control in {FILE} — {DIRECTIVE} disabled`
177
+
178
+ ---
179
+
180
+ ## Phase 3: Application Security Posture
181
+
182
+ ### 3A: CSP unsafe-inline in script-src
183
+
184
+ **What**: `unsafe-inline` in Content-Security-Policy `script-src` allows arbitrary inline scripts, negating XSS protection entirely.
185
+
186
+ **Check**:
187
+ ```bash
188
+ CSP_FILES=$(grep -rl "Content-Security-Policy\|script-src" "$REPO_PATH" --include="*.js" --include="*.ts" --include="*.json" --include="*.conf" --include="*.yml" 2>/dev/null | grep -v '.git' | grep -v node_modules)
189
+ for f in $CSP_FILES; do
190
+ HITS=$(grep -n "script-src.*unsafe-inline\|unsafe-inline.*script-src" "$f" 2>/dev/null || true)
191
+ [ -n "$HITS" ] && echo "FINDING [3A] $f:" && echo "$HITS"
192
+ done
193
+ ```
194
+
195
+ **Confirm**: Verify the CSP header applies to the production site (not just dev/storybook). Check if a nonce-based approach is already in use (nonce + unsafe-inline is still a finding but lower severity).
196
+
197
+ **Severity**: P2 (XSS protection materially weakened).
198
+
199
+ **Issue title**: `security: CSP script-src includes unsafe-inline in {FILE} — XSS protection bypassed`
200
+
201
+ ---
202
+
203
+ ### 3B: Response Header Disclosure
204
+
205
+ **What**: Headers leaking git SHA, deploy color, internal service versions, or infrastructure details help attackers target known CVEs.
206
+
207
+ **Check**:
208
+ ```bash
209
+ HEADER_FILES=$(grep -rl "X-App-Version\|X-Deployment-Color\|X-Git-Sha\|X-Build-Id\|Server:" "$REPO_PATH" --include="*.py" --include="*.ts" --include="*.js" --include="*.conf" 2>/dev/null | grep -v '.git' | grep -v node_modules)
210
+ for f in $HEADER_FILES; do
211
+ HITS=$(grep -n '"X-App-Version"\|"X-Deployment-Color"\|"X-Git-Sha"\|"X-Build-Id"\|"Server:' "$f" 2>/dev/null || true)
212
+ [ -n "$HITS" ] && echo "FINDING [3B] $f:" && echo "$HITS"
213
+ done
214
+ ```
215
+
216
+ **Confirm**: Check if the header is set in a response (not just read). If set in outbound responses → confirmed finding. Internal-only APIs (not publicly routed) are P3; public APIs are P2.
217
+
218
+ **Severity**: P2 for public APIs; P3 for internal.
219
+
220
+ **Issue title**: `security: {HEADER} response header leaks deployment/version information`
221
+
222
+ ---
223
+
224
+ ### 3C: Unauthenticated Endpoints Returning System State
225
+
226
+ **What**: Endpoints that return system health, metrics, version, or configuration data without authentication help attackers enumerate infrastructure.
227
+
228
+ **Check**:
229
+ ```bash
230
+ # Look for unprotected health/metrics/info routes (Python FastAPI/Flask pattern)
231
+ grep -rn '@router.get\("/health"\|@app.route("/health"\|@router.get\("/metrics"\|@router.get\("/info"\|@router.get\("/version"' \
232
+ "$REPO_PATH" --include="*.py" 2>/dev/null | grep -v '.git' | \
233
+ while IFS= read -r line; do
234
+ # Check if there's a dependency/auth check within 10 lines
235
+ FILE=$(echo "$line" | cut -d: -f1)
236
+ LINENO=$(echo "$line" | cut -d: -f2)
237
+ CONTEXT=$(sed -n "$((LINENO)),$((LINENO+10))p" "$FILE" 2>/dev/null)
238
+ if ! echo "$CONTEXT" | grep -q "Depends\|require_auth\|get_current_user\|verify_token\|HTTPBearer"; then
239
+ echo "FINDING [3C] $FILE:$LINENO: health/metrics route may lack auth"
240
+ fi
241
+ done
242
+ ```
243
+
244
+ **Confirm**: Manually verify the route has no authentication dependency. Check if it's behind a network-level gate (traefik middleware, nginx ACL). Confirmed if publicly routable and returns system state.
245
+
246
+ **Severity**: P2 (information disclosure enables targeted attacks).
247
+
248
+ **Issue title**: `security: unauthenticated {ROUTE} endpoint exposes system state`
249
+
250
+ ---
251
+
252
+ ### 3D: In-Memory Rate Limiters
253
+
254
+ **What**: Rate limiters backed by in-process memory (`dict`, `new Map()`, `{}`) are per-replica and reset on restart. In multi-replica deployments they provide no protection — each replica has its own independent counter.
255
+
256
+ **Check**:
257
+ ```bash
258
+ # Python: dict/defaultdict used as rate limit store (not Redis/database-backed)
259
+ grep -rn "rate_limit\|ratelimit\|rate.limit" "$REPO_PATH" --include="*.py" 2>/dev/null | \
260
+ grep -v '.git' | grep -v '#' | \
261
+ while IFS= read -r line; do
262
+ FILE=$(echo "$line" | cut -d: -f1)
263
+ LINENO=$(echo "$line" | cut -d: -f2)
264
+ # Check surrounding context for in-memory stores
265
+ CONTEXT=$(sed -n "$((LINENO-5)),$((LINENO+5))p" "$FILE" 2>/dev/null)
266
+ if echo "$CONTEXT" | grep -qE '=\s*\{\}|=\s*dict\(\)|=\s*defaultdict|new Map\(\)'; then
267
+ echo "FINDING [3D] $FILE:$LINENO: in-memory rate limiter (not Redis-backed)"
268
+ fi
269
+ done
270
+
271
+ # TypeScript/JavaScript: new Map() used as rate limit store
272
+ grep -rn "new Map\(\)" "$REPO_PATH/web" --include="*.ts" --include="*.tsx" --include="*.js" 2>/dev/null | \
273
+ grep -i "rate\|limit\|throttle" | grep -v '.git' | grep -v node_modules | \
274
+ while IFS= read -r line; do
275
+ echo "FINDING [3D] $line: potential in-memory rate limiter"
276
+ done
277
+ ```
278
+
279
+ **Confirm**: Verify the rate limiter is not backed by Redis, a database, or a distributed cache. Confirmed if per-process store with no TTL-based external backend.
280
+
281
+ **Severity**: P2 (rate limit bypass possible in scaled deployments).
282
+
283
+ **Issue title**: `security: in-memory rate limiter in {FILE} has no cross-replica enforcement`
284
+
285
+ ---
286
+
287
+ ### 3E: Cookie Security Flags
288
+
289
+ **What**: Cookies storing user-controlled data or session tokens without `httpOnly`, `Secure`, or `SameSite` flags are vulnerable to XSS theft, MITM interception, and CSRF.
290
+
291
+ **Check**:
292
+ ```bash
293
+ # Python: check set-cookie calls / cookie creation without security flags
294
+ grep -rn "set_cookie\|response.set_cookie\|cookies\[" "$REPO_PATH" --include="*.py" 2>/dev/null | \
295
+ grep -v '.git' | grep -v '#' | \
296
+ while IFS= read -r line; do
297
+ FILE=$(echo "$line" | cut -d: -f1)
298
+ LINENO=$(echo "$line" | cut -d: -f2)
299
+ CONTEXT=$(sed -n "${LINENO}p" "$FILE" 2>/dev/null)
300
+ if ! echo "$CONTEXT" | grep -q "httponly=True\|http_only=True\|secure=True\|samesite"; then
301
+ echo "FINDING [3E] $FILE:$LINENO: cookie set without httpOnly/secure/samesite flags"
302
+ fi
303
+ done
304
+
305
+ # TypeScript: check document.cookie or res.cookie calls
306
+ grep -rn "document\.cookie\s*=\|res\.cookie(" "$REPO_PATH/web" --include="*.ts" --include="*.tsx" 2>/dev/null | \
307
+ grep -v '.git' | grep -v node_modules | \
308
+ while IFS= read -r line; do
309
+ FILE=$(echo "$line" | cut -d: -f1)
310
+ LINENO=$(echo "$line" | cut -d: -f2)
311
+ CONTEXT=$(sed -n "${LINENO}p" "$FILE" 2>/dev/null)
312
+ if ! echo "$CONTEXT" | grep -q "httpOnly\|secure\|sameSite\|SameSite"; then
313
+ echo "FINDING [3E] $FILE:$LINENO: cookie set without security flags"
314
+ fi
315
+ done
316
+ ```
317
+
318
+ **Confirm**: Verify the cookie stores user-controlled data or authentication state. Internal-only cookies (debug flags, feature flags with no sensitive data) are P3. Auth/session cookies without flags are P1.
319
+
320
+ **Severity**: P1 for auth/session tokens; P2 for user-controlled data; P3 for non-sensitive cookies.
321
+
322
+ **Issue title**: `security: {COOKIE_NAME} cookie missing {FLAGS} — vulnerable to XSS/CSRF/MITM`
323
+
324
+ ---
325
+
326
+ ### 3F: Credential Placeholder Fallbacks in Config
327
+
328
+ **What**: Config files with credential fallbacks like `|| "changeme"` or `|| "password"` or `os.getenv("KEY", "secret")` provide working defaults that may silently activate in environments where the secret is absent — including production if the env var injection fails.
329
+
330
+ **Check**:
331
+ ```bash
332
+ # Python: os.getenv with non-empty default for credential-like vars
333
+ grep -rn 'os\.getenv\s*(\s*"[^"]*\(KEY\|SECRET\|PASSWORD\|TOKEN\|PASS\|CRED\|AUTH\)[^"]*"\s*,\s*"[^"]\+"\s*)' \
334
+ "$REPO_PATH" --include="*.py" 2>/dev/null | grep -v '.git' | grep -v '#' | \
335
+ while IFS= read -r line; do
336
+ echo "FINDING [3F] $line: credential env var has non-empty fallback default"
337
+ done
338
+
339
+ # YAML/config: fallback patterns
340
+ grep -rn '||\s*["'"'"'][a-zA-Z0-9]\{4,\}["'"'"']\|default.*password\|default.*secret\|default.*changeme' \
341
+ "$REPO_PATH/infra" "$REPO_PATH/services" --include="*.yml" --include="*.yaml" --include="*.conf" 2>/dev/null | \
342
+ grep -v '.git' | grep -iv '# ' | \
343
+ while IFS= read -r line; do
344
+ echo "FINDING [3F] $line: possible credential fallback in config"
345
+ done
346
+ ```
347
+
348
+ **Confirm**: Verify the fallback value is a real credential (not a placeholder like `""` or `"REPLACE_ME"` which would fail explicitly). Confirmed if a non-trivial value would silently succeed authentication.
349
+
350
+ **Severity**: P1 if the credential protects a production service; P2 otherwise.
351
+
352
+ **Issue title**: `security: credential placeholder fallback in {FILE} — {VAR} defaults to hardcoded value`
353
+
354
+ ---
355
+
356
+ ## Phase 4: Financial Integrity (AlterLab-specific)
357
+
358
+ *Skip this phase for non-AlterLab repos (MCP, n8n). These checks are specific to AlterLab's billing architecture.*
359
+
360
+ ### 4A: Stripe Webhook Handler Coverage
361
+
362
+ **What**: Stripe events that are financially relevant (subscription cancel, payment failure, dispute opened, charge refunded) must be explicitly handled. Unhandled events silently succeed (Stripe expects 200), leaving user account state inconsistent with billing state.
363
+
364
+ **Minimum required events**:
365
+ ```
366
+ customer.subscription.created
367
+ customer.subscription.updated
368
+ customer.subscription.deleted
369
+ invoice.payment_succeeded
370
+ invoice.payment_failed
371
+ customer.subscription.trial_will_end
372
+ charge.refunded
373
+ charge.dispute.created
374
+ payment_intent.payment_failed
375
+ ```
376
+
377
+ **Check**:
378
+ ```bash
379
+ WEBHOOK_FILES=$(grep -rl "stripe\|webhook" "$REPO_PATH/services" --include="*.py" 2>/dev/null | grep -v '.git' | head -10)
380
+ for f in $WEBHOOK_FILES; do
381
+ REQUIRED_EVENTS="customer.subscription.deleted invoice.payment_failed charge.refunded charge.dispute.created"
382
+ for event in $REQUIRED_EVENTS; do
383
+ if ! grep -q "\"$event\"\|'$event'" "$f" 2>/dev/null; then
384
+ echo "FINDING [4A] $f: Stripe event '$event' not handled"
385
+ fi
386
+ done
387
+ done
388
+ ```
389
+
390
+ **Confirm**: Verify the event type appears in a handler dispatch (not just a comment). Confirmed if the event would change user entitlements or financial state but has no handler.
391
+
392
+ **Severity**: P1 for events that affect billing state (subscription.deleted, payment_failed, dispute); P2 for informational events.
393
+
394
+ **Issue title**: `security: Stripe event {EVENT_TYPE} not handled in {FILE} — financial state may diverge`
395
+
396
+ ---
397
+
398
+ ### 4B: Spend Limit Accuracy
399
+
400
+ **What**: Pre-reservation credit checks that use a hardcoded cost cap lower than the maximum possible operation cost allow users to overdraw credit.
401
+
402
+ **Check**:
403
+ ```bash
404
+ grep -rn "pre_reserve\|prereserve\|reserve_credits\|MAX_COST\|max_cost\|PRE_RESERVE" \
405
+ "$REPO_PATH/services" --include="*.py" 2>/dev/null | grep -v '.git' | \
406
+ while IFS= read -r line; do
407
+ echo "REVIEW [4B] $line: verify pre-reservation amount matches maximum tier cost"
408
+ done
409
+ ```
410
+
411
+ **Manual review required**: Compare the pre-reserve amount against the maximum operation cost for the highest-tier model. If pre-reserve < max possible cost, users can overdraw.
412
+
413
+ **Severity**: P2 (financial loss; users can consume credits they don't have).
414
+
415
+ **Issue title**: `security: pre-reservation credit cap {AMOUNT} is below maximum operation cost {MAX_COST}`
416
+
417
+ ---
418
+
419
+ ## Phase 5: Reporting & Issue Creation
420
+
421
+ ### 5A: Collate findings
422
+
423
+ After all phases complete, produce a summary table:
424
+
425
+ ```
426
+ ## Security Audit Findings
427
+
428
+ | # | Phase | Finding | File | Severity | Status |
429
+ |---|-------|---------|------|----------|--------|
430
+ | 1 | 1A | Port binding | docker-compose.prod.yml:12 | P1 | CONFIRMED |
431
+ | 2 | 1B | No USER directive | services/worker/Dockerfile | P2 | CONFIRMED |
432
+ ...
433
+ ```
434
+
435
+ If `--dry-run` was passed, print the table and STOP. Do not create issues.
436
+
437
+ ### 5B: Ensure labels exist in target repo
438
+
439
+ Before creating any issues, ensure all required labels exist:
440
+
441
+ ```bash
442
+ for label in "security" "audit-finding" "P1" "P2" "P3"; do
443
+ gh label create "$label" --color "d73a4a" --force -R "{GH_REPO}" 2>/dev/null || true
444
+ done
445
+ ```
446
+
447
+ ### 5C: Create GitHub issues for CONFIRMED findings
448
+
449
+ For each CONFIRMED finding (not REVIEW-only), create an issue:
450
+
451
+ ```bash
452
+ gh issue create -R "{GH_REPO}" \
453
+ --title "{ISSUE_TITLE}" \
454
+ --label "security,audit-finding,{SEVERITY}" \
455
+ --body "$(cat <<'ISSUE_EOF'
456
+ ## Problem
457
+
458
+ {2-3 sentences describing the finding, its location, and exploitation path.}
459
+
460
+ ## Evidence
461
+
462
+ **File**: \`{FILE}\`
463
+ **Line**: {LINE}
464
+ **Finding**: {GREP_OUTPUT}
465
+
466
+ ## Risk
467
+
468
+ {1-2 sentences: who can exploit this, under what conditions, what can they do.}
469
+
470
+ ## Affected Files
471
+
472
+ 1. \`{FILE}\` — {what needs to change}
473
+
474
+ ## Acceptance Criteria
475
+
476
+ - [ ] {Specific remediation step}
477
+ - [ ] Verified by re-running \`/security-audit\` after fix
478
+
479
+ ## Context
480
+
481
+ **Audited at**: {TIMESTAMP}
482
+ **Audit phase**: {PHASE}
483
+ **Severity**: {SEVERITY}
484
+ **Source**: \`/security-audit\` run on \`{GH_REPO}\`
485
+ ISSUE_EOF
486
+ )"
487
+ ```
488
+
489
+ ### 5D: Post audit summary comment (if invoked from a GitHub issue context)
490
+
491
+ If this command was invoked from within a `/work-on` context or with an issue reference:
492
+
493
+ ```bash
494
+ gh issue comment "{CALLER_ISSUE}" -R RapierCraftStudios/forge --body "$(cat <<'COMMENT_EOF'
495
+ <!-- FORGE:SECURITY_AUDIT -->
496
+ ## Security Audit Complete
497
+
498
+ **Target**: {GH_REPO}
499
+ **Run at**: {TIMESTAMP}
500
+ **Phases**: {PHASES_RUN}
501
+
502
+ ### Findings Summary
503
+
504
+ | Severity | Count |
505
+ |----------|-------|
506
+ | P1 | {P1_COUNT} |
507
+ | P2 | {P2_COUNT} |
508
+ | P3 | {P3_COUNT} |
509
+
510
+ ### Issues Created
511
+
512
+ {LIST_OF_CREATED_ISSUE_URLS}
513
+
514
+ Re-run with: \`/security-audit --repo {REPO_PREFIX}\`
515
+ COMMENT_EOF
516
+ )"
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Suggested Cadence
522
+
523
+ | Trigger | Scope |
524
+ |---------|-------|
525
+ | After pen test engagement | All phases |
526
+ | Quarterly (scheduled) | All phases |
527
+ | After major infrastructure change (new service, docker-compose rewrite) | Phase 1 + Phase 2 |
528
+ | After auth system change | Phase 3 (3C, 3E) |
529
+ | After billing integration change | Phase 4 |