144 lines
7.2 KiB
Markdown
144 lines
7.2 KiB
Markdown
# 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
|
|
|
|
1. **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.
|
|
|
|
2. **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.
|
|
|
|
3. **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.
|
|
|
|
4. **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.
|
|
|
|
5. **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
|