Files
Kordant/piolium/attack-surface/knowledge-base-report.md
2026-05-29 09:03:47 -04:00

1104 lines
54 KiB
Markdown

# Kordant — Attack Surface Knowledge Base (Phase 3)
> **Generated**: 2026-05-28
> **Phase**: L2 (Knowledge Base / Threat Model)
> **Target**: Kordant monorepo — SolidStart + tRPC + Drizzle ORM + native mobile apps
> **Commit**: `26d9f8b050969dfaa2c9dfb714a872160b7db382`
---
## Project Classification
| Field | Value |
|-------|-------|
| **Primary type** | Web application (SSR via SolidStart) |
| **Secondary types** | API server (tRPC), Background worker (BullMQ + Redis), Browser extension (Manifest V3), Native mobile apps (iOS/SwiftUI, Android/Jetpack Compose) |
| **Monorepo structure** | pnpm workspaces: `web/`, `browser-ext/`; plus sibling projects `iOS/`, `android/`, `honker/`, `scheduler/` |
| **Hosting** | Vercel (web), self-hosted Docker (scheduler, Redis) |
| **Primary language** | TypeScript/JavaScript |
| **Secondary languages** | Swift (iOS), Kotlin (Android), Rust (`honker/` — SQLite extension, not part of Kordant runtime) |
| **Framework** | SolidStart 2.0.0-alpha.2 (Nitro-based SSR), tRPC 10.45.4, Drizzle ORM 0.45.2 |
| **Database** | Turso/libSQL (SQLite, cloud-hosted) |
| **Cache/Queue** | Redis 7 (BullMQ job queue, rate limiting via sorted sets) |
| **Auth provider** | Clerk (OAuth + email/password via `clerk-solidjs`) |
| **Payments** | Stripe (Checkout Sessions, Billing Portal, Webhooks) |
| **External APIs** | HIBP, SecurityTrails, Censys, Shodan, Twilio, Resend, Firebase FCM, Sentry |
---
## Architecture Model
### Components
```
┌─────────────────────────────────────────────────────────────────────┐
│ CLIENTS │
│ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Web (SSR)│ │ iOS │ │ Android │ │ Browser Extension│ │
│ │SolidStart│ │SwiftUI │ │Compose │ │Manifest V3 │ │
│ └────┬─────┘ └────┬────┘ └────┬─────┘ └────────┬─────────┘ │
│ │ │ │ │ │
│ └──────────────┴─────────────┴─────────────────┘ │
│ │ tRPC (HTTP batch + WebSocket) │
└──────────────────────────┼───────────────────────────────────────────┘
┌──────────────────────────┼───────────────────────────────────────────┐
│ web/ (SolidStart) │
│ ┌───────────────────────┼──────────────────────────────────────┐ │
│ │ Frontend (SSR/CSR) │ Middleware Pipeline │ │
│ │ Routes: (webapp), │ 1. requestLogger │ │
│ │ (admin), (auth), │ 2. securityHeaders (CSP, HSTS, etc) │ │
│ │ landing pages │ 3. corsHeaders │ │
│ │ Components: UI, │ 4. clerkMiddleware (authn) │ │
│ │ layouts, widgets │ └── each tRPC procedure adds: │ │
│ └───────────────────────┤ - protectedProcedure (authz) │ │
│ ┌───────────────────────┤ - adminProcedure (role=admin) │ │
│ │ Backend (tRPC) │ - rateLimitedProcedure │ │
│ │ 16 routers: │ │ │
│ │ example, user, │ │ │
│ │ billing, notification│ │ │
│ │ darkwatch, voiceprint│ │ │
│ │ spamshield, hometitle│ │ │
│ │ removebrokers, │ │ │
│ │ correlation, │ │ │
│ │ reports, scheduler, │ │ │
│ │ extension, blog, │ │ │
│ │ admin │ │ │
│ └───────────────────────┼──────────────────────────────────────┘ │
│ ┌───────────────────────┼──────────────────────────────────────┐ │
│ │ Background Jobs │ WebSocket Server (ws@8.21.0) │ │
│ │ BullMQ + Redis │ Port 3001, JWT-auth via URL param │ │
│ │ Scheduler container │ Heartbeat + pong timeout │ │
│ └───────────────────────┼──────────────────────────────────────┘ │
│ ┌───────────────────────┼──────────────────────────────────────┐ │
│ │ Report Generator │ External API Clients │ │
│ │ Puppeteer (headless)│ HIBP, SecurityTrails, Censys, │ │
│ │ HTML→PDF │ Shodan, Twilio, Stripe, Resend, FCM │ │
│ └───────────────────────┼──────────────────────────────────────┘ │
└──────────────────────────┼───────────────────────────────────────────┘
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────┐ ┌──────────┐
│ Turso │ │ Redis │ │ External │
│ libSQL │ │ 7 │ │ APIs │
│ (SQLite) │ │(BullMQ, │ │ │
│ │ │ rate │ │ │
└─────────────┘ │ limit) │ └──────────┘
└─────────┘
```
### Service Domains
| Domain | tRPC Router | Trust Level | Key Data | External Dependencies |
|--------|-------------|-------------|----------|----------------------|
| **VoicePrint** | `voiceprint` | Protected (auth) | Voice enrollments, audio samples, analysis results | None (local ML) |
| **DarkWatch** | `darkwatch` | Protected (auth + tier) | Watchlist items, exposure records | HIBP, SecurityTrails, Censys, Shodan |
| **SpamShield** | `spamshield` | Protected (auth) | Spam classifications, phone numbers, SMS content | Twilio, ML engine |
| **HomeTitle** | `hometitle` | Protected (auth + tier) | Property records, deed changes | County deed APIs |
| **RemoveBrokers** | `removebrokers` | Protected (auth + tier) | Opt-out requests, broker data | Broker opt-out APIs |
| **Billing** | `billing` | Protected (auth) | Subscriptions, payments, invoices | Stripe |
| **Admin** | `admin` | Admin-only | Blog posts, user management, stats | Drizzle ORM |
| **Extension** | `extension` | Public (some) | Device linking, phishing reports | Drizzle ORM |
| **Reports** | `reports` | Protected (auth + tier) | Security reports, PDF generation | Puppeteer, Drizzle |
| **User** | `user` | Protected (auth) | User profile, preferences | Drizzle ORM |
| **Notifications** | `notification` | Protected (auth) | Push notifications, email | Firebase FCM, Resend |
| **Correlation** | `correlation` | Protected (auth) | Cross-domain alert correlation | Drizzle ORM |
| **Scheduler** | `scheduler` | Internal (worker) | Job scheduling, cron tasks | BullMQ, Drizzle |
---
## Trust Boundaries
| # | Boundary | Direction | Protocol | Auth | Encryption | Risk |
|---|----------|-----------|----------|------|------------|------|
| TB-1 | **Internet → Web (SolidStart)** | Client → Server | HTTPS/tRPC | Clerk session + JWT + API key | TLS | HIGH — tRPC procedures are the primary attack surface |
| TB-2 | **tRPC → Drizzle ORM** | App → DB | libSQL/Turso | JWT-validated user context | TLS (Turso) | CRITICAL — SQL injection via drizzle-orm CVE-2026-39356 |
| TB-3 | **tRPC → Stripe** | App → Stripe | HTTPS | Stripe secret key | TLS | HIGH — Webhook spoofing, payment manipulation |
| TB-4 | **tRPC → External APIs** | App → HIBP/Trails/Censys/Shodan | HTTPS | API keys | TLS | MEDIUM — API key leakage, SSRF via crafted URLs |
| TB-5 | **WebSocket → ws** | Client → WS Server | WSS (port 3001) | JWT in query param | TLS | HIGH — Memory disclosure (CVE-2026-45736), DoS (CVE-2024-37890) |
| TB-6 | **Browser Extension → tRPC** | Extension → Web | HTTPS | API key (stored in extension) | TLS | HIGH — superjson prototype pollution chain (CVE-2022-23631) |
| TB-7 | **tRPC → Redis** | App → Redis | TCP (internal) | None (network-isolated) | None | MEDIUM — BullMQ job injection, cache poisoning |
| TB-8 | **Puppeteer → File System** | App → Local FS | Local | None | N/A | HIGH — Path traversal, SSRF via file input |
| TB-9 | **tRPC → VoicePrint Storage** | App → Audio Files | Local FS | Protected procedure | N/A | MEDIUM — Audio file access, path traversal |
| TB-10 | **Scheduler → Redis** | Worker → Redis | TCP (internal) | None (network-isolated) | N/A | LOW — Internal worker communication |
### Role-Based Access
| Role | Access Level | Enforcement |
|------|-------------|-------------|
| **Anonymous** | Public procedures only (`extension.reportPhishing`, `extension.getAuthStatus`) | tRPC procedure type |
| **Authenticated User** | Protected procedures (all data scoped to `ctx.user.id`) | `isAuthed` middleware checks `ctx.user` |
| **Admin** | Admin procedures (`adminRouter`) + all user procedures | `isAdmin` middleware checks `ctx.user.role === "admin"` |
| **API Key** | Limited procedures (extension API key path) | `ctx.apiKey` fallback in `createTRPCContext` |
---
## Data-Flow Slices (DFD)
### DFD-1: tRPC → Drizzle ORM (SQL Injection Vector)
```mermaid
flowchart LR
A[Client Input] -->|tRPC procedure| B[Input Validation\nvalibot schema]
B --> C{Schema pass?}
C -->|No| D[TRPCError thrown]
C -->|Yes| E[Service Layer]
E --> F[Drizzle ORM Query]
F --> G[Turso/SQLite]
style A fill:#ff6b6b
style G fill:#4ecdc4
style F fill:#ffe66d
```
**Flow**: User input → valibot validation → tRPC procedure → Drizzle ORM → Turso SQLite
**Key risk**: CVE-2026-39356 — SQL injection via improperly escaped SQL identifiers in drizzle-orm 0.45.2. If any tRPC procedure passes user input into column/table names (via `sql` tag or dynamic column references), injection is possible.
**Sinks to check**: `sql<>` template tag usage, dynamic column references, `inArray` with user-controlled values, raw Drizzle query builders.
### DFD-2: VoicePrint Audio Analysis Pipeline
```mermaid
flowchart LR
A[Base64 Audio Data] -->|tRPC analyzeAudio| B[decode Base64 → Buffer]
B --> C[saveAudio\nwrite to disk]
C --> D[preprocessAudio\nfeature extraction]
D --> E[detectSynthetic\nML inference]
E --> F[matchVoice\nembedding comparison]
F --> G[store analysis\nDrizzle ORM]
G --> H[broadcast alert\nWebSocket]
style A fill:#ff6b6b
style C fill:#ffe66d
style E fill:#ffe66d
```
**Flow**: Base64 audio → decode → save to disk → ML preprocessing → synthetic detection → voice matching → store results → WebSocket alert
**Key risks**:
- Unbounded audio size (DoS via large uploads)
- Base64 decode buffer overflow potential
- ML engine input validation
- File path construction for audio storage
### DFD-3: Browser Extension → tRPC (Prototype Pollution Chain)
```mermaid
flowchart LR
A[Extension Content Script] -->|tRPC call| B[superjson serialization]
B -->|HTTP POST| C[tRPC endpoint]
C --> D[superjson deserialization]
D --> E[Prototype Pollution?]
E -->|Yes| F[RCE via __proto__ overwrite]
D --> G[valibot validation]
G --> H[Service layer]
style A fill:#ff6b6b
style D fill:#ffe66d
style F fill:#ff0000
```
**Flow**: Extension → superjson serialize → HTTP → tRPC → superjson deserialize → valibot → service
**Key risk**: superjson CVE-2022-23631 (prototype pollution → RCE, CVSS 10.0). The browser extension uses superjson for tRPC serialization. If the tRPC server deserializes untrusted superjson data, prototype pollution is possible.
### DFD-4: WebSocket Real-Time Alerts
```mermaid
flowchart LR
A[Service Layer] -->|broadcastToUser| B[WebSocket Server]
B --> C{User connected?}
C -->|Yes| D[ws.send JSON alert]
C -->|No| E[Drop silently]
D --> F[Client receives alert]
G[Client connect] -->|JWT in ?token param| H[authenticateConnection]
H -->|Valid| I[addSocket to userSockets]
H -->|Invalid| J[close 4001]
style A fill:#4ecdc4
style B fill:#ffe66d
style G fill:#ff6b6b
```
**Flow**: Service → broadcastToUser → WebSocket server → user socket → client
**Key risks**:
- JWT in URL query parameter (log exposure, referer leakage)
- No message size limit
- No rate limiting on WebSocket messages
- Heartbeat bypass potential
### DFD-5: Stripe Webhook Processing
```mermaid
flowchart LR
A[Stripe → POST /api/webhook] --> B[signature verification]
B -->|Valid| C[handleWebhookEvent]
B -->|Invalid| D[400 rejected]
C --> E{event.type}
E -->|checkout.session.completed| F[create subscription in DB]
E -->|invoice.paid| G[update status to active]
E -->|invoice.payment_failed| H[update status to past_due]
E -->|customer.subscription.updated| I[update tier/status]
E -->|customer.subscription.deleted| J[mark as canceled]
style A fill:#ff6b6b
style B fill:#ffe66d
style F fill:#4ecdc4
```
**Key risk**: Webhook replay attacks if signature verification is weak. Type coercion in `event.data.object as unknown as Record<string, unknown>` could bypass type checks.
### DFD-6: Report Generation (Puppeteer)
```mermaid
flowchart LR
A[generateReport mutation] --> B[compileData from DB]
B --> C[renderHTML template]
C --> D[generatePDF via Puppeteer]
D --> E[uploadPDF to storage]
E --> F[update report status]
style A fill:#ff6b6b
style D fill:#ffe66d
```
**Key risk**: Puppeteer SSRF if HTML template contains user-controlled URLs. Path traversal in report filename construction.
---
## Control-Flow Slices (CFD)
### CFD-1: Authentication Flow
```mermaid
flowchart TD
A[Request arrives] --> B[Clerk middleware]
B --> C{Valid Clerk session?}
C -->|Yes| D[set ctx.user]
C -->|No| E[Check Bearer token]
E --> F{Valid JWT?}
F -->|Yes| G[set ctx.user from JWT]
F -->|No| H[Check x-api-key]
H --> I[set ctx.apiKey]
style A fill:#ff6b6b
style B fill:#ffe66d
style D fill:#4ecdc4
style G fill:#4ecdc4
```
**Auth chain**: Clerk session cookie → Bearer JWT → API key. Each level is a fallback, not a parallel auth. The `x-api-key` is the weakest link — it's checked only if both session and JWT fail.
### CFD-2: Authorization Flow
```mermaid
flowchart TD
A[tRPC procedure] --> B{Procedure type?}
B -->|publicProcedure| C[No auth check]
B -->|protectedProcedure| D{ctx.user exists?}
B -->|adminProcedure| E{ctx.user && role=admin?}
D -->|No| F[401 UNAUTHORIZED]
D -->|Yes| G[proceed]
E -->|No| H[403 FORBIDDEN]
E -->|Yes| G
C --> I[proceed]
style A fill:#ff6b6b
style F fill:#ff6b6b
style H fill:#ff6b6b
style G fill:#4ecdc4
```
**Key observation**: Admin check is `ctx.user.role !== "admin"` — this is a string comparison, not an enum. Any user with `role: "admin"` in the DB gets admin access. No additional checks (IP allowlist, MFA, audit logging).
### CFD-3: Rate Limiting Flow
```mermaid
flowchart TD
A[Procedure middleware] --> B{Path in sensitive list?}
B -->|Yes| C[Tier: sensitive\n3/hr]
B -->|No| D{ctx.user?}
D -->|Yes| E{role=admin?}
D -->|No| F[Tier: public\n5/min]
E -->|Yes| G[Tier: admin\n50/min]
E -->|No| H[Tier: authenticated\n100/min]
C --> I[Redis sorted set check]
G --> I
H --> I
F --> I
I --> J{Allowed?}
J -->|No| K[429 TOO_MANY_REQUESTS]
J -->|Yes| L[proceed]
style A fill:#ff6b6b
style K fill:#ff6b6b
style L fill:#4ecdc4
```
**Key observation**: Sensitive paths are hardcoded: `["login", "signup", "forgotPassword", "resetPassword"]`. Other procedures don't get sensitive-tier limits. The `websocket` tier (1/minute) is defined but not applied to WebSocket connections.
---
## Framework Contracts and Hidden Control Channels
### SolidStart / Nitro Middleware Pipeline
**File**: `web/src/middleware.ts`
The middleware chain is:
1. `requestLogger` — logs all requests
2. `securityHeaders` — sets HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
3. `corsHeaders` — origin validation + preflight handling
4. `clerkMiddleware` — Clerk authentication
**Critical contract**: Middleware runs before all routes AND before tRPC handlers. If any middleware fails silently, the next middleware or handler proceeds without the expected security headers or auth context.
### CSP Header Analysis
```
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' *.clerk.dev *.clerk.com *.stripe.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: *.gravatar.com *.clerk.dev *.clerk.com;
connect-src 'self' *.clerk.dev *.clerk.com *.stripe.com *.sentry.io ws: wss:;
frame-src 'self' *.stripe.com;
font-src 'self' data:;
object-src 'none';
base-uri 'self';
form-action 'self' *.stripe.com
```
**Contract implications**:
- `'unsafe-eval'` in script-src — weakens CSP, enables potential XSS exploitation
- `'unsafe-inline'` in script-src and style-src — allows inline scripts/styles
- `ws:` and `wss:` in connect-src — allows WebSocket to any host
- `*.clerk.dev` and `*.clerk.com` — trust Clerk's entire domain for script execution
### tRPC Procedure Contracts
**File**: `web/src/server/api/utils.ts`
Three procedure types with different contract guarantees:
- `publicProcedure` — No auth, no rate limiting. Used by `extensionRouter` (device linking, phishing reports).
- `protectedProcedure` — Auth required (`ctx.user` must exist). No role check.
- `adminProcedure` — Auth required + `role === "admin"`. Used by `adminRouter`.
- `rateLimitedProcedure` — Rate limiting middleware applied.
**Hidden contract**: The `path` parameter in the rate limiter middleware is used to detect sensitive paths. If a procedure's path contains `"login"`, `"signup"`, `"forgotPassword"`, or `"resetPassword"`, it gets the sensitive tier (3/hour). This is a string-match heuristic, not an exact routing match.
### CORS Contract
**File**: `web/src/middleware.ts`
```typescript
const allowedOrigins = [
"http://localhost:3000",
"http://localhost:3001",
process.env.APP_URL,
].filter(Boolean);
```
**Contract implication**: `APP_URL` from environment is trusted as an allowed CORS origin. If an attacker can control `APP_URL` (e.g., via environment variable injection), they can set an arbitrary allowed origin. The origin is checked via exact string match — no wildcard or prefix matching.
### WebSocket Auth Contract
**File**: `web/src/server/websocket.ts`
- JWT passed as `?token=` query parameter — visible in server logs, browser history, proxy logs
- No `Origin` header validation on WebSocket upgrade
- No `Sec-WebSocket-Protocol` validation
- No message size limit
- Heartbeat uses 30s interval + 10s pong timeout — potential for slow-loris DoS
### Clerk Middleware Contract
**File**: `web/src/middleware.ts`
```typescript
clerkMiddleware({
publishableKey: process.env.VITE_CLERK_PUBLISHABLE_KEY,
secretKey: process.env.CLERK_SECRET_KEY,
})
```
**Contract implication**: Clerk handles the actual authentication. The middleware sets `ctx.user` based on Clerk session. If Clerk's session validation is bypassed (e.g., via clock skew, expired session reuse), auth is compromised.
### Vite Dev Server Contract
**File**: `web/vite.config.ts`
```typescript
plugins: [solidStart(), tailwindcss(), nitro()]
```
**Contract implication**: Vite dev server runs on port 3000 in development. The `server.fs.deny` configuration is not explicitly set, meaning the default applies. Given 14+ CVEs for `server.fs.deny` bypass, the dev server is a significant risk if exposed.
### Dockerfile Contract
**File**: `web/Dockerfile`
```dockerfile
USER appuser
```
**Contract implication**: Runs as non-root user `appuser` — good security practice. However, the container has `curl` installed for health checks, which could be used for SSRF if the application is compromised.
### Stripe Contract
**File**: `web/src/server/services/billing.service.ts`
```typescript
const obj = event.data.object as unknown as Record<string, unknown>;
```
**Contract implication**: Stripe webhook events are deserialized via unsafe type coercion. The `as unknown as Record<string, unknown>` bypasses TypeScript type checking. If Stripe's event format changes, field access could fail silently or produce unexpected results.
---
## Threat Model
### Assets
| Asset | Sensitivity | Storage | Threat |
|-------|-------------|---------|--------|
| User credentials (passwords, emails) | HIGH | Turso (bcrypt hash) | Theft, brute force |
| JWT signing secret (`JWT_SECRET`) | CRITICAL | Environment variable | Token forgery |
| Clerk secret key | CRITICAL | Environment variable | Auth bypass |
| Stripe secret key | CRITICAL | Environment variable | Payment manipulation |
| User PII (names, emails, phone numbers, SSNs) | HIGH | Turso | Data exfiltration |
| Voice enrollment audio | HIGH | Local filesystem | Privacy breach |
| Voice analysis results | MEDIUM | Turso | Identity spoofing |
| Watchlist items (emails, phones, SSNs) | HIGH | Turso | Privacy breach |
| Dark web exposure data | HIGH | Turso | Data exfiltration |
| API keys (HIBP, SecurityTrails, Censys, Shodan, Twilio) | HIGH | Environment variables | Cost abuse, data access |
| Session tokens | MEDIUM | Turso | Session hijacking |
| Browser extension API key | MEDIUM | Extension storage | Unauthorized API access |
### Threat Actors
| Actor | Capability | Motivation | Likelihood |
|-------|-----------|------------|------------|
| **External attacker** | Internet access, can send crafted HTTP/WebSocket requests | Data theft, account takeover | High |
| **Compromised browser extension** | Can make tRPC calls with stored API key | Data exfiltration from linked account | Medium |
| **Insider (non-admin)** | Authenticated user access to own data | Data exfiltration, privilege escalation | Low-Medium |
| **Insider (admin)** | Full admin access | Data manipulation, privilege escalation | Low |
| **Supply chain attacker** | Compromised npm package | Code injection, credential theft | Medium |
| **Clerk infrastructure attacker** | Compromised Clerk auth service | Bypass all auth | Low |
### Attack Scenarios
#### AS-1: SQL Injection via Drizzle ORM (CRITICAL)
**Precondition**: tRPC procedure passes user input into Drizzle query identifiers or uses `sql<>` tag unsafely.
**Attack**: Attacker sends crafted input to a tRPC procedure that interpolates values into SQL column/table names.
**Impact**: Full database read/write/delete. All user data exposed.
**Likelihood**: Medium-High (CVE-2026-39356 is actively exploitable in drizzle-orm 0.45.2)
**Mitigation**: Audit all `sql<>` tag usage, avoid dynamic column names, review CVE-2026-39356 patch status.
#### AS-2: Prototype Pollution via superjson (CRITICAL)
**Precondition**: Browser extension uses superjson for tRPC serialization; tRPC server deserializes superjson data.
**Attack**: Attacker crafts malicious superjson payload with `__proto__` pollution.
**Impact**: RCE via prototype chain manipulation.
**Likelihood**: Medium (superjson CVE-2022-23631 is CVSS 10.0, but requires superjson serialization path)
**Mitigation**: Audit superjson usage, consider migrating to safe serialization, apply patches.
#### AS-3: WebSocket Authentication Bypass (HIGH)
**Precondition**: WebSocket JWT passed in query parameter.
**Attack**: Attacker captures JWT from logs/referer and replays it for WebSocket access.
**Impact**: Receive real-time alerts for any user whose JWT is leaked.
**Likelihood**: Medium (JWT in query params is logged by most reverse proxies)
**Mitigation**: Move JWT to `Sec-WebSocket-Protocol` header or `Authorization` header. Implement `Origin` validation.
#### AS-4: Admin Privilege Escalation (HIGH)
**Precondition**: Attacker can modify their own `users.role` in the database (via SQL injection or direct DB access).
**Attack**: Set `role = "admin"` in the `users` table.
**Impact**: Full admin panel access — blog management, user role changes, stats access.
**Likelihood**: Low (requires DB access first, but chained with AS-1 becomes critical)
**Mitigation**: Add role change audit logging, restrict role changes to system-only procedures.
#### AS-5: Rate Limit Bypass (MEDIUM)
**Precondition**: Rate limiting uses Redis sorted sets with `identifier` based on `ctx.user.id` or `ctx.apiKey`.
**Attack**: Attacker rotates API keys or creates multiple accounts to bypass per-identifier limits.
**Impact**: Brute force attacks, resource exhaustion.
**Likelihood**: Medium
**Mitigation**: Add IP-based rate limiting as a secondary dimension.
#### AS-6: Stripe Webhook Replay (MEDIUM)
**Precondition**: Webhook handler processes events without idempotency checks.
**Attack**: Replay `checkout.session.completed` webhook to create duplicate subscriptions.
**Impact**: Billing manipulation, subscription abuse.
**Likelihood**: Low-Medium
**Mitigation**: Add event ID deduplication in the webhook handler.
#### AS-7: XSS via SolidJS JSX (HIGH)
**Precondition**: User-controlled data rendered in SolidJS JSX components without proper escaping.
**Attack**: Inject malicious content via blog post titles, user names, or other user-controlled fields.
**Impact**: Account takeover via cookie theft, phishing.
**Likelihood**: Medium (CVE-2025-27109 affects JSX fragment rendering)
**Mitigation**: Audit all JSX rendering of user data, ensure CSP is effective.
#### AS-8: Path Traversal via Puppeteer (HIGH)
**Precondition**: Report generation uses Puppeteer to render HTML to PDF.
**Attack**: Craft HTML with `file://` URLs or relative paths to read local files.
**Impact**: Read sensitive files (`.env`, source code, database credentials).
**Likelihood**: Low (requires auth + controlled report parameters)
**Mitigation**: Sandbox Puppeteer, disable file access, validate all URLs in templates.
---
## Domain Attack Research
### Mode A — Library-as-Target: No applicable (project is not a library/plugin/protocol)
### Mode B — Library-as-Consumer: Security-Sensitive Dependencies
#### B-1: drizzle-orm 0.45.2 — SQL Injection
**CVE-2026-39356** (CVSS 7.5): SQL injection via improperly escaped SQL identifiers.
**Research findings**:
- Drizzle's `sql<>` template tag and dynamic column references are the primary injection vectors
- `inArray` with user-controlled arrays can lead to identifier injection
- `sql` helper with string interpolation bypasses parameterization
**Custom SAST targets**:
- All `sql<>` template tag usages in `web/src/server/services/**/*.ts`
- All `inArray` calls with non-literal second arguments
- All `groupBy`/`orderBy` calls with non-literal column references
- All dynamic table name construction
**Manual review checklist**:
- [ ] Search for `sql\`` or `sql\`${` patterns
- [ ] Search for `inArray` with variable arguments
- [ ] Review all Drizzle `where` clauses for string interpolation
- [ ] Check `groupBy` and `orderBy` for dynamic columns
- [ ] Verify no raw SQL strings are constructed from user input
#### B-2: @trpc/server 10.45.4 — Prototype Pollution + WebSocket DoS
**CVE-2025-68130** (CVSS HIGH): Prototype pollution in `experimental_nextAppDirCaller`
**CVE-2025-43855** (CVSS HIGH): WebSocket DoS
**Research findings**:
- `experimental_nextAppDirCaller` is a Next.js-specific adapter feature — likely not used in SolidStart
- WebSocket DoS affects the `ws` library's message framing, not tRPC itself
- The tRPC batch endpoint can be used for amplification DoS
**Custom SAST targets**:
- All WebSocket message handlers for size limits
- All tRPC batch request handlers for batch size limits
- Any use of `experimental_nextAppDirCaller` (should be none in SolidStart)
#### B-3: valibot 0.29.0 — ReDoS
**CVE-2025-66020** (CVSS HIGH): ReDoS in `EMOJI_REGEX`
**Research findings**:
- valibot's built-in emoji validation regex is vulnerable to ReDoS
- Any tRPC procedure that validates user input containing emoji characters is potentially vulnerable
- The regex is used in `string()` validation with emoji-related constraints
**Custom SAST targets**:
- All valibot `string()` schemas with emoji constraints
- All input validation schemas in `web/src/server/api/schemas/**/*.ts`
- All user-facing string input paths
**Manual review checklist**:
- [ ] Review all valibot schemas for `EMOJI_REGEX` usage
- [ ] Check if valibot version has the ReDoS fix applied
- [ ] Test with crafted emoji sequences
#### B-4: superjson 2.2.1/2.2.6 — Prototype Pollution → RCE
**CVE-2022-23631** (CVSS 10.0): Prototype pollution via `__proto__` in JSON deserialization
**Research findings**:
- superjson is used by the browser extension for tRPC serialization
- The tRPC server may receive superjson-serialized data from the extension
- CVE-2022-23631 affects superjson < 2.2.6; the browser extension uses 2.2.1 (vulnerable)
- The web app's package.json does NOT list superjson as a dependency — the server uses native JSON
**Custom SAST targets**:
- Browser extension superjson usage (`browser-ext/src/lib/api-client.ts`)
- Any server-side superjson import (should be none — web app uses native JSON)
**Manual review checklist**:
- [ ] Verify server does NOT use superjson for deserialization
- [ ] Audit browser extension superjson serialization of user data
- [ ] Check if extension sends any data containing `__proto__` keys
#### B-5: jose 5.10.0 — Resource Exhaustion
**CVE-2024-28176** (CVSS 5.3): Resource exhaustion via crafted JWE with compressed plaintext
**Research findings**:
- jose is used for JWT signing/verification (`web/src/server/auth/jwt.ts`)
- The project uses HS256 (symmetric), not JWE (encrypted JWT)
- CVE-2024-28176 affects JWE decryption with compressed payloads
- Since the project only uses `SignJWT` and `jwtVerify` with HS256, this CVE is NOT directly applicable
**Applicability**: LOW — project uses HS256 JWTs, not JWE.
#### B-6: ws 8.21.0 — Memory Disclosure + DoS
**CVE-2026-45736** (CVSS 5.3): Uninitialized memory disclosure
**CVE-2024-37890** (CVSS 7.5): DoS via many HTTP headers
**Research findings**:
- WebSocket server uses `ws@8.21.0` on port 3001
- Memory disclosure (CVE-2026-45736) affects binary frame handling
- DoS (CVE-2024-37890) affects request header parsing during WebSocket upgrade
- The WebSocket server validates JWT from query params during upgrade
**Custom SAST targets**:
- WebSocket server configuration (max payload size)
- WebSocket upgrade handler for header count limits
- Binary frame handling in the WebSocket server
#### B-7: vite 6.4.2/7.3.3 — Path Traversal (14+ CVEs)
**CVE lineage**: 14+ CVEs for `server.fs.deny` bypass
**Research findings**:
- Vite dev server is the primary concern — production build should not expose the dev server
- The project deploys via `vite build``vite start` (Nitro server), not the dev server
- Dockerfile uses `node .output/server/index.mjs` which is the Nitro production server
- The `server.fs.deny` CVEs affect the dev server (`vite dev`) and some Nitro file-serving paths
**Applicability**: LOW for production, MEDIUM for development environments
**Custom SAST targets**:
- Vite config for `server.fs.deny` settings
- Any custom file-serving middleware in the Nitro server
- Dockerfile for dev server exposure
### Mode C — Domain-Specific Attack Research
#### C-1: ML/AI Integration — VoicePrint Synthetic Detection
**Domain**: Voice biometrics, synthetic voice detection, ML model inference
**Research findings**:
- VoicePrint service processes audio files through ML pipeline (preprocessing → synthetic detection → voice matching)
- Audio files are saved to local filesystem as base64-decoded buffers
- No input validation on audio file format, size, or content before ML processing
- `audioBase64` parameter accepts arbitrary base64 data — could be crafted audio, non-audio data, or extremely large payloads
- No rate limiting on audio analysis (protected procedure only, not rate-limited)
**Attack vectors**:
1. **Resource exhaustion**: Upload extremely large base64 audio payloads to exhaust memory during decode + ML processing
2. **Model poisoning**: Craft audio that produces specific ML outputs (adversarial audio)
3. **Privacy leak**: Audio files stored on disk may be readable by other processes
4. **Echo location**: Use voice analysis endpoints to enumerate voice enrollments
**Custom SAST targets**:
- Audio buffer size limits in `voiceprint.service.ts`
- File path construction in `voiceprint/storage.ts`
- ML model input validation
#### C-2: External API Integration — DarkWatch Multi-Source Scanning
**Domain**: OSINT aggregation, external API orchestration, rate limiting, circuit breaking
**Research findings**:
- DarkWatch scans HIBP, SecurityTrails, Censys, Shodan, and dark web forums
- Each scan can trigger 5+ parallel API calls
- Circuit breaker pattern is implemented (5 failures → 60s timeout)
- No rate limiting between scans (only per-subscription counts via tier limits)
- No response validation beyond HTTP status codes
- `fetchWithCircuit` uses `AbortSignal.timeout(10_000)` — 10s timeout per request
**Attack vectors**:
1. **API cost exhaustion**: Trigger expensive scans via watchlist items with many values
2. **SSRF via URL manipulation**: If scan URLs are constructed from user input, SSRF is possible
3. **Response injection**: Unvalidated responses from external APIs stored in database
4. **Circuit breaker manipulation**: Rapid failures to keep circuits open (denial of service for legitimate scans)
**Custom SAST targets**:
- URL construction in `darkwatch/scan.engine.ts`
- Response parsing and storage in `darkwatch/alert.pipeline.ts`
- Watchlist item value validation (email, phone, SSN, address, domain)
#### C-3: Payment Processing — Stripe Integration
**Domain**: Payment processing, subscription management, webhook handling
**Research findings**:
- Stripe Checkout Sessions use embedded UI mode (`ui_mode: "embedded_page"`)
- Webhook handler uses unsafe type coercion (`as unknown as Record<string, unknown>`)
- No webhook event ID deduplication
- `returnUrl` in checkout session is user-controlled (from `CreateCheckoutSessionSchema`)
- Stripe API version pinned to `2026-04-22.dahlia`
**Attack vectors**:
1. **Webhook replay**: Replay checkout events to create duplicate subscriptions
2. **Return URL redirect**: If `returnUrl` is not validated, open redirect post-payment
3. **Type coercion bypass**: Malformed webhook events could bypass event type checks
4. **Price ID manipulation**: `mapStripeProductToTier` uses string comparison — if price ID format changes, tier assignment could be wrong
**Custom SAST targets**:
- Webhook handler event type switch statement
- Return URL validation
- Price ID to tier mapping
#### C-4: Real-Time Communication — WebSocket Alerts
**Domain**: WebSocket, real-time messaging, alert distribution
**Research findings**:
- WebSocket server on port 3001, JWT-authenticated via query parameter
- No message size limit
- No rate limiting on incoming messages
- Heartbeat: 30s interval, 10s pong timeout
- `broadcastToUser` sends to all connected sockets for a user
- No message validation — any JSON is accepted
**Attack vectors**:
1. **Memory exhaustion**: Connect many sockets per user (no connection limit per user)
2. **Slow-loris DoS**: Send valid but slow messages to keep connections alive
3. **Alert flooding**: If a service calls `broadcastToUser` in a loop, flood all connected clients
4. **JWT leakage**: JWT in query params logged by proxies, load balancers, access logs
**Custom SAST targets**:
- WebSocket connection limit per user
- Message size validation
- Broadcast rate limiting
#### C-5: File System — Puppeteer Report Generation + Audio Storage
**Domain**: Headless browser, file I/O, path traversal
**Research findings**:
- Puppeteer used for HTML-to-PDF report generation
- Audio files stored on local filesystem (path based on userId + audio hash)
- Report templates directory: `join(__dirname, "templates")`
- Reports output directory: `join(process.cwd(), "reports")`
- No explicit sandboxing for Puppeteer
- No file size limits on audio uploads
**Attack vectors**:
1. **SSRF via Puppeteer**: If HTML template contains `file://` or `http://` URLs from user control
2. **Path traversal**: If userId or report filename is user-controlled and not sanitized
3. **Disk exhaustion**: Large audio uploads fill disk, affecting report generation
4. **Template injection**: If HTML templates are user-modifiable
**Custom SAST targets**:
- Puppeteer launch configuration (sandbox, no-sandbox flags)
- File path construction in reports generator
- Audio file storage paths
---
## Phase 4 CodeQL Extraction Targets
### DFD-1: tRPC → Drizzle ORM (SQL Injection)
| Source | Sink | Language | File Pattern |
|--------|------|----------|-------------|
| `LocalUserInput` (tRPC procedure input) | `sql-execution` (Drizzle ORM queries) | TypeScript | `web/src/server/api/routers/**/*.ts` |
| `LocalUserInput``RemoteFlowSource` (valibot validated) | `sql-execution` | TypeScript | `web/src/server/services/**/*.ts` |
| `EnvironmentVariable` (API keys) | `http-request` (external API calls) | TypeScript | `web/src/server/services/darkwatch/**/*.ts` |
**Expected sink kinds**: `sql-execution`, `http-request`, `command-execution`
### DFD-2: WebSocket (Authentication + Message Handling)
| Source | Sink | Language | File Pattern |
|--------|------|----------|-------------|
| `RemoteFlowSource` (WebSocket frames) | `code-execution` (message handlers) | TypeScript | `web/src/server/websocket.ts` |
| `LocalUserInput` (JWT query param) | `LocalFlowSource` (JWT verification) | TypeScript | `web/src/server/websocket.ts` |
**Expected sink kinds**: `code-execution`, `deserialization`
### DFD-3: Puppeteer Report Generation
| Source | Sink | Language | File Pattern |
|--------|------|----------|-------------|
| `LocalUserInput` (report parameters) | `http-request` (Puppeteer navigation) | TypeScript | `web/src/server/services/reports/generator.ts` |
| `LocalUserInput` | `file-access` (PDF output, audio storage) | TypeScript | `web/src/server/services/reports/**/*.ts`, `web/src/server/services/voiceprint/storage.ts` |
**Expected sink kinds**: `http-request`, `file-access`
### DFD-4: Stripe Webhook Processing
| Source | Sink | Language | File Pattern |
|--------|------|----------|-------------|
| `RemoteFlowSource` (webhook event) | `sql-execution` (subscription updates) | TypeScript | `web/src/server/services/billing.service.ts` |
| `RemoteFlowSource` | `http-request` (Stripe API calls) | TypeScript | `web/src/server/services/billing.service.ts` |
**Expected sink kinds**: `sql-execution`, `http-request`
### DFD-5: Browser Extension → tRPC (Prototype Pollution)
| Source | Sink | Language | File Pattern |
|--------|------|----------|-------------|
| `RemoteFlowSource` (superjson deserialized data) | `code-execution` (prototype chain manipulation) | TypeScript | `browser-ext/src/lib/api-client.ts` |
**Expected sink kinds**: `deserialization`, `code-execution`
---
## Spec Gap Candidates
No formal specs or RFCs were identified in the codebase or documentation. The project implements the following protocols/standards informally:
| Standard | Implementation | Gap Risk |
|----------|---------------|----------|
| **tRPC protocol** | @trpc/server 10.45.4 | Low — well-defined protocol, version pinned |
| **Clerk auth protocol** | clerk-solidjs 2.0.10 | Low — managed auth provider |
| **Stripe API** | stripe 22.1.1 (API version 2026-04-22) | Medium — webhook handling uses unsafe type coercion |
| **JWT (RFC 7519)** | jose 5.10.0 (HS256) | Low — standard JWT, but JWT in WebSocket query params is non-standard |
| **WebSocket (RFC 6455)** | ws 8.21.0 | Medium — no Origin validation, no message size limits |
| **CORS (W3C)** | Custom middleware | Low — origin whitelist is correct but APP_URL env var is trusted |
---
## Coverage Gaps
### Not Assessed in This Phase
| Area | Reason | Impact on Later Phases |
|------|--------|----------------------|
| **iOS native app** | Codebase not explored in depth (SwiftUI) | Phase 5-7: certificate pinning, keychain storage, root detection |
| **Android native app** | Codebase not explored in depth (Kotlin/Compose) | Phase 5-7: certificate pinning, keystore storage, root detection |
| **Honker SQLite extension** | Separate Rust project, not part of Kordant runtime | N/A — not in scope |
| **Docker image base** | `node:22-alpine` — no CVE scan of base image | Phase 6: supply chain risk |
| **CI/CD pipelines** | No `.github/workflows/` with AI agents found | Phase 7: supply chain, agentic attack surface |
| **DNS/DHCP configuration** | Not in codebase | N/A |
| **Infrastructure (Vercel config)** | Vercel deployment config not in repo | Phase 6: environment isolation |
| **TLS configuration** | Not in codebase (handled by Vercel/proxy) | N/A |
| **Backup procedures** | Referenced in `docs/BACKUPS.md` but not reviewed | Phase 6: data integrity |
### Known False-Positive Sources
1. **Vite `server.fs.deny` bypasses** — These affect the **dev server** only (`vite dev`). The production deployment uses `vite build` + `vite start` (Nitro), which does not use the dev server's file serving. Findings related to `server.fs.deny` bypass in production should be evaluated against whether the dev server is exposed.
2. **superjson prototype pollution** — The web server (`web/`) does NOT use superjson as a dependency. Only the browser extension uses superjson for client-side serialization. The tRPC server uses native JSON serialization. The prototype pollution risk is confined to the browser extension's local data handling, not server-side deserialization.
3. **jose JWE resource exhaustion** — The project uses HS256 JWTs (symmetric, no encryption), not JWE (encrypted JWTs). CVE-2024-28176 affects JWE decryption only and is not applicable.
4. **@trpc/server `experimental_nextAppDirCaller` prototype pollution** — This is a Next.js-specific adapter feature. The project uses SolidStart, which does not use this adapter. Not applicable.
---
## Recent Security Context
From internal git history (commit `26d9f8b`), the following security-related fixes were made (referenced by internal ticket IDs):
| Ticket | Description |
|--------|-------------|
| FRE-4572 | VoicePrint auth bypass fix |
| FRE-4807 | P1 security findings remediation |
| FRE-5003 | JWT security hardening |
| FRE-4498 | Auth bypass patch |
| FRE-4500 | Rate limiting improvements |
| FRE-4612 | CORS tightening |
| FRE-4701 | Session token rotation |
| FRE-4850 | Webhook signature verification |
These represent **real security vulnerabilities** in the project's own codebase that were fixed internally. Their details are not publicly documented in CVE/GHSA format.
---
## Static Analysis Summary
**Phase**: L3 (SAST — Greppable Fallback)
**Date**: 2026-05-28
**Tools**: Built-in candidate scanner + targeted grep/read analysis
**CodeQL**: Not available (not on PATH)
**Semgrep**: Not available (not on PATH)
### Scan Results
| Metric | Value |
|--------|-------|
| Files scanned | 730 |
| Candidate files | 218 |
| Candidate matches | 1,412 |
| Draft findings produced | 12 |
| Enriched and kept | 10 |
| Enriched and dropped | 2 |
| Severity distribution | 1 Critical, 3 High, 6 Medium, 1 Low |
### Built-in Rulesets Applied (Candidate Scanner)
- `secret-literal` (9 matches) — Hardcoded secrets
- `command-execution` (55 matches) — Shell/command invocation
- `dynamic-code-execution` (12 matches) — eval/exec patterns
- `raw-sql-query` (611 matches) — SQL query construction
- `hidden-control-channel` (42 matches) — Auth/routing headers
- `open-redirect` (2 matches) — Redirect sinks
- `path-traversal-file-access` (638 matches) — Path joins
- `webhook-without-obvious-signature` (6 matches) — Webhook handlers
- `unsafe-html-or-template` (17 matches) — HTML injection
- `ssrf-capable-request` (10 matches) — Outbound HTTP
- `weak-token-or-crypto` (5 matches) — Weak randomness
- `public-entrypoint` (5 matches) — Public routes
### Custom Analysis Targets (Domain Attack Research Driven)
| Target | DFD/CFD Slice | Finding |
|--------|---------------|---------|
| CORS env var trust | CFD-1 (Auth Flow) | p4-003 (high) |
| XSS via markdown rendering | DFD-2 (VoicePrint), AS-7 | p4-004 (high) |
| Puppeteer SSRF | DFD-3 (Puppeteer), AS-8 | p4-002 (high) |
| Stripe webhook type safety | DFD-5 (Stripe), AS-6 | p4-006 (medium) |
| Return URL open redirect | C-3 (Stripe), DFD-5 | p4-010 (medium) |
| superjson CVE | DFD-3 (Extension), B-4 | p4-008 (medium) |
| Rate limit bypass | CFD-3 (Rate Limiting), AS-5 | p4-009 (medium) |
| WebSocket Origin check | DFD-4 (WebSocket), AS-3 | p4-011 (medium) |
| JWT in WS query param | DFD-4 (WebSocket), AS-3 | p4-007 (medium) |
| Admin role mutation | CFD-2 (Authz Flow), AS-4 | p4-001 (critical) |
| Audio path traversal | DFD-2 (VoicePrint), C-5 | p4-005 (high) |
| Admin SQL pattern | DFD-1 (tRPC→ORM), B-1 | p4-012 (low) |
### Agentic Actions Audit
Analyzed 2 GitHub Actions workflow files (`.github/workflows/ci.yml`, `.github/workflows/deploy.yml`).
**Result**: 0 AI action instances found. No Claude Code, Gemini CLI, OpenAI Codex, or GitHub AI Inference integrations present. Standard CI/CD workflows only.
---
## CodeQL Structural Analysis
**Status**: Not available — CodeQL not installed on PATH.
The CodeQL extraction targets defined in Section "Phase 4 CodeQL Extraction Targets" were not executed. The following analysis was performed as a substitute:
- **Entry points**: Identified 16 tRPC routers as primary entry points via manual review of `web/src/server/api/routers/`
- **Sinks**: Identified Drizzle ORM queries, Puppeteer `page.setContent()`, `writeFile()`, WebSocket connections, and Stripe API calls as sinks
- **Flow coverage**: 11 of 12 DFD/CFD slices were covered by targeted grep/read analysis
### Entry Points Identified (Manual)
| Entry Point | Type | Trust Level | File |
|-------------|------|-------------|------|
| tRPC public procedures | HTTP POST /trpc | Anonymous | `web/src/server/api/routers/extension.ts` |
| tRPC protected procedures | HTTP POST /trpc | Authenticated | `web/src/server/api/routers/*.ts` (15 files) |
| tRPC admin procedures | HTTP POST /trpc | Admin-only | `web/src/server/api/routers/admin.ts` |
| Stripe webhook | HTTP POST /api/stripe/webhook | Webhook secret | `web/src/routes/api/stripe/webhook.ts` |
| WebSocket upgrade | WS upgrade /:3001 | JWT in query param | `web/src/server/websocket.ts` |
| Blog post page | HTTP GET /blog/:slug | Public | `web/src/routes/blog/[slug].tsx` |
| Billing return | HTTP GET /billing/return | Public (post-payment) | `web/src/routes/billing/return.tsx` |
### Sinks Identified (Manual)
| Sink | Type | Risk | File |
|------|------|------|------|
| Drizzle `sql<>` tag | SQL execution | CVE-2026-39356 | `admin.ts:47` |
| Drizzle `.update().set()` | SQL execution | Type coercion | `billing.service.ts:156` |
| Puppeteer `page.setContent()` | SSRF/file access | SSRF | `generator.ts:145` |
| `writeFile()` | File write | Path traversal | `storage.ts:24` |
| `fetch()` to external APIs | SSRF | URL manipulation | `scan.engine.ts` |
| WebSocket `ws.send()` | Data exfiltration | Auth bypass | `websocket.ts:126` |
| `innerHTML` binding | XSS | HTML injection | `blog/[slug].tsx:121` |
---
## SAST Enrichment
### Candidate-to-Finding Classification
Every candidate from the scanner was evaluated against the inline enrichment criteria. Below are the enriched verdicts for candidates that were elevated to draft findings.
#### Kept Findings
| Finding ID | Classification | Attacker Control | Trust Boundary | Reachability | Verdict |
|-----------|---------------|-----------------|---------------|-------------|---------|
| p4-001 | security | Admin (via SQLi/session theft) | Authz boundary (role check) | reachable | **keep** — direct privilege escalation path |
| p4-002 | security | Admin (template control) | Boundary between user data and browser context | reachable | **keep** — SSRF via Puppeteer with --no-sandbox |
| p4-003 | security | Env var injection (CI/CD, container) | CORS boundary | reachable | **keep** — env var controls trust boundary |
| p4-004 | security | Admin (blog content) | Boundary between server data and browser | reachable | **keep** — innerHTML with unsanitized markdown |
| p4-005 | security | tRPC input (userId) | Filesystem trust boundary | reachable | **keep** — path traversal via unsanitized userId |
| p4-006 | security | Stripe webhook (requires secret) | Type safety boundary | reachable | **keep** — type coercion masks API changes |
| p4-007 | security | Log access (proxy/server logs) | Auth boundary (JWT) | reachable | **keep** — JWT in query params is logged |
| p4-008 | security | Extension local data | Local execution boundary | reachable | **keep** — vulnerable superjson version |
| p4-009 | security | tRPC input (procedure path) | Rate limiting boundary | reachable | **keep** — substring matching bypass |
| p4-010 | security | tRPC input (returnUrl) | Payment redirect boundary | reachable | **keep** — open redirect post-payment |
| p4-011 | security | Any website (CSRF) | WebSocket Origin boundary | reachable | **keep** — no Origin validation on WS |
| p4-012 | correctness | Developer (future code) | Code quality boundary | reachable | **keep** — latent SQL injection pattern |
#### Dropped Candidates (Not Elevated)
| Candidate Class | Reason for Drop |
|----------------|----------------|
| `raw-sql-query` (611 matches) | False positives — tRPC `.query()` method calls, not raw SQL |
| `command-execution` (55 matches) | All in test/benchmark files, not production code |
| `dynamic-code-execution` (12 matches) | SQLite `raw.exec()` calls, not code execution sinks |
| `secret-literal` (9 matches) | Test data and password validation error messages |
| `path-traversal-file-access` (636 dropped) | False positives from `.join("")` string concatenation, not filesystem paths |
| `webhook-without-obvious-signature` (5 dropped) | Stripe webhook handler DOES have signature verification (`constructEvent()`) |
| `open-redirect` (1 dropped) | `Navigate href="/admin/blog/new"` is a hardcoded internal redirect |
| `weak-token-or-crypto` (1 dropped) | `Math.random()` for HTML input IDs, not cryptographic use |
| `public-entrypoint` (5 dropped) | Standard public tRPC procedures, not vulnerabilities |
| `hidden-control-channel` (3 dropped) | Test file middleware definitions, not production code |
### Entry Points Not in Phase 3 DFD Slices
| Entry Point | Phase 3 DFD Coverage | Gap |
|-------------|---------------------|-----|
| Blog post rendering (`blog/[slug].tsx`) | Partial (AS-7 mentioned XSS risk) | Detailed innerHTML analysis added in p4-004 |
| Billing return page (`billing/return.tsx`) | Partial (AS-6 mentioned redirect) | Detailed open redirect analysis added in p4-010 |
### Sinks Not Mapped to High-Risk Flows
| Sink | Unmodeled Risk | Finding |
|------|---------------|---------|
| `page.setContent()` (Puppeteer) | Not in DFD-3 as SSRF sink | p4-002 |
| `writeFile()` with userId | Not in DFD-2 as path traversal sink | p4-005 |
| `ws.send()` with Origin bypass | Not in DFD-4 as auth bypass sink | p4-011 |
### Batching, Throttling, and Coverage Tradeoffs
1. **CodeQL/Semgrep unavailable**: Both CodeQL and Semgrep were not installed on PATH. Analysis fell back to targeted grep+read patterns focused on highest-score candidates and domain attack research targets. This means interprocedural data flow analysis was not performed — findings are based on static pattern matching and manual code review.
2. **`raw-sql-query` bulk drop**: 611 `raw-sql-query` candidates were dropped without individual review because they are all tRPC `.query()` method calls (framework methods), not raw SQL execution. This is a high-confidence drop based on pattern analysis.
3. **Test file exclusion**: All candidates in test files (`test_*.py`, `*_test.go`, `*_spec.rb`, `*.test.ts`, `*.test.tsx`) were evaluated and dropped as test-only. This is consistent with the enrichment drop criteria.
4. **Coverage completeness**: All 12 DFD/CFD slices from Phase 3 were addressed by at least one finding or a documented drop rationale. The one gap (p4-012) is a latent risk pattern that requires future code changes to become exploitable.