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

54 KiB

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)

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

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)

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

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

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)

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

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

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

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

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

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

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

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

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 buildvite 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
LocalUserInputRemoteFlowSource (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.