7.2 KiB
Balanced Chamber Summary — Kordant Security Audit
Phase: L5 (Single Review Chamber + FP Check)
Date: 2026-05-28
Target: Kordant monorepo (SolidStart + tRPC + Drizzle ORM + Stripe + WebSocket + Browser Extension)
Status: CLOSED
Executive Summary
19 draft findings were evaluated (12 from p4 SAST phase, 7 from l4 probe phase). After ideological challenge, false-positive elimination, and duplicate consolidation:
- Valid findings promoted to p8: 11
- Rejected (false positive): 1
- Rejected (low severity): 3
- Rejected (duplicate): 4
The 11 surviving findings cover XSS, SSRF, open redirect, rate limit bypass, CORS misconfiguration, webhook type safety, webhook replay, WebSocket authentication weaknesses, resource exhaustion, and vulnerable dependency usage.
Finding Verdict Table
| # | Source ID | Slug | Verdict | Severity | p8 Draft |
|---|---|---|---|---|---|
| 1 | p4-004 | xss-in-innerhtml | VALID | HIGH | p8-001-xss-in-innerhtml.md |
| 2 | p4-002 | puppeteer-ssrf | VALID | MEDIUM | p8-002-puppeteer-ssrf.md |
| 3 | p4-010 | open-redirect-return-url | VALID | MEDIUM | p8-003-open-redirect-return-url.md |
| 4 | p4-009 | rate-limit-substring-bypass | VALID | MEDIUM | p8-004-rate-limit-substring-bypass.md |
| 5 | p4-003 | cors-origin-env-var | VALID | MEDIUM | p8-005-cors-origin-env-var.md |
| 6 | p4-006 | webhook-type-coercion | VALID | MEDIUM | p8-006-webhook-type-coercion.md |
| 7 | l4-001 | webhook-replay | VALID | MEDIUM | p8-007-webhook-replay.md |
| 8 | p4-007 | websocket-jwt-query-param | VALID | MEDIUM | p8-008-websocket-jwt-query-param.md |
| 9 | p4-011 | websocket-no-origin-validation | VALID | MEDIUM | p8-009-websocket-no-origin-validation.md |
| 10 | l4-003 | voiceprint-resource-exhaustion | VALID | MEDIUM | p8-010-voiceprint-resource-exhaustion.md |
| 11 | p4-008 | superjson-vulnerable-version | VALID | MEDIUM | p8-011-superjson-vulnerable-version.md |
Rejected Findings
False Positive (1)
| Source ID | Slug | Reason |
|---|---|---|
| p4-005 | path-traversal-audio-storage | userId comes from ctx.user.id (authenticated session), NOT user input — no path traversal vector exists |
Low Severity (3)
| Source ID | Slug | Reason |
|---|---|---|
| p4-001 | unvalidated-role-mutation | No current privilege escalation path; role check only looks for "admin"; setting role to non-admin strings grants no privileges |
| p4-012 | admin-sql-pattern | Latent risk only; current sql<> usage is safe (wraps count() aggregate); no active exploitation |
| l4-007 | extension-noop-endpoints | Rate-limited at 5/min; no data corruption or privilege escalation; resource waste only |
Duplicate (4)
| Source ID | Slug | Duplicate Of |
|---|---|---|
| l4-002 | return-url-open-redirect-stripe | p4-010 (open-redirect-return-url) |
| l4-004 | websocket-jwt-leakage-query-param | p4-007 (websocket-jwt-query-param) |
| l4-005 | webhook-type-coercion-data-corruption | p4-006 (webhook-type-coercion) |
| l4-006 | admin-role-unrestricted-value | p4-001 (unvalidated-role-mutation) |
Severity Distribution
| Severity | Count | Findings |
|---|---|---|
| HIGH | 1 | XSS via unsanitized innerHTML |
| MEDIUM | 10 | Puppeteer SSRF, Open redirect, Rate limit bypass, CORS origin, Webhook type coercion, Webhook replay, WebSocket JWT leak, WebSocket origin, VoicePrint exhaustion, Superjson CVE |
| LOW | 0 | — |
Threat Cluster Coverage
| DFD/CFD Slice | Findings | Notes |
|---|---|---|
| DFD-1: tRPC → Drizzle ORM | 0 | CVE-2026-39356 surface noted; no active injection found |
| DFD-2: VoicePrint Pipeline | 2 | p8-010 (resource exhaustion), p4-005 (FP) |
| DFD-3: Browser Ext → tRPC | 1 | p8-011 (superjson CVE) |
| DFD-4: WebSocket Alerts | 2 | p8-008 (JWT leak), p8-009 (no origin) |
| DFD-5: Stripe Webhook | 2 | p8-006 (type coercion), p8-007 (replay) |
| CFD-1: Auth Flow | 2 | p8-008 (JWT leak), p8-009 (no origin) |
| CFD-2: Authz Flow | 0 | p4-001 rejected (low severity) |
| CFD-3: Rate Limiting | 1 | p8-004 (bypass) |
| DFD-6: Puppeteer Reports | 1 | p8-002 (SSRF) |
| CFD-1 (CORS) | 1 | p8-005 (env var trust) |
Attack Pattern Registry Updates
New Patterns Identified
| Pattern ID | Root Cause | Detection Signature | Severity |
|---|---|---|---|
| AP-001 | Stored XSS via innerHTML + unsanitized markdown | Grep: innerHTML=.*contentHtml + contentToHtml with raw concatenation |
HIGH |
| AP-002 | Puppeteer SSRF via --no-sandbox + page.setContent() | Grep: puppeteer.launch.*--no-sandbox + page.setContent |
MEDIUM |
| AP-003 | Open redirect via unvalidated return URL | Grep: return_url.*returnUrl + url() validator only |
MEDIUM |
| AP-004 | Rate limit bypass via incomplete sensitive path list | Grep: path.includes.*sensitivePaths |
MEDIUM |
| AP-005 | CORS origin from unvalidated env var | Grep: process.env.APP_URL.*allowedOrigins |
MEDIUM |
| AP-006 | Webhook type coercion via chained as unknown as |
Grep: as unknown as Record<string, unknown> in billing |
MEDIUM |
| AP-007 | Webhook replay via missing event ID deduplication | Grep: handleWebhookEvent without event.id check |
MEDIUM |
| AP-008 | JWT in WebSocket query parameter | Grep: searchParams.get.*token in websocket handler |
MEDIUM |
| AP-009 | WebSocket no Origin validation | Grep: verifyClient missing in WebSocketServer config |
MEDIUM |
| AP-010 | Unbounded input → resource exhaustion | Grep: minLength(1) without maxLength in audio schemas |
MEDIUM |
| AP-011 | Vulnerable dependency version (superjson) | Grep: superjson.*\^2.2.1 in package.json |
MEDIUM |
Variant Candidates (Not Promoted)
| Candidate | Reason |
|---|---|
| p4-005 (path traversal audio) | FALSE POSITIVE — userId from ctx.user.id |
| p4-001 (unvalidated role) | LOW severity — no current exploit |
| p4-012 (admin SQL pattern) | LOW severity — latent risk only |
Key Observations
-
No remotely triggerable CRITICAL findings — All valid findings require some precondition (auth, admin access, log access, or env var injection). The highest severity is HIGH (stored XSS), which requires admin access for payload creation.
-
WebSocket authentication is the weakest link — Two findings (p8-008, p8-009) show that the WebSocket server lacks both Origin validation and uses JWT in query parameters. Together, these create a complete authentication bypass chain.
-
Stripe webhook handler has two independent issues — Type coercion (p8-006) and missing idempotency (p8-007) create a combined risk: a replayed forged webhook event can corrupt subscription data.
-
Input validation gaps are systematic — Multiple findings (p8-004, p8-009, p8-010) point to a pattern of missing maximum-length constraints and incomplete validation in valibot schemas.
-
No active SQL injection found — Despite the CVE-2026-39356 surface, no actively exploitable SQL injection was found in the current codebase. The
sql<>tag usage in admin.ts is safe.
Chamber Closure
Findings written: 11
Patterns added to registry: 11
Variant candidates: 3
Chamber closed: 2026-05-28T13:00:00Z