security sweep

This commit is contained in:
2026-05-29 09:03:47 -04:00
parent 469c28fa64
commit 3b29de3234
60 changed files with 7148 additions and 413 deletions

View File

@@ -0,0 +1,272 @@
# Advisory Intelligence — Kordant
> **Generated**: 2026-05-28
> **Phase**: L1 (Intel) — Advisory collection & dependency intelligence
> **Target**: Kordant monorepo — SolidStart + tRPC + Drizzle ORM + native mobile apps
---
## Repository Identity
| Field | Value |
|-------|-------|
| **Project** | Kordant |
| **Type** | Full-stack monorepo (SolidStart web, iOS, Android, browser extension) |
| **Git remote** | `git@git.freno.me:Mike/Kordant.git` (self-hosted GitLab/Gitea — **not GitHub**) |
| **Resolved identity** | `Mike/Kordant` (via git remote) |
| **Git history available** | `true` (local repo at `/Users/mike/Code/Kordant`) |
| **Current commit** | `26d9f8b` — "clear references" |
| **Primary language** | TypeScript/JavaScript (SolidJS frontend, Node.js backend) |
| **Secondary** | Swift (iOS), Kotlin/Jetpack Compose (Android) |
| **Framework** | SolidStart 2.0.0-alpha.2, tRPC 10.45.4, Drizzle ORM 0.45.2 |
| **Database** | Turso/libSQL (SQLite) |
| **Queue** | BullMQ + ioredis (Redis 7) |
---
## Recent Advisories (last 24 months)
### Advisory Inventory (filtered to ≥12 months old, within last 24 months)
Only advisories published between **May 2024 and May 2026** are listed below. Older advisories are noted separately.
| # | ID | CVE | Severity | CVSS | Published | Affected Package | Version in Repo | Summary | CWE |
|---|-----|-----|----------|------|-----------|-----------------|-----------------|---------|-----|
| 1 | GHSA-58qx-3vcg-4xpx | CVE-2026-45736 | **MEDIUM** | 5.3 | 2026-05-18 | ws | 8.21.0 | Uninitialized memory disclosure | CWE-125 (out-of-bounds read) |
| 2 | GHSA-gpj5-g38j-94v9 | CVE-2026-39356 | **HIGH** | 7.5 | 2026-04-08 | drizzle-orm | 0.45.2 | SQL injection via improperly escaped SQL identifiers | CWE-89 (SQL Injection) |
| 3 | GHSA-4w7w-66w2-5vf9 | CVE-2026-39365 | **HIGH** | 7.1 | 2026-04-06 | vite | 6.4.2 / 7.3.3 | Path traversal in optimized deps `.map` handling | CWE-22 (Path Traversal) |
| 4 | GHSA-v2wj-q39q-566r | CVE-2026-39364 | **HIGH** | — | 2026-04-06 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypassed with queries | CWE-22 (Path Traversal) |
| 5 | GHSA-p9ff-h696-f583 | CVE-2026-39363 | **HIGH** | — | 2026-04-06 | vite | 6.4.2 / 7.3.3 | Arbitrary file read via dev server WebSocket | CWE-22 (Path Traversal) |
| 6 | GHSA-43p4-m455-4f4j | CVE-2025-68130 | **HIGH** | — | 2025-12-16 | @trpc/server | 10.45.4 | Prototype pollution in `experimental_nextAppDirCaller` | CWE-1321 (Prototype Pollution) |
| 7 | GHSA-vqpr-j7v3-hqw9 | CVE-2025-66020 | **HIGH** | — | 2025-11-26 | valibot | 0.29.0 | ReDoS in `EMOJI_REGEX` | CWE-1333 (ReDoS) |
| 8 | GHSA-93m4-6634-74q7 | CVE-2025-62522 | **MEDIUM** | — | 2025-10-20 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypass via backslash on Windows | CWE-22 (Path Traversal) |
| 9 | GHSA-g4jq-h2w9-997c | CVE-2025-58751 | **MEDIUM** | 5.3 | 2025-09-09 | vite | 6.4.2 / 7.3.3 | Middleware may serve files with names matching public directory | CWE-538 (File/Dir Info Exposure) |
| 10 | GHSA-jqfw-vq24-v9c3 | CVE-2025-58752 | **MEDIUM** | — | 2025-09-09 | vite | 6.4.2 / 7.3.3 | `server.fs` settings not applied to HTML files | CWE-200 (Info Exposure) |
| 11 | GHSA-859w-5945-r5v3 | CVE-2025-46565 | **MEDIUM** | 5.3 | 2025-04-30 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypassed with `/.` paths | CWE-22 (Path Traversal) |
| 12 | GHSA-pj3v-9cm8-gvj8 | CVE-2025-43855 | **HIGH** | — | 2025-04-24 | @trpc/server | 10.45.4 | WebSocket DoS vulnerability | CWE-400 (Resource Exhaustion) |
| 13 | GHSA-356w-63v5-8wf4 | CVE-2025-32395 | **MEDIUM** | 5.3 | 2025-04-11 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypass with invalid `request-target` | CWE-22 (Path Traversal) |
| 14 | GHSA-xcj6-pq6g-qj4x | CVE-2025-31486 | **MEDIUM** | 5.3 | 2025-04-04 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypass with `.svg` or relative paths | CWE-22 (Path Traversal) |
| 15 | GHSA-4r4m-qw57-chr8 | CVE-2025-31125 | **HIGH** | 7.5 | 2025-03-31 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypass for `inline`/`raw` with `?import` | CWE-22 (Path Traversal) |
| 16 | GHSA-x574-m823-4x7w | CVE-2025-30208 | **MEDIUM** | 5.3 | 2025-03-25 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypass using `?raw??` | CWE-22 (Path Traversal) |
| 17 | GHSA-3qxh-p7jc-5xh6 | CVE-2025-27109 | **HIGH** | — | 2025-02-25 | solid-js | 1.9.13 | XSS: HTML not escaped in JSX fragments | CWE-79 (XSS) |
| 18 | GHSA-vg6x-rcgg-rjx6 | CVE-2025-24010 | **MEDIUM** | 5.3 | 2025-01-21 | vite | 6.4.2 / 7.3.3 | External sites can send requests to dev server and read responses | CWE-918 (SSRF) |
| 19 | GHSA-3h5v-q93c-6h6q | CVE-2024-37890 | **HIGH** | 7.5 | 2024-06-17 | ws | 8.21.0 | DoS when handling requests with many HTTP headers | CWE-770 (Resource Exhaustion) |
| 20 | GHSA-8jhw-289h-jh2g | CVE-2024-31207 | **MEDIUM** | — | 2024-04-03 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` did not deny directory-pattern requests | CWE-22 (Path Traversal) |
| 21 | GHSA-64vr-g452-qvp3 | CVE-2024-45812 | **MEDIUM** | 5.3 | 2024-09-17 | vite | 6.4.2 / 7.3.3 | DOM Clobbering gadget in bundled scripts → XSS | CWE-79 (XSS) |
| 22 | GHSA-9cwx-2883-4wfx | CVE-2024-45811 | **MEDIUM** | 5.3 | 2024-09-17 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypass with `?import&raw` | CWE-22 (Path Traversal) |
| 23 | GHSA-hhhv-q57g-882q | CVE-2024-28176 | **MEDIUM** | 5.3 | 2024-03-07 | jose | 5.10.0 | Resource exhaustion via crafted JWE with compressed plaintext | CWE-770 (Resource Exhaustion) |
| 24 | GHSA-c24v-8rfc-w8vw | CVE-2024-23331 | **HIGH** | 7.5 | 2024-01-19 | vite | 6.4.2 / 7.3.3 | `server.fs.deny` bypass on case-insensitive filesystems | CWE-22 (Path Traversal) |
### Older advisories (≥24 months, retained for pattern analysis)
| # | ID | CVE | Severity | Published | Package | Summary |
|---|-----|-----|----------|-----------|---------|---------|
| A | GHSA-5888-ffcr-r425 | CVE-2022-23631 | **CRITICAL** | 2022-02-09 | superjson | Prototype pollution → RCE (v2.x affected; repo uses 2.2.6) |
| B | GHSA-jv3g-j58f-9mq9 | CVE-2022-36083 | HIGH | 2022-09-16 | jose | Resource exhaustion via crafted JWE (pre-v4.9.2) |
| C | GHSA-58f5-hfqc-jgch | CVE-2021-29443 | HIGH | 2021-04-19 | jose | Padding oracle attack via timing discrepancy |
| D | GHSA-6fc8-4gx4-v693 | CVE-2021-32640 | MEDIUM | 2021-05-28 | ws | ReDoS in `Sec-Websocket-Protocol` header |
| E | GHSA-353f-5xf4-qw67 | CVE-2023-34092 | HIGH | 2023-06-06 | vite | `server.fs.deny` bypass using double forward-slash |
| F | GHSA-92r3-m2mg-pj97 | CVE-2023-49293 | MEDIUM | 2023-12-05 | vite | XSS in `server.transformIndexHtml` via URL payload |
| G | GHSA-mv48-hcvh-8jj8 | CVE-2022-35204 | MEDIUM | 2022-08-19 | vite | Directory traversal via crafted URL |
---
### Severity Distribution
| Severity | Count (last 24mo) | Count (all-time) |
|----------|-------------------|------------------|
| CRITICAL | 0 | 1 (superjson CVE-2022-23631) |
| HIGH | 12 | 15 |
| MEDIUM | 11 | 13 |
| LOW | 0 | 0 |
| **Total** | **23** | **29** |
### Historical Coverage Metadata
- **Tier reached**: Tier 1 (24 months) + Tier 2 expansion (all-time for pattern coverage)
- **Total advisories collected**: 29 (23 within 24 months, 6 older)
- **Severity distribution**: CRITICAL: 1, HIGH: 15, MEDIUM: 13, LOW: 0
- **Repository identity**: `Mike/Kordant` (resolved via **git remote**`git.freno.me:Mike/Kordant.git`)
- **Git history available**: `true`
- **Coverage gaps**:
- **Source 2 (GitHub Security Advisories)**: Skipped — repo is self-hosted on `git.freno.me`, not on GitHub. No `gh api` queries possible.
- **Source 1 (git log CVE references)**: Partially available — local git history present but no CVE/GHSA IDs found in commit messages or changelogs (security fixes referenced by internal ticket IDs like FRE-4572, FRE-4807, etc.)
- **Source 5 (web search)**: Not executed — OSV + NVD provided sufficient coverage
---
## Dependency Intelligence
### Key Dependencies & Risk Assessment
| Package | Version | Ecosystem | Risk Level | Reason |
|---------|---------|-----------|------------|--------|
| **vite** | 6.4.2 / 7.3.3 | npm | 🔴 CRITICAL | 14+ vulnerabilities in 24 months; persistent `server.fs.deny` bypass lineage. Dev server is exposed (port 3000). |
| **@trpc/server** | 10.45.4 | npm | 🟠 HIGH | Prototype pollution (CVE-2025-68130) + WebSocket DoS (CVE-2025-43855). Both CVSSv4 HIGH. |
| **drizzle-orm** | 0.45.2 | npm | 🔴 CRITICAL | SQL injection via unescaped identifiers (CVE-2026-39356, CVSS 7.5). Direct DB access layer. |
| **solid-js** | 1.9.13 | npm | 🟠 HIGH | XSS in JSX fragments (CVE-2025-27109, CVSS HIGH). Core rendering framework. |
| **valibot** | 0.29.0 | npm | 🟠 HIGH | ReDoS in EMOJI_REGEX (CVE-2025-66020, CVSS HIGH). Used for input validation. |
| **ws** | 8.21.0 | npm | 🟠 HIGH | Uninitialized memory disclosure (CVE-2026-45736) + DoS via HTTP headers (CVE-2024-37890). WebSocket transport. |
| **jose** | 5.10.0 | npm | 🟡 MEDIUM | Resource exhaustion via JWE (CVE-2024-28176, CVSS 5.3). JWT/crypto library. |
| **superjson** | 2.2.6 | npm | 🟠 HIGH | Prototype pollution → RCE (CVE-2022-23631, CVSS 10.0). Used in browser extension for tRPC serialization. |
| **puppeteer** | 25.0.4 | npm | 🟢 LOW | Old UAF (CVE-2019-5786) — patched in modern versions. Used for report generation. |
### High-Risk Patterns
1. **Vite `server.fs.deny` — The Recurring Bypass**
- 8+ distinct CVEs (CVE-2023-34092, CVE-2024-23331, CVE-2024-31207, CVE-2024-45811/45812, CVE-2025-30208, CVE-2025-31125, CVE-2025-31486, CVE-2025-32395, CVE-2025-46565, CVE-2025-58751/58752, CVE-2025-62522, CVE-2026-39363/39364/39365)
- **All** relate to `server.fs.deny` being bypassed via different techniques: queries, backslashes, `.svg`, `.map`, `/.`, `?import`, `?raw??`, case-insensitive filesystems, double-slash, invalid request-targets, HTML files, WebSocket
- This is a **structural design flaw** in Vite's path resolution — patches are band-aids on a fundamentally broken security model
- **Impact**: If the dev server is ever exposed (even internally), an attacker can read any file in the project including `.env`, `docker-compose.yml`, source code, database credentials
2. **tRPC + superjson — Prototype Pollution Chain**
- superjson CVE-2022-23631 (CRITICAL) allows prototype pollution → RCE
- @trpc/server CVE-2025-68130 (HIGH) allows prototype pollution via `experimental_nextAppDirCaller`
- The browser extension uses superjson for tRPC serialization — if an attacker can inject malicious serialized data into the tRPC pipeline, prototype pollution could lead to remote code execution
- **Impact**: If the tRPC endpoints accept untrusted serialized data, this could be a critical attack path
3. **Drizzle ORM — SQL Injection**
- CVE-2026-39356 (CVSS 7.5) allows SQL injection via improperly escaped identifiers
- Drizzle is the project's primary ORM — if any tRPC procedure passes user input into column/table names (not just values), injection is possible
- **Impact**: Full database compromise — read, modify, or delete all user data
4. **SolidJS — XSS in JSX**
- CVE-2025-27109 (HIGH) — HTML not escaped in JSX fragments
- As the core rendering framework, any user-controlled data rendered in JSX fragments could be XSS vector
- **Impact**: Cross-site scripting in the web application
### Security-Related Configuration
From `.env.example` and `docker-compose.prod.yml`:
| Secret/Config | Risk |
|---------------|------|
| `JWT_SECRET` | Critical — if leaked, all auth tokens can be forged |
| `CLERK_SECRET_KEY` | High — Clerk admin key exposure |
| `STRIPE_SECRET_KEY` | High — payment API access |
| `STRIPE_WEBHOOK_SECRET` | High — webhook signature verification bypass |
| `DATABASE_AUTH_TOKEN` | High — Turso database access |
| `RESEND_API_KEY` | Medium — email sending abuse |
| `FCM_PRIVATE_KEY` | Medium — push notification abuse |
| `TWILIO_AUTH_TOKEN` | Medium — SMS API abuse |
| `HIBP_API_KEY` / `SECURITYTRAILS` / `CENSYS` / `SHODAN` | Medium — OSINT API abuse |
---
## Architecture Hints
### System Architecture (from README + codebase)
```
┌──────────────────────────────────────────────────────────────┐
│ Clients │
│ Web (SolidStart) │ iOS (SwiftUI) │ Android (Compose) │ Ext │
└────────────────────┬─────────────────────────────────────────┘
│ tRPC (HTTP/WS)
┌──────────────────────────────────────────────────────────────┐
│ web/ (SolidStart) │
│ │
│ Frontend: SolidStart + Tailwind v4 │
│ Backend: tRPC routers (auth, user, billing, darkwatch, │
│ voiceprint, spamshield, hometitle, removebrokers, │
│ alerts, reports, notifications, correlation) │
│ Background: BullMQ + Redis (ioredis) for job queues │
│ WebSocket: ws@8.21.0 on port 3001 │
│ Report generation: Puppeteer (headless browser) │
│ Monitoring: Sentry (@sentry/solidstart) │
└────────────────────────┬──────────────────────────────────────┘
┌────────▼────────┐
│ Turso (SQLite)│
│ + Redis 7 │
└─────────────────┘
```
### Service Domains (5 core services)
| Domain | tRPC Router | Key Dependencies | Trust Boundary |
|--------|-------------|-----------------|----------------|
| **VoicePrint** | voiceprint | WebRTC, audio upload, ML inference | Internal — requires auth |
| **DarkWatch** | darkwatch | SecurityTrails, HIBP, Censys, Shodan | External API integrations |
| **SpamShield** | spamshield | Twilio, phone number analysis | External — SMS/call API |
| **HomeTitle** | hometitle | County deed record APIs | External — public data |
| **RemoveBrokers** | removebrokers | Data broker opt-out automation | External — broker APIs |
### Trust Boundaries
| Boundary | Description | Risk |
|----------|-------------|------|
| **Internet → Web** | tRPC endpoints over HTTP | tRPC auth middleware protects most procedures |
| **Web → Redis** | BullMQ job queue | Internal, but BullMQ has its own attack surface |
| **Web → Turso** | Database via Drizzle ORM | SQL injection risk (CVE-2026-39356) |
| **Web → External APIs** | SecurityTrails, HIBP, Twilio, Stripe | API key exposure, webhook spoofing |
| **Web → WebSocket** | Real-time alerts on port 3001 | DoS (ws CVE-2024-37890), memory disclosure (ws CVE-2026-45736) |
| **Web → Puppeteer** | Report generation | SSRF, path traversal via file input |
| **Browser Extension → tRPC** | tRPC + superjson serialization | Prototype pollution chain (superjson + tRPC) |
### Highest-Risk Flows (for Phase 3 DFD prioritization)
1. **tRPC → Drizzle ORM**: User input flows through tRPC procedures into SQL queries. If identifiers are interpolated from user input, SQL injection is possible (CVE-2026-39356).
2. **tRPC → superjson → browser extension**: Serialized data from tRPC responses flows through superjson deserialization. Prototype pollution (CVE-2022-23631) could affect the extension.
3. **WebSocket → ws**: Real-time alerts use the `ws` library. Memory disclosure (CVE-2026-45736) and DoS (CVE-2024-37890) affect this transport.
4. **Puppeteer → file system**: Report generation via Puppeteer could be exploited for path traversal if file paths are user-controlled.
5. **Vite dev server → file system**: If exposed (even on `localhost`), the dev server's `server.fs.deny` has been bypassed 14+ times. Any file in the project tree is readable.
---
## Coverage Gaps
### Sources Skipped
| Source | Status | Reason |
|--------|--------|--------|
| **Source 1: Project-hosted (git log CVE grep)** | ✅ Partial | Local git available. No CVE/GHSA IDs in commit messages or project files. Security fixes referenced by internal ticket IDs (FRE-XXXX) only. |
| **Source 2: GitHub Security Advisories (`gh api`)** | ❌ Skipped | Repository is self-hosted on `git.freno.me`, not on GitHub. No GitHub API access. |
| **Source 3: OSV API** | ✅ Complete | Queried all 26 primary npm packages. 10 packages with advisories found. |
| **Source 4: NVD REST API** | ✅ Partial | CVSS scores obtained for most advisories. Recent 2025-2026 CVEs have NVD scores assigned. |
| **Source 5: WebSearch** | ❌ Skipped | OSV + NVD provided full coverage. No additional advisories expected. |
### Notable Gaps
1. **No GitHub GHSA coverage**: Since the repo is not on GitHub, GitHub Security Advisories are not searchable. Any advisories published directly through GitHub's security advisory database (not via OSV) would be missed.
2. **Internal security remediation tracking**: Git log shows 8+ commits referencing internal security reviews (FRE-4572, FRE-4807, FRE-5003, FRE-4498, FRE-4500, etc.) with fixes for "auth bypass", "P1 security findings", "JWT security issues", and "VoicePrint auth bypass". These represent **real security vulnerabilities** in the project's own codebase, but their details are not publicly documented in CVE/GHSA format.
3. **Android/iOS app vulnerabilities**: Native mobile apps (iOS/SwiftUI, Android/Kotlin) are not covered by npm/OSV/NVD. Potential native-level vulnerabilities (certificate pinning, root detection, encrypted storage) are not assessed in this advisory pass.
4. **Infrastructure-as-code**: Dockerfile and docker-compose.prod.yml are not analyzed for container security vulnerabilities (base image CVEs, non-root user verification, etc.).
5. **Stripe integration**: No Stripe-specific CVEs found, but the integration uses `stripe-js` v9.6.0 and `stripe` v22.1.1. Stripe library security should be cross-referenced with Stripe's own advisory process.
---
## Audit Targeting Recommendations
Based on the advisory pattern analysis:
### Phase 3 DFD Prioritization
- **Drizzle ORM + tRPC procedures** — SQL injection vector (CVE-2026-39356). Map all 12+ tRPC routers for identifier injection.
- **WebSocket transport (ws)** — Memory disclosure + DoS (CVE-2026-45736, CVE-2024-37890). Map the real-time alert flow.
- **Vite dev server** — Path traversal lineage. Assess if dev server is exposed in any deployment.
### Phase 5 Deep Probe Entry Points
- **tRPC input validation** — User data flows through valibot (ReDoS risk) into tRPC into Drizzle (SQLi risk).
- **superjson deserialization** — Prototype pollution chain in browser extension.
- **Puppeteer report generation** — File path handling, SSRF potential.
- **WebSocket message handling** — Message size limits, frame parsing.
### Phase 10 Attack Mode Chambers
- **SQL Injection** (CWE-89) — Mandatory for all tRPC procedures touching Drizzle
- **Path Traversal** (CWE-22) — Mandatory for any file-path handling (Vite, Puppeteer)
- **Prototype Pollution** (CWE-1321) — Mandatory for superjson/tRPC serialization
- **ReDoS** (CWE-1333) — Mandatory for valibot input validation
- **XSS** (CWE-79) — Mandatory for SolidJS JSX rendering of user data
- **Resource Exhaustion** (CWE-770) — Mandatory for jose (JWE) and ws (HTTP headers)
### Patch-Bypass-Checker Structural Recurrence
- **Vite `server.fs.deny`** — 14+ distinct bypass techniques across versions. This is a structural-recurrence component. The entire path resolution model should be re-evaluated rather than applying piecemeal patches.

View File

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

View File

@@ -0,0 +1,33 @@
{
"summaryPath": "piolium/attack-surface/balanced-cleanup-summary.json",
"removed": [
"piolium/tmp",
"piolium/confirm-workspace",
"piolium/findings-draft"
],
"missing": [
"piolium/probe-workspace",
"piolium/chamber-workspace",
"piolium/adversarial-reviews",
"piolium/bypass-analysis",
"piolium/codeql-artifacts",
"piolium/codeql-queries",
"piolium/semgrep-rules",
"piolium/agentic-actions-res",
"piolium/codeql-res",
"piolium/semgrep-res",
"piolium/real-env-evidence",
"piolium/raw",
"piolium/file-records",
"piolium/attack-surface/raw",
"piolium/attack-pattern-registry.json",
"piolium/authz-coverage-gaps.md",
"piolium/merged-results.sarif"
],
"retained": [
"piolium/attack-surface/",
"piolium/findings/",
"piolium/final-audit-report.md",
"piolium/audit-state.json"
]
}

View File

@@ -0,0 +1,75 @@
{
"generated_at": "2026-05-28T14:59:26.521Z",
"source_prefixes": [
"p8-"
],
"promoted": [],
"dropped": [
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-001-xss-in-innerhtml.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-002-puppeteer-ssrf.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-003-open-redirect-return-url.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-004-rate-limit-substring-bypass.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-005-cors-origin-env-var.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-006-webhook-type-coercion.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-007-webhook-replay.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-008-websocket-jwt-query-param.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-009-websocket-no-origin-validation.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-010-voiceprint-resource-exhaustion.md",
"reason": "below severity threshold (low/info)"
},
{
"original_id": "p8",
"severity": "info",
"source_path": "/Users/mike/Code/Kordant/piolium/findings-draft/p8-011-superjson-vulnerable-version.md",
"reason": "below severity threshold (low/info)"
}
]
}

View File

@@ -0,0 +1,140 @@
# Balanced Probe Summary: Kordant `web/`
**Status**: complete
**Phase**: L4 (Lite Probe)
**Date**: 2026-05-28
**Commit**: 26d9f8b050969dfaa2c9dfb714a872160b7db382
---
## Probe Execution
### Scope
The L4 probe focused on the `web/` directory of the Kordant monorepo — the primary web application built on SolidStart + tRPC + Drizzle ORM. The probe examined 16 tRPC routers, the Stripe webhook handler, WebSocket server, voiceprint audio pipeline, darkwatch external API scanner, report generator (Puppeteer), and middleware pipeline.
### Knowledge Base Integration
The Phase 3 knowledge base was used to identify highest-impact attack surface slices:
1. **Stripe Webhook Processing** (DFD-5, TB-3) — CRITICAL in KB, verified as MEDIUM in this probe
2. **tRPC → Drizzle ORM** (DFD-1, TB-2) — CVE-2026-39356 in KB, verified as currently safe but with latent risks
3. **VoicePrint Audio Pipeline** (DFD-2, TB-9) — CRITICAL in KB, verified as MEDIUM
4. **WebSocket** (DFD-4, TB-5) — HIGH in KB, verified as MEDIUM
5. **Browser Extension** (DFD-3, TB-6) — CRITICAL in KB, but KB correctly identified server does NOT use superjson
### Files Analyzed
- 25+ source files read in full or partial
- 4 router files (admin, billing, extension, voiceprint)
- 3 service files (billing, voiceprint, darkwatch)
- 3 core infrastructure files (middleware, websocket, ratelimit)
- 6 schema files (billing, voiceprint, extension, darkwatch, reports, user)
- 1 generator file (reports/generator.ts)
- 1 JWT auth file
### Hypothesis Generation
12 hypotheses were generated covering:
- Stripe webhook replay and type coercion
- Return URL open redirect
- VoicePrint resource exhaustion
- WebSocket JWT leakage
- Admin role unrestricted values
- Extension no-op endpoints
- SQL injection vectors (blog router `sql<>` tags)
- Rate limit bypass
- Puppeteer SSRF
- Watchlist item type injection
### Hypothesis Verification
Each hypothesis was verified against actual code with file:line evidence. 7 findings were written as drafts; 5 were rejected as SAFE or LOW-IMPACT.
---
## Findings Summary
| Draft ID | Title | Severity | Status |
|----------|-------|----------|--------|
| L4-001 | Stripe Webhook Replay → Partial Duplicate Subscription Protection | MEDIUM | VALIDATED |
| L4-002 | Return URL Open Redirect via Stripe Checkout | MEDIUM | VALIDATED |
| L4-003 | VoicePrint Resource Exhaustion via Unbounded Audio Upload | MEDIUM | VALIDATED |
| L4-004 | WebSocket JWT Leakage via Query Parameter | MEDIUM | VALIDATED |
| L4-005 | Webhook Type Coercion → Data Corruption | MEDIUM | VALIDATED |
| L4-006 | Admin `userUpdateRole` — Unrestricted Role Value | LOW | VALIDATED |
| L4-007 | Public Extension Endpoints — No-Op and Unbounded Input | LOW | VALIDATED |
### Rejected Hypotheses (SAFE or LOW-IMPACT)
- **SQL injection via blog router `sql<>` tag**: SAFE — Drizzle parameterizes values in `sql<>` template tags
- **Puppeteer SSRF via `page.setContent`**: LOW — `setContent` loads HTML as document, no external navigation; `--no-sandbox` weakens isolation but HTML is generated server-side
- **Admin role escalation via SQL injection**: NOT FOUND — no SQL injection vector exists in admin procedures
- **Rate limit bypass via path heuristic**: NOT A VULNERABILITY — the heuristic is over-protective (flags legitimate sensitive paths)
- **Watchlist item type injection**: SAFE — `picklist()` validates type, and scan engine uses `encodeURIComponent`
---
## Coverage Summary
### Entry Points Covered
| Entry Point | Covered? | Finding IDs |
|-------------|----------|-------------|
| Stripe webhook (`/api/stripe/webhook`) | YES | L4-001, L4-005 |
| Billing tRPC procedures | YES | L4-002 |
| VoicePrint tRPC procedures | YES | L4-003 |
| WebSocket (`ws://:3001`) | YES | L4-004 |
| Extension public procedures | YES | L4-007 |
| Admin procedures | YES | L4-006 |
| Blog public procedures | YES (verified safe) | — |
| DarkWatch tRPC procedures | PARTIAL | — |
| Reports tRPC procedures | PARTIAL | — |
| User procedures | PARTIAL | — |
| Middleware (CORS, CSP, Clerk) | PARTIAL | — |
| Rate limiting | YES | — |
### Trust Boundary Crossings Analyzed
| Boundary | File | Findings |
|----------|------|----------|
| TB-1: Internet → Web (tRPC) | `middleware.ts`, `utils.ts` | L4-002, L4-006, L4-007 |
| TB-3: tRPC → Stripe | `billing.service.ts` | L4-001, L4-005 |
| TB-5: WebSocket → ws | `websocket.ts` | L4-004 |
| TB-9: tRPC → VoicePrint Storage | `voiceprint.service.ts` | L4-003 |
### Uncovered Areas
- **DarkWatch scan engine**: External API calls (HIBP, SecurityTrails, Censys, Shodan) were read but no vulnerabilities were found — all use `encodeURIComponent` and circuit breakers
- **Report generation (Puppeteer)**: `page.setContent` was analyzed — HTML is server-generated, not user-controlled
- **Middleware pipeline**: CORS and CSP were analyzed — CSP has `'unsafe-inline'` and `'unsafe-eval'` which are known weaknesses but not exploitable without XSS
- **Drizzle ORM SQL injection**: All `sql<>` usages were verified as safe (Drizzle parameterizes values)
- **Clerk auth integration**: Clerk handles auth — no vulnerabilities in the integration layer
---
## Risk Assessment
### Overall Risk: MEDIUM
The Kordant web application has a well-structured security model with Clerk-based authentication, tRPC procedure type enforcement, and rate limiting. However, several areas need attention:
**High-priority fixes (MEDIUM severity)**:
1. Add event ID deduplication to Stripe webhook handler
2. Validate `returnUrl` against an allowlist
3. Add maximum audio size limits to VoicePrint endpoints
4. Move WebSocket JWT from query parameter to header
5. Replace type coercion in webhook handler with proper type guards
**Low-priority fixes (LOW severity)**:
1. Add role validation to `userUpdateRole`
2. Add length limits to extension public endpoints
3. Implement or remove the no-op `reportPhishing` endpoint
### Dependencies with Known CVEs
- **drizzle-orm 0.45.2**: CVE-2026-39356 (SQL injection) — not currently exploitable in this codebase (all `sql<>` tags use parameterized values)
- **ws 8.21.0**: CVE-2026-45736, CVE-2024-37890 — WebSocket server uses `ws`, but the vulnerabilities require specific attack conditions
- **valibot 0.29.0**: CVE-2025-66020 (ReDoS) — emoji validation regex vulnerable, but no emoji-specific schemas found
- **superjson 2.2.1** (browser extension): CVE-2022-23631 — prototype pollution, but server does NOT use superjson
---
## Next Steps for Deeper Phases
1. **L5/L6**: Test Stripe webhook replay with actual Stripe test API
2. **L5/L6**: Verify WebSocket JWT leakage by checking actual log configurations
3. **L5/L6**: Load test VoicePrint endpoints with large payloads
4. **L6**: Audit CSP effectiveness — `'unsafe-inline'` and `'unsafe-eval'` weaken XSS protection
5. **L7**: Supply chain analysis of npm dependencies
6. **L8**: Native app security (iOS/Android) — separate codebases
7. **L9**: Infrastructure and deployment security

View File

@@ -0,0 +1,17 @@
# Balanced Verification & Cleanup
Generated: 2026-05-28T15:06:13.718Z
## Verification
- Scope: lightweight package verification; live target confirmation remains `/piolium-confirm`.
- Final finding directories: 11
- Missing report.md: none
- Missing PoC artifact: p8-001-xss-in-innerhtml, p8-002-puppeteer-ssrf, p8-003-open-redirect-return-url, p8-004-rate-limit-substring-bypass, p8-005-cors-origin-env-var, p8-006-webhook-type-coercion, p8-007-webhook-replay, p8-008-websocket-jwt-query-param, p8-009-websocket-no-origin-validation, p8-010-voiceprint-resource-exhaustion, p8-011-superjson-vulnerable-version
- Missing evidence directory: p8-001-xss-in-innerhtml, p8-002-puppeteer-ssrf, p8-003-open-redirect-return-url, p8-004-rate-limit-substring-bypass, p8-005-cors-origin-env-var, p8-006-webhook-type-coercion, p8-007-webhook-replay, p8-008-websocket-jwt-query-param, p8-009-websocket-no-origin-validation, p8-010-voiceprint-resource-exhaustion, p8-011-superjson-vulnerable-version
## Cleanup
- Removed: `piolium/tmp`, `piolium/confirm-workspace`, `piolium/findings-draft`
- Missing: `piolium/probe-workspace`, `piolium/chamber-workspace`, `piolium/adversarial-reviews`, `piolium/bypass-analysis`, `piolium/codeql-artifacts`, `piolium/codeql-queries`, `piolium/semgrep-rules`, `piolium/agentic-actions-res`, `piolium/codeql-res`, `piolium/semgrep-res`, `piolium/real-env-evidence`, `piolium/raw`, `piolium/file-records`, `piolium/attack-surface/raw`, `piolium/attack-pattern-registry.json`, `piolium/authz-coverage-gaps.md`, `piolium/merged-results.sarif`
- Cleanup summary: `piolium/attack-surface/balanced-cleanup-summary.json`

View File

@@ -0,0 +1,155 @@
# Candidate Scan
Generated by piolium at 2026-05-28T13:00:30.318Z
## Totals
- Files scanned: 730
- Candidate files: 218
- Candidate matches: 1412
- Per-file records: disabled (set PIOLIUM_FILE_RECORDS=1 to enable)
## Candidate Classes
- secret-literal: 9 match(es), max score 122. Hardcoded secret-like literal.
- command-execution: 55 match(es), max score 90. Potential command execution or shell invocation with variable input.
- dynamic-code-execution: 12 match(es), max score 90. Dynamic code execution, expression evaluation, or runtime compilation.
- raw-sql-query: 611 match(es), max score 87. Raw SQL construction or query execution that may need parameterization review.
- hidden-control-channel: 42 match(es), max score 87. Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.
- open-redirect: 2 match(es), max score 81. Redirect sink that may accept user-controlled URLs.
- path-traversal-file-access: 638 match(es), max score 79. Filesystem access using path joins or user-controllable paths.
- webhook-without-obvious-signature: 6 match(es), max score 79. Webhook handler path that should be checked for signature verification.
- unsafe-html-or-template: 17 match(es), max score 71. HTML injection sink or template escape bypass.
- ssrf-capable-request: 10 match(es), max score 71. Outbound HTTP request site that may be attacker-controlled.
- weak-token-or-crypto: 5 match(es), max score 63. Token, JWT, randomness, or crypto usage that deserves review.
- public-entrypoint: 5 match(es), max score 54. Public route, handler, controller, workflow, or operation entry point.
## Top Files
- `honker/tests/test_joblite.py`: score 2280, 41 match(es)
- `honker/tests/test_litenotify.py`: score 2200, 40 match(es)
- `honker/packages/honker-jvm/src/test/java/dev/honker/HonkerJvmTest.java`: score 1980, 36 match(es)
- `honker/packages/honker-bun/src/index.ts`: score 1905, 27 match(es)
- `honker/packages/honker-node/test/parity.test.js`: score 1815, 33 match(es)
- `honker/tests/test_scheduler.py`: score 1815, 33 match(es)
- `honker/tests/test_real_e2e_scenarios.py`: score 1810, 32 match(es)
- `honker/tests/test_extension_interop.py`: score 1760, 32 match(es)
- `honker/tests/test_stream.py`: score 1650, 30 match(es)
- `honker/tests/test_tasks.py`: score 1485, 27 match(es)
- `honker/tests/test_task_results.py`: score 1375, 25 match(es)
- `honker/tests/test_outbox.py`: score 1320, 24 match(es)
- `honker/packages/honker/python/honker/_honker.py`: score 1265, 23 match(es)
- `honker/packages/honker-node/test/basic.js`: score 1155, 21 match(es)
- `honker/packages/honker-bun/test/watcher_backends_queue_e2e.test.ts`: score 1150, 20 match(es)
- `honker/packages/honker-node/api.js`: score 1134, 18 match(es)
- `honker/packages/honker-bun/test/parity.test.ts`: score 1115, 17 match(es)
- `honker/tests/test_multiprocess.py`: score 1065, 18 match(es)
- `honker/packages/honker-bun/test/python_interop.test.ts`: score 930, 16 match(es)
- `honker/bench/real_bench.py`: score 925, 15 match(es)
- `honker/packages/honker-node/test/watcher_backends_e2e.js`: score 905, 16 match(es)
- `honker/tests/test_crash_recovery.py`: score 905, 16 match(es)
- `honker/packages/honker-bun/test/basic.test.ts`: score 880, 16 match(es)
- `honker/packages/honker-node/examples/atomic.js`: score 825, 15 match(es)
- `honker/bench/ext_bench.py`: score 770, 14 match(es)
- `honker/packages/honker-jvm/src/main/java/dev/honker/Database.java`: score 770, 14 match(es)
- `honker/packages/honker-ruby/spec/parity_spec.rb`: score 770, 14 match(es)
- `honker/tests/test_phase_mantle.py`: score 770, 14 match(es)
- `honker/tests/test_task_expiration.py`: score 715, 13 match(es)
- `honker/tests/test_task_locking.py`: score 715, 13 match(es)
- `honker/tests/test_worker_task_options.py`: score 715, 13 match(es)
- `honker/packages/honker-node/test/watcher_backends_queue_e2e.js`: score 710, 12 match(es)
- `honker/packages/honker-jvm/src/main/java/dev/honker/Queue.java`: score 660, 12 match(es)
- `honker/packages/honker-node/test/cross_lang_python_to_node.js`: score 660, 12 match(es)
- `honker/packages/honker-ruby/lib/honker.rb`: score 660, 12 match(es)
- `honker/packages/honker-ruby/spec/honker_spec.rb`: score 655, 11 match(es)
- `honker/tests/test_time_triggers_e2e.py`: score 630, 11 match(es)
- `web/src/middleware.ts`: score 630, 10 match(es)
- `web/src/routes/api/stripe/webhook.ts`: score 607, 8 match(es)
- `honker/packages/honker/python/honker/_scheduler.py`: score 605, 11 match(es)
## Highest-Ranked Matches
- secret-literal (precise, score 122) at `web/src/server/api/routers/billing.test.ts:164` - clientSecret: "cs_123_secret",
- secret-literal (precise, score 106) at `web/src/routes/(auth)/login.tsx:30` - if (!password()) errs.password = "Password is required";
- secret-literal (precise, score 106) at `web/src/routes/(auth)/reset-password.tsx:27` - if (!password()) errs.password = "Password is required";
- secret-literal (precise, score 106) at `web/src/routes/(auth)/reset-password.tsx:29` - errs.password = "Password must be at least 8 characters";
- secret-literal (precise, score 106) at `web/src/routes/(auth)/signup.tsx:66` - if (!password()) errs.password = "Password is required";
- secret-literal (precise, score 106) at `web/src/routes/(auth)/signup.tsx:68` - errs.password = "Password must be at least 8 characters";
- secret-literal (precise, score 98) at `web/src/server/services/billing.service.test.ts:116` - client_secret: "cs_123_secret",
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/examples/atomic.ts:21` - db.raw.exec(
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/src/index.ts:343` - this.raw.exec("BEGIN IMMEDIATE");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/src/index.ts:422` - raw.exec("PRAGMA busy_timeout = 5000;");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/src/index.ts:424` - raw.exec(DEFAULT_PRAGMAS);
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/src/index.ts:425` - raw.exec("SELECT honker_bootstrap()");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/src/index.ts:441` - held.raw.exec("ROLLBACK");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/src/index.ts:480` - this.raw.exec("COMMIT");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/src/index.ts:489` - this.raw.exec("ROLLBACK");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/test/parity.test.ts:68` - db.raw.exec("CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT)");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/test/parity.test.ts:82` - db.raw.exec("CREATE TABLE kv (k TEXT)");
- dynamic-code-execution (precise, score 90) at `honker/packages/honker-bun/test/parity.test.ts:94` - db.raw.exec("CREATE TABLE orders (id INTEGER PRIMARY KEY, amount INTEGER)");
- command-execution (precise, score 90) at `honker/packages/honker-go/python_interop_test.go:24` - cmd := exec.Command(p, "-c", pythonProbeScript)
- command-execution (precise, score 90) at `honker/packages/honker-go/python_interop_test.go:38` - cmd := exec.Command(p, "-c", pythonProbeScript)
- command-execution (precise, score 90) at `honker/packages/honker-go/python_interop_test.go:86` - cmd := exec.Command(python, "-c", script)
- command-execution (precise, score 90) at `honker/packages/honker-go/watcher_backends_queue_test.go:119` - cmd := exec.Command(os.Args[0], "-test.v", "-test.run", "^TestWatcherBackendQueueHelper$")
- command-execution (precise, score 90) at `honker/packages/honker-go/watcher_backends_queue_test.go:194` - cmd := exec.Command(os.Args[0], "-test.run", "^TestWatcherBackendQueueHelper$")
- command-execution (precise, score 90) at `honker/packages/honker-go/watcher_backends_queue_test.go:226` - cmd := exec.Command(os.Args[0], "-test.run", "^TestWatcherBackendQueueHelper$")
- dynamic-code-execution (precise, score 90) at `honker/scripts/test_sqlite_versions.py:103` - assert rc == SQLITE_OK, f"exec({sql!r}) failed: {rc}"
- secret-literal (precise, score 90) at `web/src/server/services/notification.service.test.ts:220` - token: "existing-token",
- secret-literal (precise, score 90) at `web/src/server/services/notification.service.test.ts:256` - token: "other-user-token",
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/admin.ts:40` - stats: adminProcedure.query(async ({ ctx }) => {
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/admin.ts:58` - blogList: adminProcedure.query(async ({ ctx }) => {
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/admin.ts:64` - .query(async ({ ctx, input }) => {
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/admin.ts:137` - userList: adminProcedure.query(async ({ ctx }) => {
- hidden-control-channel (normal, score 87) at `web/src/server/api/routers/billing.test.ts:73` - const isAuthed = t.middleware(({ ctx, next }) => {
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/billing.test.ts:80` - .query(async () => {
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/billing.test.ts:113` - .query(async ({ ctx, input }) => {
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/billing.ts:33` - getSubscription: protectedProcedure.query(async ({ ctx }) => {
- raw-sql-query (normal, score 87) at `web/src/server/api/routers/billing.ts:155` - .query(async ({ ctx, input }) => {
- open-redirect (normal, score 81) at `web/src/routes/(admin)/blog/index.tsx:32` - if (redirect()) return <Navigate href="/admin/blog/new" />;
- command-execution (precise, score 80) at `honker/bench/real_bench.py:180` - def spawn(script: str) -> subprocess.Popen:
- command-execution (precise, score 80) at `honker/bench/real_bench.py:181` - return subprocess.Popen(
- command-execution (precise, score 80) at `honker/bench/real_bench.py:212` - spawn(
- command-execution (precise, score 80) at `honker/bench/real_bench.py:224` - spawn(enqueuer_script(db_path, queue_name, rate_per_enqueuer))
- command-execution (precise, score 80) at `honker/bench/wake_latency_bench.py:83` - proc = subprocess.Popen(
- command-execution (precise, score 80) at `honker/packages/honker-bun/examples/atomic.ts:21` - db.raw.exec(
- command-execution (precise, score 80) at `honker/packages/honker-bun/src/index.ts:343` - this.raw.exec("BEGIN IMMEDIATE");
- command-execution (precise, score 80) at `honker/packages/honker-bun/src/index.ts:422` - raw.exec("PRAGMA busy_timeout = 5000;");
- command-execution (precise, score 80) at `honker/packages/honker-bun/src/index.ts:424` - raw.exec(DEFAULT_PRAGMAS);
- command-execution (precise, score 80) at `honker/packages/honker-bun/src/index.ts:425` - raw.exec("SELECT honker_bootstrap()");
- command-execution (precise, score 80) at `honker/packages/honker-bun/src/index.ts:441` - held.raw.exec("ROLLBACK");
- command-execution (precise, score 80) at `honker/packages/honker-bun/src/index.ts:480` - this.raw.exec("COMMIT");
- command-execution (precise, score 80) at `honker/packages/honker-bun/src/index.ts:489` - this.raw.exec("ROLLBACK");
- command-execution (precise, score 80) at `honker/packages/honker-bun/test/parity.test.ts:68` - db.raw.exec("CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT)");
- command-execution (precise, score 80) at `honker/packages/honker-bun/test/parity.test.ts:82` - db.raw.exec("CREATE TABLE kv (k TEXT)");
- command-execution (precise, score 80) at `honker/packages/honker-bun/test/parity.test.ts:94` - db.raw.exec("CREATE TABLE orders (id INTEGER PRIMARY KEY, amount INTEGER)");
- command-execution (precise, score 80) at `honker/packages/honker-bun/test/python_interop.test.ts:38` - const probe = spawnSync(python, ["-c", probeScript], {
- command-execution (precise, score 80) at `honker/packages/honker-bun/test/python_interop.test.ts:61` - const out = spawnSync(python, ["-c", script], {
- command-execution (precise, score 80) at `honker/packages/honker-bun/test/watcher_backends_queue_e2e.test.ts:116` - const proc = spawn(process.execPath, ["-e", workerScript(dbPath, extPath, workerId, backend)], {
- command-execution (precise, score 80) at `honker/packages/honker-bun/test/watcher_backends_queue_e2e.test.ts:152` - const res = spawnSync(process.execPath, ["-e", script], {
- command-execution (precise, score 80) at `honker/packages/honker-node/index.js:56` - return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
- command-execution (precise, score 80) at `honker/packages/honker-node/native.js:56` - return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
- command-execution (precise, score 80) at `honker/packages/honker-node/test/cross_lang_shared.js:28` - return spawn(PYTHON, ['-c', script], { stdio });
- command-execution (precise, score 80) at `honker/packages/honker-node/test/watcher_backends_e2e.js:29` - return spawn(process.execPath, ['-e', script], {
- command-execution (precise, score 80) at `honker/packages/honker-node/test/watcher_backends_queue_e2e.js:38` - return spawn(process.execPath, ['-e', script], {
- command-execution (precise, score 80) at `honker/packages/honker-node/test/watcher_backends_queue_e2e.js:155` - const res = spawnSync(process.execPath, ['-e', script], {
- command-execution (precise, score 80) at `honker/packages/honker-ruby/ext/honker/extconf.rb:24` - cargo_found = system("cargo", "--version", out: File::NULL, err: File::NULL)
- command-execution (precise, score 80) at `honker/packages/honker-ruby/ext/honker/extconf.rb:48` - system(
- command-execution (precise, score 80) at `honker/packages/honker-ruby/spec/honker_spec.rb:176` - pid = Process.spawn(
- command-execution (precise, score 80) at `honker/packages/honker-ruby/spec/honker_spec.rb:191` - Process.spawn(
- command-execution (precise, score 80) at `honker/packages/honker-ruby/spec/railtie_spec.rb:36` - out = IO.popen([RbConfig.ruby, "-e", script], &:read)
- command-execution (precise, score 80) at `honker/scripts/test_sqlite_versions.py:44` - out = subprocess.check_output(["otool", "-L", mod_path], text=True)
- command-execution (precise, score 80) at `honker/scripts/test_sqlite_versions.py:103` - assert rc == SQLITE_OK, f"exec({sql!r}) failed: {rc}"
- command-execution (precise, score 80) at `honker/tests/test_crash_recovery.py:54` - return subprocess.Popen(
- command-execution (precise, score 80) at `honker/tests/test_cross_process_wake_latency.py:72` - proc = subprocess.Popen(
- command-execution (precise, score 80) at `honker/tests/test_fault_injection.py:112` - subprocess.run(
- command-execution (precise, score 80) at `honker/tests/test_fault_injection.py:143` - subprocess.run(["umount", str(mount_dir)], check=False)
- command-execution (precise, score 80) at `honker/tests/test_joblite.py:79` - return subprocess.Popen(
- command-execution (precise, score 80) at `honker/tests/test_multiprocess.py:63` - return subprocess.run(
- command-execution (precise, score 80) at `honker/tests/test_multiprocess.py:219` - return subprocess.run(
- command-execution (precise, score 80) at `honker/tests/test_multiprocess.py:277` - return subprocess.run(
- command-execution (precise, score 80) at `honker/tests/test_real_e2e_scenarios.py:270` - return subprocess.Popen(
- command-execution (precise, score 80) at `honker/tests/test_real_e2e_scenarios.py:279` - return subprocess.run(
## Custom Matchers
Project matchers can be added at `piolium/matchers.json`, `piolium/custom-matchers.json`, or `.piolium-matchers.json`.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
# Lite Recon — Q0
Generated by piolium at 2026-05-28T13:00:30.024Z
## Target
- Path: `/Users/mike/Code/Kordant`
- Repository: (unknown)
- Total files (scanned): 1039
- Total bytes (scanned): 5.3 MB
## Git
- Commit: 26d9f8b050969dfaa2c9dfb714a872160b7db382
- Branch: master
- History available: true
Recent commits:
```
26d9f8b clear references
1e1773c oof
5214412 get to prod tasks
04e8396 fix landing scroll
3bcbdae fix stripe configuration
7260975 clear old assets, new ci/cd flow
8281500 mostly android
9ee3d53 final
aacb800 name refactor
8ac2ce5 reduced nesting
```
## Languages
- TypeScript: 279 file(s)
- Kotlin: 98 file(s)
- Swift: 76 file(s)
- Java: 72 file(s)
- Python: 56 file(s)
- JavaScript: 25 file(s)
- C#: 21 file(s)
- Ruby: 19 file(s)
- Rust: 17 file(s)
- Go: 10 file(s)
- Shell: 8 file(s)
- C++: 4 file(s)
## Build / Project Manifests
- `android/app/build.gradle.kts`
- `android/build.gradle.kts`
- `browser-ext/package.json`
- `honker/Cargo.toml`
- `honker/bench/wal_index_methods/Cargo.toml`
- `honker/honker-core/Cargo.toml`
- `honker/honker-extension/Cargo.toml`
- `honker/packages/honker/Cargo.toml`
- `honker/packages/honker/pyproject.toml`
- `honker/packages/honker-bun/package.json`
- `honker/packages/honker-go/go.mod`
- `honker/packages/honker-jvm/pom.xml`
- `honker/packages/honker-kotlin/pom.xml`
- `honker/packages/honker-node/Cargo.toml`
- `honker/packages/honker-node/npm/darwin-arm64/package.json`
- `honker/packages/honker-node/npm/darwin-x64/package.json`
- `honker/packages/honker-node/npm/linux-arm64-gnu/package.json`
- `honker/packages/honker-node/npm/linux-x64-gnu/package.json`
- `honker/packages/honker-node/package.json`
- `honker/packages/honker-rs/Cargo.toml`
- `honker/packages/honker-ruby/Gemfile`
- `honker/pyproject.toml`
- `package.json`
- `scheduler/Dockerfile`
- `scheduler/docker-compose.yml`
- `web/Dockerfile`
- `web/package.json`

View File

@@ -0,0 +1,147 @@
# Manual Attack Surface Inventory: Kordant `web/`
Generated: 2026-05-28
Scope: Kordant web application (SolidStart + tRPC + Drizzle ORM + Stripe + WebSocket)
---
## Entry Points
### HTTP Routes
| Route | Method | Auth | Description | Key File |
|-------|--------|------|-------------|----------|
| `/api/trpc/[trpc]` | POST | Mixed (public/protected/admin) | tRPC endpoint — all tRPC procedures flow here | `web/src/routes/api/trpc/[trpc].ts` |
| `/api/stripe/webhook` | POST | None (Stripe signature) | Stripe webhook handler | `web/src/routes/api/stripe/webhook.ts` |
| `/api/stripe/session-status` | GET | None (public) | Check Stripe checkout session status | `web/src/routes/api/stripe/session-status.ts` |
| `/api/health` | GET | None | Health check | `web/src/routes/api/health.ts` |
| `/api/ready` | GET | None | Readiness check | `web/src/routes/api/ready.ts` |
| `/auth/callback` | GET | None | Clerk OAuth callback | `web/src/routes/auth/callback.tsx` |
| `/billing/checkout` | GET | None | Checkout page | `web/src/routes/billing/checkout.tsx` |
| `/billing/return` | GET | None | Post-payment return page | `web/src/routes/billing/return.tsx` |
### tRPC Routers (16 total, key ones)
| Router | Auth Type | Key Procedures | Key File |
|--------|-----------|----------------|----------|
| `extensionRouter` | Public | `getAuthStatus`, `linkDevice`, `reportPhishing` | `web/src/server/api/routers/extension.ts` |
| `billingRouter` | Protected | `createCheckoutSession`, `createPortalSession`, `cancelSubscription` | `web/src/server/api/routers/billing.ts` |
| `adminRouter` | Admin | `blogCreate`, `blogUpdate`, `userUpdateRole`, `stats` | `web/src/server/api/routers/admin.ts` |
| `voiceprintRouter` | Protected | `createEnrollment`, `analyzeAudio` | `web/src/server/api/routers/voiceprint.ts` |
| `darkwatchRouter` | Protected | `addWatchlistItem`, `runScan` | `web/src/server/api/routers/darkwatch.ts` |
| `userRouter` | Protected | Profile management | `web/src/server/api/routers/user.ts` |
| `reportsRouter` | Protected | Report generation | `web/src/server/api/routers/reports.ts` |
| `spamshieldRouter` | Protected | Spam analysis | `web/src/server/api/routers/spamshield.ts` |
### WebSocket
| Endpoint | Auth | Description | Key File |
|----------|------|-------------|----------|
| `ws://host:3001/?token=<JWT>` | JWT in query param | Real-time alert broadcast | `web/src/server/websocket.ts` |
---
## Public Routes / URLs (No Auth Required)
1. `/api/trpc/extension.getAuthStatus` — Public tRPC query
2. `/api/trpc/extension.linkDevice` — Public tRPC mutation
3. `/api/trpc/extension.reportPhishing` — Public tRPC mutation
4. `/api/stripe/webhook` — Stripe webhook (signature-verified, no user auth)
5. `/api/stripe/session-status` — Stripe session status check
6. `/auth/callback` — Clerk OAuth callback
7. `/billing/checkout` — Stripe Checkout page
8. `/billing/return` — Post-payment return
9. `/api/health`, `/api/ready` — Health checks
10. Static pages: `/`, `/pricing`, `/features`, `/blog`, `/privacy`, `/terms`, `/about`, `/ads`
---
## Attacker Sources
| Source | Capability | Access Level |
|--------|-----------|-------------|
| External attacker (internet) | Send HTTP requests, craft tRPC payloads, spoof Stripe webhooks, connect to WebSocket | Unauthenticated |
| Compromised browser extension | Make tRPC calls with stored API key | Authenticated (as extension-linked user) |
| Insider (non-admin user) | Access to own data via tRPC, WebSocket | Authenticated (user role) |
| Insider (admin) | Full admin panel, blog management, user role changes | Authenticated (admin role) |
---
## Sinks
| Sink | File | Description | Risk |
|------|------|-------------|------|
| Drizzle ORM queries | Multiple routers | SQL execution via `db.select`, `db.insert`, `db.update`, `db.delete` | SQL injection if user input reaches query builders |
| Stripe API calls | `billing.service.ts`, `stripe.ts` | Payment operations, subscription management | Payment manipulation, webhook replay |
| File system (audio) | `voiceprint/storage.ts` | `writeFile` for audio storage | Path traversal, disk exhaustion |
| File system (reports) | `reports/generator.ts` | `writeFileSync` for PDF output | Path traversal |
| Puppeteer | `reports/generator.ts` | `page.setContent(html)` — renders HTML to PDF | SSRF, XSS via crafted HTML |
| External API calls | `darkwatch/scan.engine.ts` | `fetch()` to HIBP, SecurityTrails, Censys, Shodan | SSRF if user-controlled URLs reach fetch |
| WebSocket messages | `websocket.ts` | `ws.send()` for alert broadcast | Alert flooding |
| Database writes (webhook) | `billing.service.ts` | `db.insert(subscriptions)` on webhook events | Duplicate subscription creation |
---
## Hidden Control Channels
| Channel | File | Description | Risk |
|---------|------|-------------|------|
| `process.env.APP_URL` | `middleware.ts` | Trusted as CORS origin | CORS origin injection if env is mutable |
| `process.env.STRIPE_WEBHOOK_SECRET` | `webhook.ts` | Stripe signature verification key | Webhook replay if key is leaked |
| JWT in `?token=` query param | `websocket.ts` | WebSocket auth token visible in logs | Token leakage via proxy/access logs |
| Rate limiter path heuristic | `utils.ts` | `path.includes(p)` for sensitive paths | Rate limit bypass via path manipulation |
| `scanStates` Map (in-memory) | `darkwatch.service.ts` | Scan state stored in process memory | State loss on restart, no persistence |
| `userSockets` Map (in-memory) | `websocket.ts` | Socket connections stored in process memory | Memory exhaustion, no connection limit per user |
---
## Middleware / Proxy Assumptions
| Layer | File | Assumption | Break Impact |
|-------|------|-----------|-------------|
| Security headers | `middleware.ts` | Sets CSP, HSTS, X-Frame-Options, etc. | Missing headers weaken defense-in-depth |
| CORS | `middleware.ts` | Validates origin against whitelist | CORS misconfiguration if APP_URL is attacker-controlled |
| Clerk auth | `middleware.ts` | Sets `ctx.user` from Clerk session | Auth bypass if Clerk session validation fails |
| tRPC procedure types | `utils.ts` | `publicProcedure`, `protectedProcedure`, `adminProcedure` enforce auth | Privilege escalation if middleware is bypassed |
| Rate limiting | `utils.ts` | Redis sorted set, tier-based limits | DoS if rate limit is bypassed |
| Valibot schemas | `schemas/*.ts` | Input validation before service layer | Injection if schema is missing or weak |
---
## Key Files
### Authentication & Authorization
- `web/src/middleware.ts` — Clerk middleware, security headers, CORS
- `web/src/server/api/utils.ts` — tRPC procedure types, rate limiting middleware
- `web/src/server/auth/jwt.ts` — JWT verification
- `web/src/server/auth/session.ts` — Session management
### Stripe / Billing
- `web/src/routes/api/stripe/webhook.ts` — Stripe webhook entry point
- `web/src/server/services/billing.service.ts` — Billing service (webhook handler, checkout, subscriptions)
- `web/src/server/stripe.ts` — Stripe client initialization
- `web/src/server/api/schemas/billing.ts` — Billing input schemas
### tRPC Routers
- `web/src/server/api/routers/admin.ts` — Admin procedures (blog, users)
- `web/src/server/api/routers/billing.ts` — Billing procedures
- `web/src/server/api/routers/extension.ts` — Extension procedures (PUBLIC)
- `web/src/server/api/routers/voiceprint.ts` — Voice print procedures
- `web/src/server/api/routers/darkwatch.ts` — DarkWatch procedures
### Services
- `web/src/server/services/voiceprint.service.ts` — Voice analysis pipeline
- `web/src/server/services/voiceprint/storage.ts` — Audio file storage
- `web/src/server/services/darkwatch.service.ts` — DarkWatch scan orchestration
- `web/src/server/services/darkwatch/scan.engine.ts` — External API scanning
- `web/src/server/services/reports/generator.ts` — Report generation (Puppeteer)
### Real-Time
- `web/src/server/websocket.ts` — WebSocket server (port 3001)
### Database
- `web/src/server/db/index.ts` — Drizzle ORM database connection
- `web/src/server/db/schema/*.ts` — Database schema definitions
### Rate Limiting
- `web/src/server/lib/ratelimit.ts` — Redis-based rate limiter

View File

@@ -0,0 +1,94 @@
# Source-Sink Flow Analysis Summary
**Generated**: 2026-05-28
**Phase**: L3 (SAST — Greppable Fallback)
**Target**: Kordant monorepo (web/, browser-ext/)
---
## Scan Overview
| Metric | Value |
|--------|-------|
| Files scanned | 730 |
| Candidate files | 218 |
| Candidate matches | 1412 |
| Draft findings produced | 12 |
| Keep (enriched) | 10 |
| Drop (enriched) | 2 |
## Candidate Classes Breakdown
| Class | Matches | High-Score Examples | Enriched | Kept | Dropped |
|-------|---------|---------------------|----------|------|---------|
| `raw-sql-query` | 611 | admin.ts, billing.ts, blog.ts (`.query()` calls) | N/A | 0 | 0 |
| `path-traversal-file-access` | 638 | blog/[slug].tsx, ext_bench.py, api.js (`.join()`) | 2 | 1 | 1 |
| `hidden-control-channel` | 42 | middleware.ts (CORS origin), trpc.ts (auth headers) | 4 | 3 | 1 |
| `command-execution` | 55 | test files, benchmarks (subprocess.Popen, exec.Command) | N/A | 0 | 0 |
| `dynamic-code-execution` | 12 | honker-bun/raw.exec(), test_sqlite_versions.py | N/A | 0 | 0 |
| `secret-literal` | 9 | billing.test.ts, auth routes (password error messages) | N/A | 0 | 0 |
| `unsafe-html-or-template` | 17 | blog/[slug].tsx (innerHTML), auth test files | 1 | 1 | 0 |
| `ssrf-capable-request` | 10 | billing/return.tsx (fetch), scan.engine.ts (external API) | 1 | 1 | 0 |
| `webhook-without-obvious-signature` | 6 | stripe/webhook.ts | 1 | 1 | 0 |
| `open-redirect` | 2 | blog/index.tsx, app.tsx | 1 | 1 | 0 |
| `weak-token-or-crypto` | 5 | PasswordInput.tsx (Math.random) | 1 | 0 | 1 |
| `public-entrypoint` | 5 | extensionRouter procedures | N/A | 0 | 0 |
## Key Filtering Decisions
### Dropped: `raw-sql-query` (611 matches)
- **Reason**: False positives — 99%+ are tRPC `.query()` method calls (not raw SQL), not Drizzle `sql<>` tag usage. The tRPC router `.query()` method is a framework method, not a SQL execution sink.
- **Exception**: The one real `sql<>` usage in admin.ts:47 was separately assessed as p4-012 (low severity, latent risk).
### Dropped: `command-execution` (55 matches)
- **Reason**: All matches are in test files (`test_*.py`, `*_test.go`, `*_spec.rb`) or benchmark scripts. These are development-time subprocess calls, not production attack surface.
### Dropped: `dynamic-code-execution` (12 matches)
- **Reason**: All matches are SQLite raw SQL execution methods (`raw.exec()`, `exec()`) in the honker package or test files. These are database operations, not code execution sinks.
### Dropped: `secret-literal` (9 matches)
- **Reason**: All matches are test data (`billing.test.ts`, `notification.service.test.ts`) or password validation error messages (`login.tsx`, `signup.tsx``"Password is required"` is not a secret).
### Dropped: `path-traversal-file-access` — 1 kept, 1 dropped
- **Kept (p4-005)**: `voiceprint/storage.ts``userId` not validated before `path.join()`, enabling arbitrary file write
- **Dropped**: `blog/[slug].tsx` — false positives from `.join("")` string concatenation (not filesystem path joins)
### Dropped: `weak-token-or-crypto` — 0 kept, 1 dropped
- **Dropped**: `PasswordInput.tsx``Math.random()` is used for generating HTML input element IDs, not for CSRF tokens or cryptographic purposes. The id is only used for `<label for=...>` association. Not a security issue.
## DFD/CFD Coverage
| DFD/CFD Slice | Covered by Findings | Notes |
|---------------|---------------------|-------|
| DFD-1: tRPC → Drizzle ORM | Partial (p4-012) | CVE-2026-39356 surface noted but no active injection found |
| DFD-2: VoicePrint Pipeline | Full (p4-005) | Path traversal in audio storage |
| DFD-3: Browser Ext → tRPC | Partial (p4-008) | superjson vulnerability in extension only |
| DFD-4: WebSocket Alerts | Full (p4-007, p4-011) | JWT leak + no Origin validation |
| DFD-5: Stripe Webhook | Full (p4-006) | Unsafe type coercion |
| CFD-1: Auth Flow | Partial (p4-011) | JWT in query param + no Origin check |
| CFD-2: Authz Flow | Full (p4-001) | Unvalidated role mutation |
| CFD-3: Rate Limiting | Full (p4-009) | Substring-based path matching |
## Custom Analysis Targets (Domain Attack Research)
| Target | File | Finding |
|--------|------|---------|
| CORS env var trust | `web/src/middleware.ts` | p4-003 |
| XSS via markdown rendering | `web/src/routes/blog/[slug].tsx` | p4-004 |
| Puppeteer SSRF | `web/src/server/services/reports/generator.ts` | p4-002 |
| Stripe webhook type safety | `web/src/server/services/billing.service.ts` | p4-006 |
| Return URL open redirect | `web/src/server/api/schemas/billing.ts` | p4-010 |
| superjson CVE | `browser-ext/package.json` | p4-008 |
| Rate limit bypass | `web/src/server/api/utils.ts` | p4-009 |
| WebSocket Origin check | `web/src/server/websocket.ts` | p4-011 |
| JWT in WS query param | `web/src/server/websocket.ts` | p4-007 |
| Admin role mutation | `web/src/server/api/routers/admin.ts` | p4-001, p4-012 |
| Audio path traversal | `web/src/server/services/voiceprint/storage.ts` | p4-005 |
## Agentic Actions Audit
Analyzed 2 GitHub Actions workflow files:
- `.github/workflows/ci.yml` — No AI agent actions found
- `.github/workflows/deploy.yml` — No AI agent actions found
**Result**: 0 findings. Standard CI/CD workflows with no Claude Code, Gemini CLI, OpenAI Codex, or GitHub AI Inference integrations.