security sweep
This commit is contained in:
272
piolium/attack-surface/advisory-summary.md
Normal file
272
piolium/attack-surface/advisory-summary.md
Normal 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.
|
||||
143
piolium/attack-surface/balanced-chamber-summary.md
Normal file
143
piolium/attack-surface/balanced-chamber-summary.md
Normal 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
|
||||
33
piolium/attack-surface/balanced-cleanup-summary.json
Normal file
33
piolium/attack-surface/balanced-cleanup-summary.json
Normal 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"
|
||||
]
|
||||
}
|
||||
75
piolium/attack-surface/balanced-consolidation-manifest.json
Normal file
75
piolium/attack-surface/balanced-consolidation-manifest.json
Normal 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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
140
piolium/attack-surface/balanced-probe-summary.md
Normal file
140
piolium/attack-surface/balanced-probe-summary.md
Normal 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
|
||||
17
piolium/attack-surface/balanced-verification-summary.md
Normal file
17
piolium/attack-surface/balanced-verification-summary.md
Normal 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`
|
||||
155
piolium/attack-surface/candidates-summary.md
Normal file
155
piolium/attack-surface/candidates-summary.md
Normal 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`.
|
||||
1412
piolium/attack-surface/candidates.jsonl
Normal file
1412
piolium/attack-surface/candidates.jsonl
Normal file
File diff suppressed because it is too large
Load Diff
1103
piolium/attack-surface/knowledge-base-report.md
Normal file
1103
piolium/attack-surface/knowledge-base-report.md
Normal file
File diff suppressed because it is too large
Load Diff
76
piolium/attack-surface/lite-recon.md
Normal file
76
piolium/attack-surface/lite-recon.md
Normal 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`
|
||||
147
piolium/attack-surface/manual-attack-surface-inventory.md
Normal file
147
piolium/attack-surface/manual-attack-surface-inventory.md
Normal 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
|
||||
94
piolium/attack-surface/source-sink-flows-all-severities.md
Normal file
94
piolium/attack-surface/source-sink-flows-all-severities.md
Normal 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.
|
||||
Reference in New Issue
Block a user