@jaimevalasek/aioson 1.17.2 → 1.18.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 (65) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +85 -51
  3. package/docs/en/3-recipes/full-feature-with-sheldon.md +1 -1
  4. package/docs/en/5-reference/cli-reference.md +4 -4
  5. package/docs/en/5-reference/qa-browser.md +2 -2
  6. package/docs/en/README.md +1 -1
  7. package/docs/en/deyvin-subtask-scout/how-to-use.md +2 -2
  8. package/docs/en/deyvin-subtask-scout/sub-task-scout.md +3 -3
  9. package/docs/en/deyvin-subtask-scout/troubleshooting.md +1 -1
  10. package/docs/pt/3-receitas/publicar-no-aioson-com.md +17 -0
  11. package/docs/pt/5-referencia/comandos-cli.md +2 -2
  12. package/docs/pt/5-referencia/inteligencia-adaptativa.md +3 -3
  13. package/docs/pt/5-referencia/skills.md +1 -1
  14. package/docs/pt/5-referencia/web3.md +3 -3
  15. package/docs/pt/README.md +1 -1
  16. package/docs/pt/_arquivo/README.md +1 -1
  17. package/docs/pt/_arquivo/cenarios.md +31 -31
  18. package/docs/pt/_arquivo/design-hybrid-forge.md +5 -5
  19. package/docs/pt/_arquivo/guia-engineer.md +1 -1
  20. package/docs/pt/_arquivo/profiler-system.md +1 -1
  21. package/docs/pt/_arquivo/site-forge.md +16 -16
  22. package/docs/pt/_arquivo/squad-genome.md +2 -2
  23. package/docs/pt/agentes.md +37 -37
  24. package/docs/pt/deyvin-subtask-scout/como-usar.md +2 -2
  25. package/docs/pt/deyvin-subtask-scout/sub-task-scout.md +1 -1
  26. package/docs/pt/deyvin-subtask-scout/troubleshooting.md +1 -1
  27. package/docs/pt/living-memory/README.md +1 -1
  28. package/docs/pt/living-memory/memoria-viva.md +2 -2
  29. package/docs/pt/living-memory/reflexao-in-harness.md +1 -1
  30. package/docs/pt/living-memory/troubleshooting.md +6 -6
  31. package/package.json +4 -2
  32. package/src/commands/gate-approve.js +56 -1
  33. package/src/commands/live.js +81 -54
  34. package/src/commands/op-capture.js +27 -2
  35. package/src/commands/op-list.js +33 -1
  36. package/src/commands/store-system.js +104 -12
  37. package/src/commands/tool-capabilities.js +14 -10
  38. package/src/commands/workflow-heal.js +47 -1
  39. package/src/i18n/messages/en.js +6 -5
  40. package/src/i18n/messages/pt-BR.js +6 -5
  41. package/src/lib/dev-resume.js +6 -1
  42. package/src/lib/tool-capabilities.js +64 -37
  43. package/src/operator-memory/decision.js +11 -4
  44. package/src/operator-memory/proposal.js +11 -7
  45. package/src/session-handoff.js +52 -1
  46. package/template/.aioson/agents/analyst.md +34 -2
  47. package/template/.aioson/agents/architect.md +33 -1
  48. package/template/.aioson/agents/briefing.md +26 -1
  49. package/template/.aioson/agents/copywriter.md +1 -1
  50. package/template/.aioson/agents/dev.md +2 -2
  51. package/template/.aioson/agents/deyvin.md +12 -12
  52. package/template/.aioson/agents/neo.md +74 -74
  53. package/template/.aioson/agents/orchestrator.md +26 -0
  54. package/template/.aioson/agents/pentester.md +66 -14
  55. package/template/.aioson/agents/pm.md +18 -1
  56. package/template/.aioson/agents/product.md +12 -1
  57. package/template/.aioson/agents/qa.md +3 -3
  58. package/template/.aioson/agents/sheldon.md +24 -4
  59. package/template/.aioson/agents/tester.md +115 -2
  60. package/template/.aioson/docs/briefing/briefing-craft.md +16 -0
  61. package/template/.aioson/docs/deyvin/runtime-handoffs.md +1 -1
  62. package/template/.aioson/docs/handoff-persistence.md +7 -7
  63. package/template/.aioson/docs/pentester/browser-dast-playbook.md +398 -0
  64. package/template/.aioson/rules/agent-structural-contract.md +139 -0
  65. package/template/.aioson/skills/process/decision-presentation/SKILL.md +2 -2
@@ -12,7 +12,7 @@ Do not implement features. Do not review the product. Test what exists.
12
12
 
13
13
  - `@tester` validates behavior, regressions, coverage gaps, and reproducibility of implemented code.
14
14
  - `@tester` does not perform offensive review, threat modeling, exploit discovery, or adversarial probing. Those belong to `@pentester`.
15
- - If `.aioson/context/security-findings-{slug}.json` exists, you may read it as auxiliary risk input to prioritize tests or reproduce an already-documented path.
15
+ - If `.aioson/context/security-findings-{slug}.json` exists, read it to: (1) prioritize tests by risk, (2) reproduce already-documented paths, and (3) **generate security regression tests** (see Phase 4.6) that prevent fixed vulnerabilities from recurring.
16
16
  - Do not create or close security findings, reclassify severity, or take ownership of residual security risk.
17
17
  - If testing reveals a likely security issue that is not already documented, record the evidence in `test-plan.md` or `test-inventory.md` and route it to `@pentester` or `@qa`.
18
18
 
@@ -339,6 +339,119 @@ Before declaring Phase 4 done, run this checklist against every test file writte
339
339
 
340
340
  For deep refactor guidance, load `.aioson/docs/tester/coverage-quality.md` § 4.
341
341
 
342
+ ## Phase 4.6 — Security regression tests (from @pentester findings)
343
+
344
+ **Trigger:** `.aioson/context/security-findings-{slug}.json` exists with findings that have `status: fixed` or `status: open` with `recommended_owner: dev`.
345
+
346
+ **Purpose:** Convert one-shot pentester findings into persistent Playwright tests that run in CI and catch regressions. The pentester discovers; the tester prevents recurrence.
347
+
348
+ **Do NOT perform adversarial probing or threat modeling.** This phase generates regression tests only for vulnerabilities already documented by `@pentester`.
349
+
350
+ ### Step 1 — Read findings
351
+
352
+ 1. Load `security-findings-{slug}.json`.
353
+ 2. Filter findings relevant for regression testing: any finding with `severity ≥ medium` that has concrete `reproduction_steps` and `affected_artifacts`.
354
+ 3. Group by surface type — each group becomes a test describe block.
355
+
356
+ ### Step 2 — Generate tests by surface type
357
+
358
+ Create `tests/security-regression.test.{ext}` (or `tests/security-regression-{slug}.test.{ext}` for feature-scoped). Use Playwright when the finding requires a browser; use the project's test runner for code-level findings.
359
+
360
+ **Test patterns by surface:**
361
+
362
+ | Finding surface | Test pattern | Example assertion |
363
+ |---|---|---|
364
+ | `app_target_browser_exposure` | Playwright: fetch main page, inspect response headers | `expect(headers['content-security-policy']).toBeTruthy()` |
365
+ | `app_target_browser_exposure` (cookies) | Playwright: authenticate, inspect cookies | `expect(sessionCookie.httpOnly).toBe(true)` |
366
+ | `app_target_browser_exposure` (storage) | Playwright: authenticate, evaluate localStorage | `expect(storageKeys).not.toContain('token')` |
367
+ | `app_target_browser_exposure` (CORS) | Playwright/fetch: request with evil Origin | `expect(acao).not.toBe('*')` |
368
+ | `app_target_browser_exposure` (source maps) | Playwright: try fetching `*.js.map` | `expect(mapResponse.status()).not.toBe(200)` |
369
+ | `app_target_secrets_crypto` | Grep/read: scan rendered HTML for secret patterns | `expect(html).not.toMatch(/sk-[a-zA-Z0-9]{20,}/)` |
370
+ | `app_target_injection_xss` | Playwright: inject payload in inputs, check for execution | `expect(xssFired).toBe(false)` |
371
+ | `app_target_ownership_idor` | HTTP: request resource as wrong user | `expect(response.status).toBe(403)` |
372
+ | `app_target_auth_rate_limit` | HTTP: send N+1 wrong passwords | `expect(response.status).toBe(429)` after threshold |
373
+ | `app_target_logging_monitoring` | Read log output after security event | `expect(logEntry).toContain('login_failed')` |
374
+
375
+ ### Step 3 — Playwright security regression template
376
+
377
+ For browser-based findings, generate tests following this structure:
378
+
379
+ ```javascript
380
+ const { test, expect } = require('@playwright/test');
381
+
382
+ test.describe('Security regression — {slug}', () => {
383
+
384
+ test('SF-{slug}-01: CSP header present and no unsafe-inline', async ({ page }) => {
385
+ const response = await page.goto(process.env.TARGET_URL || 'http://localhost:3000');
386
+ const csp = response.headers()['content-security-policy'] || '';
387
+ expect(csp).toBeTruthy();
388
+ expect(csp).not.toContain("'unsafe-inline'");
389
+ });
390
+
391
+ test('SF-{slug}-02: session cookie has HttpOnly and Secure flags', async ({ context }) => {
392
+ const cookies = await context.cookies();
393
+ const session = cookies.find(c => /session|token|auth|sid/i.test(c.name));
394
+ if (session) {
395
+ expect(session.httpOnly).toBe(true);
396
+ expect(session.secure).toBe(true);
397
+ expect(session.sameSite).not.toBe('None');
398
+ }
399
+ });
400
+
401
+ test('SF-{slug}-03: no secrets in localStorage', async ({ page }) => {
402
+ await page.goto(process.env.TARGET_URL || 'http://localhost:3000');
403
+ const storage = await page.evaluate(() => {
404
+ const data = {};
405
+ for (let i = 0; i < localStorage.length; i++) {
406
+ const key = localStorage.key(i);
407
+ data[key] = localStorage.getItem(key);
408
+ }
409
+ return JSON.stringify(data);
410
+ });
411
+ expect(storage).not.toMatch(/sk-[a-zA-Z0-9]{20,}/);
412
+ expect(storage).not.toMatch(/eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/);
413
+ });
414
+
415
+ test('SF-{slug}-04: source maps not accessible in production', async ({ page }) => {
416
+ const jsFiles = [];
417
+ page.on('response', (res) => {
418
+ if (res.url().endsWith('.js') && res.status() === 200) jsFiles.push(res.url());
419
+ });
420
+ await page.goto(process.env.TARGET_URL || 'http://localhost:3000', { waitUntil: 'networkidle' });
421
+ for (const js of jsFiles.slice(0, 5)) {
422
+ const mapRes = await page.request.get(js + '.map');
423
+ expect(mapRes.status()).not.toBe(200);
424
+ }
425
+ });
426
+
427
+ });
428
+ ```
429
+
430
+ ### Step 4 — Traceability
431
+
432
+ Each test name must include the finding ID from `security-findings-{slug}.json` (e.g., `SF-checkout-03`). This creates a traceable link: finding → regression test → CI pass/fail.
433
+
434
+ In `test-plan.md`, add a **Security regression coverage** section:
435
+
436
+ ```markdown
437
+ ## Security regression coverage
438
+
439
+ | Finding ID | Severity | Surface | Test file | Test name | Status |
440
+ |---|---|---|---|---|---|
441
+ | SF-checkout-01 | high | browser_exposure | tests/security-regression.test.js | CSP header present | ✓ passing |
442
+ | SF-checkout-03 | critical | secrets_crypto | tests/security-regression.test.js | no secrets in localStorage | ✓ passing |
443
+ ```
444
+
445
+ ### Step 5 — Verify all regression tests pass
446
+
447
+ Run the security regression tests. If any fail, it means the fix is incomplete — report in `test-plan.md` as `[fix-incomplete]` and route to `@dev`.
448
+
449
+ ### When to skip this phase
450
+
451
+ - No `security-findings-{slug}.json` exists — skip silently
452
+ - All findings have `severity: info` or `severity: low` — skip (not worth regression test maintenance)
453
+ - Project has no browser UI and all findings are code-level — skip Playwright tests, use unit/integration tests only
454
+
342
455
  ## Adjacent quality layers — opt-in by trigger
343
456
 
344
457
  Don't auto-load. Add only when the trigger fires. Full details: `.aioson/docs/tester/coverage-quality.md` § 6.
@@ -517,7 +630,7 @@ Register: `aioson agent:done . --agent=tester --summary="<one-line summary>" 2>/
517
630
  ---
518
631
  ## ▶ Próximo passo
519
632
  **[Se aprovado: @dev para próxima fase | Se gaps: @dev com lista de falhas]**
520
- Ative: `/dev`
633
+ Ative: `/aioson:agent:dev`
521
634
  > Recomendado: `/clear` antes — janela de contexto fresca
522
635
  ---
523
636
 
@@ -33,6 +33,22 @@ Goal of every briefing: give `@product` enough confidence to either commit to a
33
33
  - **Themes that repeat the table of contents** instead of partitioning concerns.
34
34
  - **PM-only briefing** with no engineering or design eyes — a feasibility delusion is hiding somewhere.
35
35
 
36
+ ### Mitigating weak markers — handoff stays `@product`
37
+
38
+ When you detect a weak marker (especially **PM-only / single-voice / feasibility delusion**), the **canonical handoff does not change**: `@briefing → @product`. The mitigation is recorded *inside* the briefing, not in the handoff.
39
+
40
+ - **Acknowledge the marker explicitly** in `## Risks` or `## Open questions` (e.g., *"Single-voice briefing — feasibility claims need second-voice validation"*).
41
+ - **Name the specific items** that need expert review: which technical assumption, which sizing call, which architectural choice is at risk.
42
+ - **Record the consultation as a recommendation for `@product`'s enrichment phase**, not as a handoff: *"`@product` should consult `@sheldon` during enrichment for second-voice on AST scope and sizing"*. The PRD is the input `@sheldon` needs to run.
43
+
44
+ **Anti-pattern — never do this:**
45
+
46
+ > ❌ *"Recommendation: pass through `@sheldon` before `@product` commits a PRD."*
47
+
48
+ `@sheldon` operates **exclusively on PRDs not yet implemented** (see `sheldon.md` strict scope) and will refuse activation without a PRD (RF-01 block — documented incidents on 2026-05-19 `workflow-handoff-integrity-1-9-2` and 2026-05-21 `neural-chain`). Skipping `@product` breaks the chain.
49
+
50
+ **Other weak markers map the same way:** mention the gap, name who should weigh in *during PRD enrichment*, hand off to `@product`.
51
+
36
52
  ## 2. Problem framing — Jobs-to-be-Done (JTBD)
37
53
 
38
54
  Customers don't "want a product"; they hire it to make progress. The job is the *progress*, not the surface task.
@@ -39,4 +39,4 @@ Plain natural-language agent activation in an external client does not create ru
39
39
 
40
40
  The runtime helpers above cover same-session handoffs (`live:handoff`, `runtime:session:finish`). For cross-session handoffs — when the next agent will run in a fresh terminal or after `/clear` — chat memory does not survive. Before suggesting `/clear`, persist the diagnostic to `plans/{slug}.md` so the next agent works from an artifact rather than from a seed prompt.
41
41
 
42
- Load `.aioson/docs/handoff-persistence.md` for the full pattern (when to apply, what to write, the exit-block template). Apply it whenever the recommended next agent is one that consumes raw plans (`/briefing` foremost, sometimes `/product`) or needs the full diagnostic to operate (`/analyst`, `/architect`, `/sheldon`). Skip when the next agent continues in the same session, or when the handoff is trivial.
42
+ Load `.aioson/docs/handoff-persistence.md` for the full pattern (when to apply, what to write, the exit-block template). Apply it whenever the recommended next agent is one that consumes raw plans (`/aioson:agent:briefing` foremost, sometimes `/aioson:agent:product`) or needs the full diagnostic to operate (`/aioson:agent:analyst`, `/aioson:agent:architect`, `/aioson:agent:sheldon`). Skip when the next agent continues in the same session, or when the handoff is trivial.
@@ -25,21 +25,21 @@ Before suggesting `/clear` to the user, persist the actionable diagnostic to `pl
25
25
  2. /clear is safe — the next agent reads plans/{slug}.md
26
26
  ```
27
27
 
28
- `plans/` is the canonical input directory for `/briefing` (and a useful seed for `/product` too). The directory may not exist yet — create it.
28
+ `plans/` is the canonical input directory for `/aioson:agent:briefing` (and a useful seed for `/aioson:agent:product` too). The directory may not exist yet — create it.
29
29
 
30
30
  ## When to apply
31
31
 
32
32
  | Situation | Persist? |
33
33
  |---|---|
34
- | Handoff routes to an agent that takes raw plans (`/briefing` first and foremost, sometimes `/product`) | Yes |
35
- | Handoff routes to an agent that needs a discovery pass (`/analyst`, `/architect`, `/sheldon`) | Yes — they read context from `.aioson/context/` AND from raw plans |
36
- | Same-session continuation (`/dev` keeps going, `/qa` reviews implementation just done) | No — context is in chat |
34
+ | Handoff routes to an agent that takes raw plans (`/aioson:agent:briefing` first and foremost, sometimes `/aioson:agent:product`) | Yes |
35
+ | Handoff routes to an agent that needs a discovery pass (`/aioson:agent:analyst`, `/aioson:agent:architect`, `/aioson:agent:sheldon`) | Yes — they read context from `.aioson/context/` AND from raw plans |
36
+ | Same-session continuation (`/aioson:agent:dev` keeps going, `/aioson:agent:qa` reviews implementation just done) | No — context is in chat |
37
37
  | Handoff happens via tracked live session (`aioson live:handoff`) | No — telemetry already carries the trail |
38
- | Trivial routing ("you want `/setup` first") with no diagnostic to preserve | No |
38
+ | Trivial routing ("you want `/aioson:agent:setup` first") with no diagnostic to preserve | No |
39
39
 
40
40
  ## What to write
41
41
 
42
- Structure of `plans/{slug}.md` (lightweight — `/briefing` will enrich it):
42
+ Structure of `plans/{slug}.md` (lightweight — `/aioson:agent:briefing` will enrich it):
43
43
 
44
44
  ```md
45
45
  # {Short title} — raw plan
@@ -89,6 +89,6 @@ Session artifacts written:
89
89
  ## Anti-patterns
90
90
 
91
91
  - **Inlining 2 KB of diagnostic as a "seed prompt" in the routing message.** The user shouldn't have to copy-paste a wall of text. Persist it.
92
- - **Persisting trivial routings.** A user who asks "what does `/setup` do" doesn't need a `plans/` file written. Apply the table above.
92
+ - **Persisting trivial routings.** A user who asks "what does `/aioson:agent:setup` do" doesn't need a `plans/` file written. Apply the table above.
93
93
  - **Persisting code archaeology.** Code lives in code; reading recommendations live in the artifact only when they would otherwise be lost across `/clear`.
94
94
  - **Forgetting to mention the file.** If you wrote `plans/{slug}.md` but the handoff message doesn't reference it, the user won't know to read it (or to let the next agent read it).
@@ -0,0 +1,398 @@
1
+ ---
2
+ description: "Pentester browser DAST playbook — Playwright-based dynamic security probes for app_target surface TS-A08 (browser_exposure). Load when review_contract.target_mode = app_target AND the application has a browser-accessible UI."
3
+ ---
4
+
5
+ # Pentester — Browser DAST Playbook
6
+
7
+ Load this when `review_contract.target_mode = app_target` AND the target application serves a browser-accessible UI (web app, SPA, SSR page).
8
+
9
+ ## Prerequisites
10
+
11
+ 1. Application must be running and accessible at a known URL.
12
+ 2. Playwright installed: `npm install -g playwright && npx playwright install chromium`
13
+ 3. Verify readiness: `aioson qa:doctor`
14
+
15
+ ## Phase 0 — Baseline DAST (mandatory pre-step)
16
+
17
+ Before running any manual Playwright probe, execute the AIOSON automated DAST baseline:
18
+
19
+ ```bash
20
+ # Full run with hacker persona (secrets, XSS, IDOR, open redirect, SQL injection, debug routes)
21
+ aioson qa:run --persona=hacker --url=<target-url>
22
+
23
+ # Autonomous crawl scan (discovers routes, probes each one)
24
+ aioson qa:scan --url=<target-url> --depth=3 --max-pages=50
25
+ ```
26
+
27
+ Read `aios-qa-report.json` and import any `critical` or `high` findings into the pentester findings artifact as `status: needs_validation`. The pentester validates or reclassifies each — `qa:run` automates detection, pentester applies adversarial judgment.
28
+
29
+ **Do NOT skip Phase 0.** The automated baseline catches low-hanging fruit (exposed secrets, accessible .env files, basic XSS) without burning manual probe time. Phase 1+ targets what automation misses.
30
+
31
+ ## Phase 1 — Security Headers Audit
32
+
33
+ Navigate to the target URL with Playwright and capture response headers on the main document request.
34
+
35
+ ### Mandatory headers to check
36
+
37
+ | Header | Expected | Severity if missing/weak |
38
+ |---|---|---|
39
+ | `Content-Security-Policy` | Present, no `unsafe-inline` for scripts, no `unsafe-eval` | `high` if missing entirely, `medium` if has `unsafe-inline`/`unsafe-eval` |
40
+ | `Strict-Transport-Security` | `max-age>=31536000; includeSubDomains` | `medium` (localhost exempt) |
41
+ | `X-Content-Type-Options` | `nosniff` | `medium` |
42
+ | `X-Frame-Options` | `DENY` or `SAMEORIGIN` (check CSP `frame-ancestors` too) | `medium` |
43
+ | `Referrer-Policy` | `strict-origin-when-cross-origin` or stricter | `low` |
44
+ | `Permissions-Policy` | Present, restricts `camera`, `microphone`, `geolocation` at minimum | `low` |
45
+ | `X-XSS-Protection` | `0` (modern recommendation — CSP supersedes; `1; mode=block` causes info leaks in old IE) | `info` |
46
+
47
+ ### Headers that SHOULD NOT be present
48
+
49
+ | Header | Why | Severity |
50
+ |---|---|---|
51
+ | `Server` with version (e.g. `nginx/1.24.0`) | Reveals infrastructure version — aids targeted exploits | `low` |
52
+ | `X-Powered-By` | Reveals framework (Express, PHP, ASP.NET) | `low` |
53
+ | `X-AspNet-Version` / `X-AspNetMvc-Version` | .NET version disclosure | `low` |
54
+
55
+ ### Playwright probe pattern
56
+
57
+ ```javascript
58
+ const response = await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
59
+ const headers = response.headers();
60
+
61
+ // Check presence and value of each security header
62
+ const csp = headers['content-security-policy'] || '';
63
+ const hsts = headers['strict-transport-security'] || '';
64
+ const xcto = headers['x-content-type-options'] || '';
65
+ const xfo = headers['x-frame-options'] || '';
66
+ const rp = headers['referrer-policy'] || '';
67
+ const pp = headers['permissions-policy'] || '';
68
+
69
+ // Disclosure headers (should NOT be present with version info)
70
+ const server = headers['server'] || '';
71
+ const poweredBy = headers['x-powered-by'] || '';
72
+ ```
73
+
74
+ **ASVS:** V3.4 (CSP), V12.1 (HSTS), V13.1.5 (security headers), V14.3.3 (server disclosure).
75
+
76
+ ## Phase 2 — Cookie Security Audit
77
+
78
+ After authentication (if credentials are available), inspect all cookies set by the application.
79
+
80
+ ### Required attributes for session/auth cookies
81
+
82
+ | Attribute | Expected | Severity if missing |
83
+ |---|---|---|
84
+ | `Secure` | `true` (except localhost) | `high` |
85
+ | `HttpOnly` | `true` for session tokens | `high` |
86
+ | `SameSite` | `Lax` or `Strict` | `medium` |
87
+ | `Path` | Narrowest scope needed (prefer `/` only if necessary) | `info` |
88
+ | `__Host-` prefix | Recommended for session cookies — enforces Secure + Path=/ + no Domain | `info` |
89
+ | `__Secure-` prefix | Alternative — enforces Secure flag | `info` |
90
+ | Max-Age / Expires | Session cookies should not persist beyond browser close unless explicitly justified | `low` |
91
+
92
+ ### Playwright probe pattern
93
+
94
+ ```javascript
95
+ const cookies = await context.cookies();
96
+ for (const cookie of cookies) {
97
+ const isSession = /session|token|auth|sid|jwt/i.test(cookie.name);
98
+ if (isSession) {
99
+ // Check Secure flag
100
+ if (!cookie.secure) { /* HIGH finding */ }
101
+ // Check HttpOnly flag
102
+ if (!cookie.httpOnly) { /* HIGH finding */ }
103
+ // Check SameSite
104
+ if (cookie.sameSite === 'None' && !cookie.secure) { /* MEDIUM finding */ }
105
+ if (!cookie.sameSite || cookie.sameSite === 'None') { /* MEDIUM finding — CSRF surface */ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ **ASVS:** V7.1.1 (cookie attributes), V7.1.2 (HttpOnly), V7.1.3 (SameSite).
111
+
112
+ ## Phase 3 — Client-Side Storage Audit
113
+
114
+ Check localStorage and sessionStorage for sensitive data that should never be stored client-side.
115
+
116
+ ### Sensitive patterns to flag
117
+
118
+ | Pattern | Risk | Severity |
119
+ |---|---|---|
120
+ | JWT tokens (eyJ...) | Token theft via XSS gives full account takeover | `high` |
121
+ | API keys (sk-*, pk_*, AKIA*, AIzaSy*) | Direct API abuse | `critical` |
122
+ | Passwords / password hashes | Direct compromise | `critical` |
123
+ | PII (email + name + phone + address combined) | Privacy violation, GDPR exposure | `medium` |
124
+ | Credit card numbers / CVV | PCI-DSS violation | `critical` |
125
+ | Session IDs | Session hijacking via XSS | `high` |
126
+
127
+ ### Playwright probe pattern
128
+
129
+ ```javascript
130
+ const storageData = await page.evaluate(() => {
131
+ const data = { localStorage: {}, sessionStorage: {} };
132
+ for (let i = 0; i < localStorage.length; i++) {
133
+ const key = localStorage.key(i);
134
+ data.localStorage[key] = localStorage.getItem(key);
135
+ }
136
+ for (let i = 0; i < sessionStorage.length; i++) {
137
+ const key = sessionStorage.key(i);
138
+ data.sessionStorage[key] = sessionStorage.getItem(key);
139
+ }
140
+ return data;
141
+ });
142
+
143
+ // Check each value against SECRET_PATTERNS and JWT regex
144
+ const jwtRegex = /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/;
145
+ ```
146
+
147
+ **ASVS:** V14.1.3 (sensitive data in client storage), V8.2.2 (no sensitive data in browser storage).
148
+
149
+ ## Phase 4 — CORS Misconfiguration
150
+
151
+ Test the server's CORS policy by sending requests with crafted `Origin` headers.
152
+
153
+ ### Probes
154
+
155
+ 1. **Wildcard origin**: send `Origin: https://evil.com` — if response has `Access-Control-Allow-Origin: *`, flag it.
156
+ 2. **Reflected origin**: send `Origin: https://evil.com` — if response reflects `Access-Control-Allow-Origin: https://evil.com`, flag it.
157
+ 3. **Null origin**: send `Origin: null` — some misconfigured servers allow this.
158
+ 4. **Credentials with wildcard**: `Access-Control-Allow-Credentials: true` with `Access-Control-Allow-Origin: *` is a browser-rejected but server-misconfigured pattern.
159
+ 5. **Subdomain regex bypass**: `Origin: https://evil-example.com` or `Origin: https://example.com.evil.com` — catches poor regex matching.
160
+
161
+ ### Playwright probe pattern
162
+
163
+ ```javascript
164
+ // Playwright doesn't set custom Origin on navigation, use page.evaluate with fetch
165
+ const corsResult = await page.evaluate(async (targetUrl) => {
166
+ try {
167
+ const res = await fetch(targetUrl, { mode: 'cors', credentials: 'include' });
168
+ return {
169
+ acao: res.headers.get('access-control-allow-origin'),
170
+ acac: res.headers.get('access-control-allow-credentials')
171
+ };
172
+ } catch { return { blocked: true }; }
173
+ }, apiEndpoint);
174
+ ```
175
+
176
+ For deeper CORS testing, use `page.route()` to intercept and modify request headers, or use the Playwright `request` API directly.
177
+
178
+ **ASVS:** V4.3.1, V4.3.2 (CORS policy).
179
+
180
+ ## Phase 5 — Source Map Exposure
181
+
182
+ Source maps (`.js.map`) in production reveal the full original source code, including comments, variable names, and internal logic.
183
+
184
+ ### Probes
185
+
186
+ 1. Capture all JS file URLs loaded by the page.
187
+ 2. For each `*.js` URL, try fetching `*.js.map`.
188
+ 3. Check the HTML source for `//# sourceMappingURL=` directives.
189
+ 4. Check response headers for `SourceMap:` or `X-SourceMap:` headers.
190
+
191
+ ### Playwright probe pattern
192
+
193
+ ```javascript
194
+ const jsFiles = [];
195
+ page.on('response', (response) => {
196
+ if (response.url().endsWith('.js') && response.status() === 200) {
197
+ jsFiles.push(response.url());
198
+ }
199
+ });
200
+
201
+ await page.goto(targetUrl, { waitUntil: 'networkidle' });
202
+
203
+ for (const jsUrl of jsFiles) {
204
+ const mapUrl = jsUrl + '.map';
205
+ try {
206
+ const mapResponse = await page.goto(mapUrl, { waitUntil: 'commit', timeout: 3000 });
207
+ if (mapResponse && mapResponse.status() === 200) {
208
+ const body = await mapResponse.text();
209
+ if (body.includes('"sources"') || body.includes('"mappings"')) {
210
+ // HIGH finding — source map accessible
211
+ }
212
+ }
213
+ } catch { /* not accessible — good */ }
214
+ }
215
+ ```
216
+
217
+ **Severity:** `high` (reveals full source code including business logic, API routes, internal comments).
218
+
219
+ **Fix:** Remove source maps from production builds. Configure bundler (webpack/vite/next) to exclude `.map` files from production output, or restrict via web server rules.
220
+
221
+ ## Phase 6 — Clickjacking (Frame Injection)
222
+
223
+ Test if the target page can be embedded in an iframe on an attacker-controlled domain.
224
+
225
+ ### Probes
226
+
227
+ 1. Check `X-Frame-Options` header (covered in Phase 1).
228
+ 2. Check CSP `frame-ancestors` directive (more granular than X-Frame-Options).
229
+ 3. If neither is set, attempt to iframe the page:
230
+
231
+ ```javascript
232
+ // Create a test page that iframes the target
233
+ await page.setContent(`
234
+ <iframe id="target" src="${targetUrl}" width="800" height="600"></iframe>
235
+ `);
236
+ await page.waitForTimeout(2000);
237
+
238
+ const iframeLoaded = await page.evaluate(() => {
239
+ const iframe = document.getElementById('target');
240
+ try {
241
+ // Cross-origin iframe access will throw — but loading without error means framing is allowed
242
+ return iframe.contentWindow.location.href !== 'about:blank';
243
+ } catch { return true; } // cross-origin = loaded but blocked by same-origin policy (still frameable)
244
+ });
245
+ ```
246
+
247
+ **ASVS:** V3.7 (clickjacking), V13.1.5 (frame protection).
248
+
249
+ ## Phase 7 — Subresource Integrity (SRI)
250
+
251
+ Scripts and stylesheets loaded from CDNs or third-party origins should have `integrity` attributes to prevent supply-chain attacks (CDN compromise, DNS hijacking).
252
+
253
+ ### Probes
254
+
255
+ ```javascript
256
+ const sriIssues = await page.evaluate(() => {
257
+ const issues = [];
258
+ const scripts = document.querySelectorAll('script[src]');
259
+ for (const s of scripts) {
260
+ const src = s.getAttribute('src') || '';
261
+ const isExternal = src.startsWith('http') && !src.includes(window.location.hostname);
262
+ if (isExternal && !s.getAttribute('integrity')) {
263
+ issues.push({ type: 'script', src: src.substring(0, 120) });
264
+ }
265
+ }
266
+ const links = document.querySelectorAll('link[rel="stylesheet"][href]');
267
+ for (const l of links) {
268
+ const href = l.getAttribute('href') || '';
269
+ const isExternal = href.startsWith('http') && !href.includes(window.location.hostname);
270
+ if (isExternal && !l.getAttribute('integrity')) {
271
+ issues.push({ type: 'stylesheet', src: href.substring(0, 120) });
272
+ }
273
+ }
274
+ return issues;
275
+ });
276
+ ```
277
+
278
+ **Severity:** `medium` per external resource without SRI. `high` if the resource is a payment or auth SDK.
279
+
280
+ **ASVS:** V15.2 (SRI).
281
+
282
+ ## Phase 8 — Error Page Information Disclosure
283
+
284
+ Trigger error conditions and inspect the response for framework-default error pages that leak internal information.
285
+
286
+ ### Probes
287
+
288
+ 1. **404 page**: navigate to a random non-existent path. Check for stack traces, framework names, file paths.
289
+ 2. **500 trigger**: if any form/endpoint is accessible, submit malformed data designed to cause server errors.
290
+ 3. **Verbose errors**: check if the response contains path separators (`/`, `\`), line numbers (`at line`), or framework markers (`Django`, `Rails`, `Express`, `Next.js`, `Laravel`, `Spring`).
291
+
292
+ ```javascript
293
+ const errorPage = await page.goto(`${targetUrl}/nonexistent-${Date.now()}`, {
294
+ waitUntil: 'domcontentloaded', timeout: 5000
295
+ });
296
+ const errorHtml = await page.content();
297
+
298
+ const leakPatterns = [
299
+ /at\s+\w+\s+\(.*:\d+:\d+\)/, // JS stack trace
300
+ /File ".*", line \d+/, // Python traceback
301
+ /vendor\/.*\.php:\d+/, // PHP stack trace
302
+ /SQLSTATE\[/, // SQL error
303
+ /Traceback \(most recent/, // Python traceback header
304
+ /(Django|Laravel|Rails|Express|Spring|Next\.js) (Debug|Error)/i
305
+ ];
306
+ ```
307
+
308
+ **Severity:** `medium` for framework name disclosure, `high` for full stack traces with file paths.
309
+
310
+ **ASVS:** V13.1.3 (error handling), V16.2 (information disclosure).
311
+
312
+ ## Phase 9 — HTML Meta & Comment Leaks
313
+
314
+ Inspect the HTML source for metadata and comments that disclose internal information.
315
+
316
+ ### Probes
317
+
318
+ ```javascript
319
+ const metaLeaks = await page.evaluate(() => {
320
+ const issues = [];
321
+
322
+ // Generator meta tags
323
+ const generator = document.querySelector('meta[name="generator"]');
324
+ if (generator) issues.push({ type: 'generator', value: generator.content });
325
+
326
+ // HTML comments with sensitive content
327
+ const walker = document.createTreeWalker(document, NodeFilter.SHOW_COMMENT);
328
+ while (walker.nextNode()) {
329
+ const text = walker.currentNode.textContent.trim();
330
+ if (/(TODO|FIXME|HACK|password|secret|key|token|debug)/i.test(text)) {
331
+ issues.push({ type: 'comment', value: text.substring(0, 100) });
332
+ }
333
+ }
334
+
335
+ // Hidden inputs with sensitive-looking names
336
+ const hiddenInputs = document.querySelectorAll('input[type="hidden"]');
337
+ for (const input of hiddenInputs) {
338
+ const name = input.name || '';
339
+ if (/(token|secret|key|csrf|nonce)/i.test(name) && input.value.length > 20) {
340
+ issues.push({ type: 'hidden_input', name, valueLength: input.value.length });
341
+ }
342
+ }
343
+
344
+ return issues;
345
+ });
346
+ ```
347
+
348
+ **Severity:** `low` for generator tags, `medium` for TODO/debug comments, `info` for CSRF tokens in hidden inputs (expected pattern, but verify they rotate).
349
+
350
+ ## Integration with pentester findings artifact
351
+
352
+ Map every finding from this playbook to the standard pentester finding schema:
353
+
354
+ ```json
355
+ {
356
+ "id": "SF-{slug}-NN",
357
+ "feature_slug": "{slug}",
358
+ "surface": "app_target_browser_exposure",
359
+ "severity": "...",
360
+ "title": "...",
361
+ "hypothesis": "Browser-based DAST probe via Playwright",
362
+ "preconditions": ["Application running at {url}", "Playwright + Chromium installed"],
363
+ "reproduction_steps": ["1. Run aioson qa:run --persona=hacker", "2. ..."],
364
+ "evidence": ["Response header dump", "Screenshot path"],
365
+ "impact": "...",
366
+ "affected_artifacts": ["URL or source file path"],
367
+ "suggested_fix": "...",
368
+ "recommended_owner": "dev",
369
+ "recommended_gate_status": "...",
370
+ "status": "open",
371
+ "safe_to_reproduce": true,
372
+ "asvs_ids": ["V..."]
373
+ }
374
+ ```
375
+
376
+ ## Summary — probe priority order
377
+
378
+ | Phase | What | Time estimate | Severity ceiling |
379
+ |---|---|---|---|
380
+ | 0 | `qa:run --persona=hacker` + `qa:scan` baseline | 2-5 min | critical |
381
+ | 1 | Security headers | 30s | high |
382
+ | 2 | Cookie attributes | 30s | high |
383
+ | 3 | Client-side storage | 30s | critical |
384
+ | 4 | CORS misconfiguration | 1 min | high |
385
+ | 5 | Source map exposure | 1 min | high |
386
+ | 6 | Clickjacking | 30s | medium |
387
+ | 7 | SRI (CDN resources) | 30s | medium |
388
+ | 8 | Error page disclosure | 30s | high |
389
+ | 9 | HTML meta & comment leaks | 30s | medium |
390
+
391
+ Total additional time beyond Phase 0: ~5-6 minutes of automated probing.
392
+
393
+ ## References
394
+
395
+ - OWASP ASVS 5.0: Chapters V3, V4, V7, V8, V12, V13, V14, V15, V16
396
+ - OWASP Testing Guide v4.2: OTG-CONFIG, OTG-INFO, OTG-SESS
397
+ - Mozilla Observatory scoring methodology
398
+ - Playwright API: https://playwright.dev/docs/api/class-response