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:
requestLogger— logs all requestssecurityHeaders— sets HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-PolicycorsHeaders— origin validation + preflight handlingclerkMiddleware— 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/stylesws:andwss:in connect-src — allows WebSocket to any host*.clerk.devand*.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 byextensionRouter(device linking, phishing reports).protectedProcedure— Auth required (ctx.usermust exist). No role check.adminProcedure— Auth required +role === "admin". Used byadminRouter.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
Originheader validation on WebSocket upgrade - No
Sec-WebSocket-Protocolvalidation - 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 inArraywith user-controlled arrays can lead to identifier injectionsqlhelper with string interpolation bypasses parameterization
Custom SAST targets:
- All
sql<>template tag usages inweb/src/server/services/**/*.ts - All
inArraycalls with non-literal second arguments - All
groupBy/orderBycalls with non-literal column references - All dynamic table name construction
Manual review checklist:
- Search for
sql\`` orsql`${` patterns - Search for
inArraywith variable arguments - Review all Drizzle
whereclauses for string interpolation - Check
groupByandorderByfor 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_nextAppDirCalleris a Next.js-specific adapter feature — likely not used in SolidStart- WebSocket DoS affects the
wslibrary'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_REGEXusage - 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
SignJWTandjwtVerifywith 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.0on 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.mjswhich is the Nitro production server - The
server.fs.denyCVEs 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.denysettings - 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
audioBase64parameter 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:
- Resource exhaustion: Upload extremely large base64 audio payloads to exhaust memory during decode + ML processing
- Model poisoning: Craft audio that produces specific ML outputs (adversarial audio)
- Privacy leak: Audio files stored on disk may be readable by other processes
- 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
fetchWithCircuitusesAbortSignal.timeout(10_000)— 10s timeout per request
Attack vectors:
- API cost exhaustion: Trigger expensive scans via watchlist items with many values
- SSRF via URL manipulation: If scan URLs are constructed from user input, SSRF is possible
- Response injection: Unvalidated responses from external APIs stored in database
- 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
returnUrlin checkout session is user-controlled (fromCreateCheckoutSessionSchema)- Stripe API version pinned to
2026-04-22.dahlia
Attack vectors:
- Webhook replay: Replay checkout events to create duplicate subscriptions
- Return URL redirect: If
returnUrlis not validated, open redirect post-payment - Type coercion bypass: Malformed webhook events could bypass event type checks
- Price ID manipulation:
mapStripeProductToTieruses 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
broadcastToUsersends to all connected sockets for a user- No message validation — any JSON is accepted
Attack vectors:
- Memory exhaustion: Connect many sockets per user (no connection limit per user)
- Slow-loris DoS: Send valid but slow messages to keep connections alive
- Alert flooding: If a service calls
broadcastToUserin a loop, flood all connected clients - 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:
- SSRF via Puppeteer: If HTML template contains
file://orhttp://URLs from user control - Path traversal: If userId or report filename is user-controlled and not sanitized
- Disk exhaustion: Large audio uploads fill disk, affecting report generation
- 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
-
Vite
server.fs.denybypasses — These affect the dev server only (vite dev). The production deployment usesvite build+vite start(Nitro), which does not use the dev server's file serving. Findings related toserver.fs.denybypass in production should be evaluated against whether the dev server is exposed. -
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. -
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.
-
@trpc/server
experimental_nextAppDirCallerprototype 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 secretscommand-execution(55 matches) — Shell/command invocationdynamic-code-execution(12 matches) — eval/exec patternsraw-sql-query(611 matches) — SQL query constructionhidden-control-channel(42 matches) — Auth/routing headersopen-redirect(2 matches) — Redirect sinkspath-traversal-file-access(638 matches) — Path joinswebhook-without-obvious-signature(6 matches) — Webhook handlersunsafe-html-or-template(17 matches) — HTML injectionssrf-capable-request(10 matches) — Outbound HTTPweak-token-or-crypto(5 matches) — Weak randomnesspublic-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
-
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.
-
raw-sql-querybulk drop: 611raw-sql-querycandidates 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. -
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. -
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.