Add attack surface audit artifacts
Some checks failed
CI / Validate (pull_request) Has been cancelled
Some checks failed
CI / Validate (pull_request) Has been cancelled
- Add advisory, entrypoint, and candidate scan outputs - Capture dependency intelligence and cross-service attack surface notes
This commit is contained in:
parent
a35a757622
commit
47a5adca90
26 changed files with 2807 additions and 0 deletions
66
piolium/attack-surface/advisory-summary.md
Normal file
66
piolium/attack-surface/advisory-summary.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Stage 01 Advisory & Dependency Intelligence Summary
|
||||||
|
|
||||||
|
## Scope and coverage
|
||||||
|
- Target: `/Users/kell/dev/islandflow`.
|
||||||
|
- Repository identity resolution: `islandflow` via basename fallback. No `owner/repo` was resolved from env, git remote, or manifests, so repo-specific GitHub Security Advisory API queries were skipped.
|
||||||
|
- Local git history: available. Repo commit search found `8464287 fix cves from forgejo issue 10 with dependency upgrades` and index commit `bff5334`, indicating recent dependency security remediation.
|
||||||
|
- First-party advisory signals: no project-owned CVE/GHSA IDs found outside installed `node_modules` and piolium artifacts.
|
||||||
|
- NVD keyword query for `islandflow`: 0 results.
|
||||||
|
- OSV batch query against npm dependencies: 116 historical advisories across dependency names. These are dependency-history signals, not all applicable to the pinned/ranged versions.
|
||||||
|
|
||||||
|
## Advisory inventory highlights
|
||||||
|
|
||||||
|
| Package/component | Advisory | Severity | CVE/alias | Affected / fixed range from OSV | Relevance to Islandflow |
|
||||||
|
|---|---:|---|---|---|---|
|
||||||
|
| `next` / web middleware | GHSA-f82v-jwr5-mffw | CRITICAL | CVE-2025-29927 | introduced 13.0.0; fixed 13.5.9 | Current `next ^16.2.6` appears beyond fixed range, but this class maps directly to auth/route middleware review. |
|
||||||
|
| `next` / script rendering | GHSA-gx5p-jg67-6x7h | MODERATE | CVE-2026-44580 | introduced 13.0.0; fixed 15.5.16 | Current range appears beyond fixed range; still informs XSS review for UI data rendering. |
|
||||||
|
| `next` / middleware redirect | GHSA-4342-x723-ch2f | MODERATE | CVE-2025-57822 | introduced 0.9.9; fixed 14.2.32 | Current range appears beyond fixed range; SSRF/redirect behavior remains important around API origin controls. |
|
||||||
|
| `next` / authorization | GHSA-7gfc-8cq8-jh5f | HIGH | CVE-2024-51479 | introduced 9.5.5; fixed 14.2.15 | Current range appears beyond fixed range; historical pattern is auth bypass in path/middleware matching. |
|
||||||
|
| `ws` | GHSA-2mhh-w6q8-5hxw | LOW | CVE-2016-10518 | introduced 0; fixed 1.0.1 | Current `ws ^8.21.0` appears beyond fixed range; websocket parsing and resource handling remain high-value. |
|
||||||
|
| `redis` | GHSA-35q2-47q7-3pc3 | HIGH | CVE-2021-29469 | introduced 2.6.0; fixed 3.1.1 | Current `redis ^5.10.0` appears beyond fixed range; Redis is security-relevant for hot caches/rolling stats. |
|
||||||
|
| `zod` | GHSA-m95q-7qp3-xv42 | MODERATE | CVE-2023-4316 | introduced 0; fixed 3.22.3 | Current `zod ^3.23.8` appears beyond fixed range; validates DoS risk from schema parsing. |
|
||||||
|
| `nats` | GHSA-prmc-5v5w-c465 | CRITICAL | none | introduced 2.0.0-201; fixed 2.0.0-209 | Current `nats ^2.24.0` appears beyond fixed range; credentials/TLS configuration remains critical. |
|
||||||
|
| `electron` | GHSA-2q4g-w47c-4674 | HIGH | CVE-2020-15174 | introduced 8.0.0-beta.0; fixed 8.5.1 | Current `electron ^39.2.0` appears beyond fixed range; desktop navigation/origin controls remain core. |
|
||||||
|
| `react-dom` | GHSA-mvjj-gqq2-p4hw | MODERATE | CVE-2018-6341 | introduced 16.0.0; fixed 16.0.1 | Current `react-dom ^19.2.0` appears beyond fixed range; historical XSS pattern relevant to rendering market/news data. |
|
||||||
|
|
||||||
|
OSV historical advisory counts by dependency name: `next` 55, `electron` 48, `ws` 6, `nats` 2, `react` 2, `react-dom` 1, `redis` 1, `zod` 1.
|
||||||
|
|
||||||
|
## Dependency intelligence
|
||||||
|
- Runtime stack: Bun workspaces, TypeScript, Next.js web frontend, Electron shell, multiple TS services, plus optional Python sidecars for IBKR/Databento options replay.
|
||||||
|
- Security-relevant direct dependencies:
|
||||||
|
- `next ^16.2.6`, `react ^19.2.0`, `react-dom ^19.2.0`: public web UI and route surface. Historical patterns: auth bypass, middleware matching, SSRF redirects, cache poisoning, XSS.
|
||||||
|
- `electron ^39.2.0`: desktop shell that loads hosted/local app. Historical patterns: navigation escape, protocol/IPC misuse, sandbox and origin boundary failures.
|
||||||
|
- `ws ^8.21.0`: live market/news ingest websocket clients. Risk: parser/resource exhaustion and trust in third-party market data.
|
||||||
|
- `nats ^2.24.0`: event bus/JetStream control plane. Risk: credential exposure, subject authorization, replay/control messages.
|
||||||
|
- `redis ^5.10.0`: hot caches and rolling metrics. Risk: cache poisoning, key construction, TTL abuse, DoS.
|
||||||
|
- `@clickhouse/client ^0.2.6`: durable event/history store. Risk: query construction, cursor pagination, large result-set DoS.
|
||||||
|
- `zod ^3.23.8`: schema validation. Risk: validation DoS and inconsistent parse/sanitize boundaries.
|
||||||
|
- `@msgpack/msgpack ^3.1.3`: binary decode in options ingest. Risk: malformed binary/resource exhaustion.
|
||||||
|
- `@pierre/diffs ^1.2.2`: low-visibility dependency; should be inspected for maintainer health and reachable use.
|
||||||
|
- Root overrides pin `postcss`, `tar`, and `tmp`, suggesting prior remediation of known transitive CVEs.
|
||||||
|
|
||||||
|
## Architecture hints
|
||||||
|
- Components: `apps/web` Next.js UI; `apps/desktop` Electron shell; services for API, options/equities/news ingest, candles, compute, replay, refdata, eod-enricher; shared packages for bus, config, observability, storage, types.
|
||||||
|
- Transports/data stores: REST, WebSocket, NATS/JetStream, ClickHouse HTTP, Redis, external Alpaca websockets/REST, Databento/IBKR Python sidecars, Docker Compose deployment.
|
||||||
|
- Trust boundaries: internet/user-facing web and API; desktop-local Electron-to-hosted-app boundary; third-party market data feeds; internal NATS subjects; ClickHouse/Redis persistence; deployment/runtime environment variables containing API keys.
|
||||||
|
- Highest-risk flows for later stages:
|
||||||
|
1. API REST/WebSocket endpoints handling cursor pagination, replay/history, raw `security=all` debug views, and live channel fanout.
|
||||||
|
2. Ingest adapters accepting external websocket/binary/sidecar data before schema normalization and NATS publication.
|
||||||
|
3. NATS subject publishing/subscription and replay service controls that can reintroduce stale or attacker-controlled events.
|
||||||
|
4. Electron shell origin allowlist, navigation controls, preload/IPC exposure, and `ISLANDFLOW_DESKTOP_START_URL` handling.
|
||||||
|
5. ClickHouse query construction for filters, cursors, symbols, and time windows.
|
||||||
|
|
||||||
|
## Pattern analysis and audit targeting
|
||||||
|
- Component heatmap from dependency history: web/Next.js is hottest (55 OSV advisories), Electron desktop second (48), websocket/event-ingest layer third (`ws`, `nats`).
|
||||||
|
- Recurring bug classes to hunt: auth bypass/middleware confusion, XSS/rendering injection, SSRF/open redirect, DoS/resource exhaustion, cache poisoning, navigation/IPC boundary bypass.
|
||||||
|
- Attack surface trends: network inputs dominate: HTTP routes, WebSocket streams, NATS messages, Redis/cache keys, ClickHouse query parameters, and external market-data payloads.
|
||||||
|
- Patch-quality signal: repeated Next.js and Electron advisory history means later review should assume framework boundary fixes are historically bypass-prone and verify application-level compensating controls.
|
||||||
|
- Recommended next-stage focus: prioritize DFD slices for API live/history/replay, ingest-to-NATS normalization, Electron shell boundary, and ClickHouse storage query paths. Mandatory review chambers should include auth bypass, XSS, SSRF/open redirect, parser/validation DoS, and message/cache poisoning.
|
||||||
|
|
||||||
|
## Artifacts produced
|
||||||
|
- `piolium/attack-surface/deps.tsv` — direct dependency inventory.
|
||||||
|
- `piolium/attack-surface/npm-dep-names.txt` — unique npm package names queried.
|
||||||
|
- `piolium/attack-surface/osv-query.json` and `osv-querybatch.json` — OSV batch request/response.
|
||||||
|
- `piolium/attack-surface/osv-findings.tsv` — flattened OSV package/advisory list.
|
||||||
|
- `piolium/attack-surface/osv-selected-details.json` — detail records for representative advisories.
|
||||||
|
- `piolium/attack-surface/nvd-islandflow.json` — NVD keyword response.
|
||||||
59
piolium/attack-surface/architecture-entrypoints.md
Normal file
59
piolium/attack-surface/architecture-entrypoints.md
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Islandflow Architecture Entrypoints Inventory
|
||||||
|
|
||||||
|
## Public/Network Routes
|
||||||
|
|
||||||
|
### API service (`services/api/src/index.ts`, Bun on `API_HOST:API_PORT`, default `127.0.0.1:4000`)
|
||||||
|
- Health: `GET /health`.
|
||||||
|
- Synthetic admin (Bearer token expected): `GET /admin/synthetic/status`, `GET /admin/synthetic/control`, `PUT /admin/synthetic/control`.
|
||||||
|
- Recent/live REST: `GET /prints/options`, `/nbbo/options`, `/prints/equities`, `/prints/equities/range`, `/quotes/equities`, `/candles/equities`, `/joins/equities`, `/dark/inferred`, `/flow/packets`, `/flow/smart-money`, `/flow/classifier-hits`, `/flow/alerts`, `/news`.
|
||||||
|
- Context/lookup: `GET /flow/packets/:id`, `GET /flow/alerts/:trace_id/context`, alert-context helper paths, `GET /option-prints/by-trace`, `GET /equity-joins/by-id`, `POST /lookup/options-support`.
|
||||||
|
- History: `GET /history/options`, `/history/nbbo`, `/history/equities`, `/history/equity-quotes`, `/history/equity-joins`, `/history/flow`, `/history/smart-money`, `/history/classifier-hits`, `/history/alerts`, `/history/inferred-dark`, `/history/news`.
|
||||||
|
- Replay: `GET /replay/options`, `/replay/nbbo`, `/replay/equities`, `/replay/equity-quotes`, `/replay/equity-candles`, `/replay/equity-joins`, `/replay/inferred-dark`, `/replay/flow`, `/replay/smart-money`, `/replay/classifier-hits`, `/replay/alerts`.
|
||||||
|
- WebSockets: `GET /ws/options`, `/ws/options-nbbo`, `/ws/equities`, `/ws/equity-candles`, `/ws/equity-quotes`, `/ws/equity-joins`, `/ws/inferred-dark`, `/ws/flow`, `/ws/classifier-hits`, `/ws/smart-money`, `/ws/alerts`, `/ws/live`.
|
||||||
|
|
||||||
|
### Web app (`apps/web/app`, Next.js on port 3000)
|
||||||
|
- Pages: `/`, `/tape`, `/signals`, `/charts`, `/news`, `/options`, `/replay`, `/frontend-cooker`.
|
||||||
|
- Next API admin proxy: `GET /api/admin/synthetic/status`, `GET|PUT /api/admin/synthetic/control`.
|
||||||
|
|
||||||
|
### Desktop (`apps/desktop`)
|
||||||
|
- Loads `https://flow.deltaisland.io` by default or trusted local/prod URL from `ISLANDFLOW_DESKTOP_START_URL`.
|
||||||
|
- Allows external `http:`/`https:` links only when navigation source is trusted app origin.
|
||||||
|
|
||||||
|
## Attacker-Controlled Sources
|
||||||
|
- URL path segments: packet IDs, alert trace IDs, by-id/by-trace arrays.
|
||||||
|
- Query params: `limit`, `before_ts`, `before_seq`, `after_ts`, `after_seq`, `trace_prefix`, option/equity filters, candle intervals/ranges/cache flag, source selectors.
|
||||||
|
- Request bodies: `PUT /admin/synthetic/control`, `POST /lookup/options-support`, WS `/ws/live` messages.
|
||||||
|
- WebSocket connection count, channels, subscription messages.
|
||||||
|
- External feed payloads: Alpaca options/equities/news REST+WS, Databento replay JSONL from Python, IBKR JSONL from Python, msgpack frames.
|
||||||
|
- Environment: `NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_SYNTHETIC_ADMIN`, `SYNTHETIC_ADMIN_TOKEN`, API/NATS/ClickHouse/Redis URLs, bind IPs, provider API keys, adapter choices, Python binary paths, Electron start URL.
|
||||||
|
- Internal network inputs: NATS subjects/KV, Redis cache contents, ClickHouse rows.
|
||||||
|
- CI/deploy inputs: branches/refs/env secrets, docker compose env overrides.
|
||||||
|
|
||||||
|
## High-Value Sinks
|
||||||
|
- ClickHouse `client.query({ query })`, `exec`, `insert`: `packages/storage/src/clickhouse.ts`.
|
||||||
|
- NATS `publishJson`, `subscribeJson`, stream/KV helpers: `packages/bus/src/**`.
|
||||||
|
- Redis hot live/candle cache: `services/api/src/live.ts`, candle service.
|
||||||
|
- Browser render sinks for news `content_html`, URLs, explanations/profile JSON: `apps/web/app/**`.
|
||||||
|
- Admin state mutation: `writeSyntheticControlState`, `openSyntheticControlKv`.
|
||||||
|
- Electron `BrowserWindow.loadURL`, `shell.openExternal`.
|
||||||
|
- Child execution: `Bun.spawn` in `services/ingest-options/src/adapters/databento.ts`, `ibkr.ts`, deployment scripts.
|
||||||
|
- Logs containing provider errors, URLs, trace IDs, and potential secret-bearing env/config.
|
||||||
|
|
||||||
|
## Key Source Files for Later Phases
|
||||||
|
- API routing/auth/WS: `services/api/src/index.ts`, `services/api/src/live.ts`, `services/api/src/synthetic-control.ts`, `services/api/src/option-queries.ts`, `services/api/src/alert-context.ts`.
|
||||||
|
- Storage/query construction: `packages/storage/src/clickhouse.ts`, all `packages/storage/src/*.ts` table modules.
|
||||||
|
- Bus/subjects/control: `packages/bus/src/index.ts`, `jetstream.ts`, `streams.ts`, `subjects.ts`, `synthetic-control.ts`.
|
||||||
|
- External ingestion: `services/ingest-options/src/adapters/alpaca.ts`, `databento.ts`, `ibkr.ts`, `synthetic.ts`, `services/ingest-equities/src/adapters/alpaca.ts`, `services/ingest-news/src/index.ts`.
|
||||||
|
- Compute integrity: `services/compute/src/*.ts`, `services/candles/src/*.ts`, `services/replay/src/index.ts`.
|
||||||
|
- Web/admin/UI rendering: `apps/web/app/api/admin/synthetic/shared.ts`, `control/route.ts`, `status/route.ts`, `apps/web/app/**/*.tsx`, `apps/web/next.config.mjs`.
|
||||||
|
- Desktop boundary: `apps/desktop/src/security.ts`, `apps/desktop/src/main.ts`.
|
||||||
|
- Config/secrets/env: `packages/config/src/env.ts`, `packages/config/src/alpaca.ts`, `deployment/docker/.env.example`, `deployment/docker/docker-compose.yml`.
|
||||||
|
- Deployment/CI: `scripts/deploy.ts`, `deploy`, `.forgejo/workflows/ci.yml`, `.github/workflows/*.yml`, Dockerfiles.
|
||||||
|
|
||||||
|
## Initial Custom Extraction Targets
|
||||||
|
- Remote HTTP input to ClickHouse query template literals.
|
||||||
|
- Remote WS input to JSON/zod parsing and send/broadcast loops.
|
||||||
|
- External provider/child stdout input to NATS publish and UI render fields.
|
||||||
|
- Env vars to SSRF-like fetch destinations and Electron navigation.
|
||||||
|
- Env vars to `Bun.spawn` executable/arguments.
|
||||||
|
- NATS messages to ClickHouse insert and derived compute decisions.
|
||||||
153
piolium/attack-surface/candidates-summary.md
Normal file
153
piolium/attack-surface/candidates-summary.md
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
# Candidate Scan
|
||||||
|
|
||||||
|
Generated by piolium at 2026-05-27T05:18:10.316Z
|
||||||
|
|
||||||
|
## Totals
|
||||||
|
|
||||||
|
- Files scanned: 189
|
||||||
|
- Candidate files: 45
|
||||||
|
- Candidate matches: 289
|
||||||
|
- Per-file records: disabled (set PIOLIUM_FILE_RECORDS=1 to enable)
|
||||||
|
|
||||||
|
## Candidate Classes
|
||||||
|
|
||||||
|
- secret-literal: 2 match(es), max score 114. Hardcoded secret-like literal.
|
||||||
|
- dynamic-code-execution: 20 match(es), max score 90. Dynamic code execution, expression evaluation, or runtime compilation.
|
||||||
|
- command-execution: 34 match(es), max score 80. Potential command execution or shell invocation with variable input.
|
||||||
|
- hidden-control-channel: 40 match(es), max score 71. Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.
|
||||||
|
- ssrf-capable-request: 25 match(es), max score 71. Outbound HTTP request site that may be attacker-controlled.
|
||||||
|
- open-redirect: 4 match(es), max score 65. Redirect sink that may accept user-controlled URLs.
|
||||||
|
- unsafe-html-or-template: 4 match(es), max score 63. HTML injection sink or template escape bypass.
|
||||||
|
- path-traversal-file-access: 99 match(es), max score 55. Filesystem access using path joins or user-controllable paths.
|
||||||
|
- raw-sql-query: 21 match(es), max score 55. Raw SQL construction or query execution that may need parameterization review.
|
||||||
|
- public-entrypoint: 40 match(es), max score 54. Public route, handler, controller, workflow, or operation entry point.
|
||||||
|
|
||||||
|
## Top Files
|
||||||
|
|
||||||
|
- `packages/storage/src/clickhouse.ts`: score 4755, 69 match(es)
|
||||||
|
- `apps/web/app/terminal.tsx`: score 2040, 38 match(es)
|
||||||
|
- `scripts/deploy.ts`: score 1795, 29 match(es)
|
||||||
|
- `services/api/src/index.ts`: score 949, 23 match(es)
|
||||||
|
- `scripts/dev.ts`: score 905, 16 match(es)
|
||||||
|
- `scripts/check-docker-workspace.ts`: score 605, 11 match(es)
|
||||||
|
- `scripts/dev-desktop.ts`: score 520, 9 match(es)
|
||||||
|
- `scripts/dev-services.ts`: score 355, 6 match(es)
|
||||||
|
- `services/api/src/live.ts`: score 316, 7 match(es)
|
||||||
|
- `scripts/check-public-api-routes.ts`: score 305, 5 match(es)
|
||||||
|
- `packages/bus/src/jetstream.ts`: score 275, 5 match(es)
|
||||||
|
- `services/compute/src/structure-packets.ts`: score 275, 5 match(es)
|
||||||
|
- `services/ingest-options/src/adapters/ibkr.ts`: score 245, 4 match(es)
|
||||||
|
- `services/api/src/option-queries.ts`: score 228, 6 match(es)
|
||||||
|
- `services/compute/src/index.ts`: score 225, 3 match(es)
|
||||||
|
- `apps/desktop/src/security.ts`: score 220, 4 match(es)
|
||||||
|
- `scripts/sync-docker-workspace.ts`: score 220, 4 match(es)
|
||||||
|
- `apps/web/app/api/admin/synthetic/shared.ts`: score 188, 3 match(es)
|
||||||
|
- `services/candles/src/index.ts`: score 170, 2 match(es)
|
||||||
|
- `services/compute/src/rolling-stats.ts`: score 170, 2 match(es)
|
||||||
|
- `services/ingest-news/src/symbols.ts`: score 170, 2 match(es)
|
||||||
|
- `apps/web/app/api/admin/synthetic/routes.test.ts`: score 168, 2 match(es)
|
||||||
|
- `apps/desktop/src/security.test.ts`: score 110, 2 match(es)
|
||||||
|
- `packages/config/src/env.ts`: score 110, 2 match(es)
|
||||||
|
- `packages/types/src/live.ts`: score 110, 2 match(es)
|
||||||
|
- `packages/types/src/options-flow.ts`: score 110, 2 match(es)
|
||||||
|
- `services/compute/src/contracts.ts`: score 110, 2 match(es)
|
||||||
|
- `services/ingest-equities/src/adapters/alpaca.ts`: score 110, 2 match(es)
|
||||||
|
- `services/ingest-options/py/databento_replay.py`: score 110, 2 match(es)
|
||||||
|
- `services/ingest-options/py/ibkr_stream.py`: score 110, 2 match(es)
|
||||||
|
- `services/replay/src/index.ts`: score 110, 2 match(es)
|
||||||
|
- `apps/web/app/terminal.test.ts`: score 90, 3 match(es)
|
||||||
|
- `packages/config/tests/alpaca.test.ts`: score 90, 1 match(es)
|
||||||
|
- `apps/web/scripts/dev.ts`: score 80, 1 match(es)
|
||||||
|
- `services/ingest-options/src/adapters/databento.ts`: score 80, 1 match(es)
|
||||||
|
- `apps/web/app/charts/page.tsx`: score 65, 1 match(es)
|
||||||
|
- `apps/web/app/replay/page.tsx`: score 65, 1 match(es)
|
||||||
|
- `apps/web/app/signals/page.tsx`: score 65, 1 match(es)
|
||||||
|
- `apps/web/app/tape/page.tsx`: score 65, 1 match(es)
|
||||||
|
- `apps/web/app/frontend-cooker/page.tsx`: score 55, 1 match(es)
|
||||||
|
|
||||||
|
## Highest-Ranked Matches
|
||||||
|
|
||||||
|
- secret-literal (precise, score 114) at `apps/web/app/api/admin/synthetic/routes.test.ts:28` - token: "secret-token"
|
||||||
|
- secret-literal (precise, score 90) at `packages/config/tests/alpaca.test.ts:60` - secret: "short-secret",
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:118` - exec(params: { query: string }): Promise<void>;
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:189` - async exec({ query }) {
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:243` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:247` - await client.exec({ query });
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:254` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:262` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:270` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:278` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:286` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:294` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:302` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:310` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:318` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:324` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:328` - await client.exec({ query });
|
||||||
|
- dynamic-code-execution (precise, score 90) at `packages/storage/src/clickhouse.ts:333` - await client.exec({
|
||||||
|
- dynamic-code-execution (precise, score 90) at `services/candles/src/index.ts:156` - await multi.exec();
|
||||||
|
- dynamic-code-execution (precise, score 90) at `services/compute/src/index.ts:351` - const match = SYNTHETIC_EVENT_CONDITION_RE.exec(condition);
|
||||||
|
- dynamic-code-execution (precise, score 90) at `services/compute/src/rolling-stats.ts:163` - await multi.exec();
|
||||||
|
- dynamic-code-execution (precise, score 90) at `services/ingest-news/src/symbols.ts:27` - while ((match = regex.exec(value)) !== null) {
|
||||||
|
- command-execution (precise, score 80) at `apps/web/scripts/dev.ts:16` - const child = Bun.spawn(["next", "dev", "-p", String(port)], {
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:118` - exec(params: { query: string }): Promise<void>;
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:189` - async exec({ query }) {
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:243` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:247` - await client.exec({ query });
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:254` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:262` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:270` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:278` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:286` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:294` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:302` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:310` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:318` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:324` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:328` - await client.exec({ query });
|
||||||
|
- command-execution (precise, score 80) at `packages/storage/src/clickhouse.ts:333` - await client.exec({
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:180` - const result = spawnSync(command, args, {
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:196` - const result = spawnSync(command, args, {
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:216` - const result = spawnSync(command, args, {
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:238` - const result = spawnSync("bash", localArgs, {
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:253` - const result = spawnSync("ssh", sshArgs, {
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:402` - return spawnSync("git", ["remote", "get-url", name], {
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:581` - const result = spawnSync("bun", ["run", "check:docker-workspace"], {
|
||||||
|
- command-execution (precise, score 80) at `scripts/deploy.ts:670` - const upstreamResult = spawnSync(
|
||||||
|
- command-execution (precise, score 80) at `scripts/dev-desktop.ts:137` - const proc = Bun.spawn(cmd, {
|
||||||
|
- command-execution (precise, score 80) at `scripts/dev-services.ts:136` - const proc = Bun.spawn(cmd, {
|
||||||
|
- command-execution (precise, score 80) at `scripts/dev.ts:189` - const proc = Bun.spawn(cmd, {
|
||||||
|
- command-execution (precise, score 80) at `services/candles/src/index.ts:156` - await multi.exec();
|
||||||
|
- command-execution (precise, score 80) at `services/compute/src/index.ts:351` - const match = SYNTHETIC_EVENT_CONDITION_RE.exec(condition);
|
||||||
|
- command-execution (precise, score 80) at `services/compute/src/rolling-stats.ts:163` - await multi.exec();
|
||||||
|
- command-execution (precise, score 80) at `services/ingest-news/src/symbols.ts:27` - while ((match = regex.exec(value)) !== null) {
|
||||||
|
- command-execution (precise, score 80) at `services/ingest-options/src/adapters/databento.ts:305` - const child = Bun.spawn(buildArgs(trimmed), {
|
||||||
|
- command-execution (precise, score 80) at `services/ingest-options/src/adapters/ibkr.ts:92` - const child = Bun.spawn(args, {
|
||||||
|
- ssrf-capable-request (normal, score 71) at `apps/web/app/api/admin/synthetic/shared.ts:51` - const response = await fetch(url.toString(), {
|
||||||
|
- hidden-control-channel (normal, score 71) at `apps/web/app/api/admin/synthetic/shared.ts:60` - "content-type": response.headers.get("content-type") ?? "application/json"
|
||||||
|
- hidden-control-channel (normal, score 71) at `scripts/check-public-api-routes.ts:20` - return (response.headers.get("content-type") ?? "").toLowerCase().includes("application/json");
|
||||||
|
- ssrf-capable-request (normal, score 71) at `scripts/check-public-api-routes.ts:25` - const response = await fetch(url);
|
||||||
|
- hidden-control-channel (normal, score 71) at `scripts/check-public-api-routes.ts:34` - throw new Error(`${url.pathname} returned non-JSON content (${response.headers.get("content-type") ?? "none"}): ${sample}`);
|
||||||
|
- open-redirect (normal, score 65) at `apps/web/app/charts/page.tsx:6` - redirect("/");
|
||||||
|
- open-redirect (normal, score 65) at `apps/web/app/replay/page.tsx:6` - redirect("/");
|
||||||
|
- open-redirect (normal, score 65) at `apps/web/app/signals/page.tsx:6` - redirect("/");
|
||||||
|
- open-redirect (normal, score 65) at `apps/web/app/tape/page.tsx:6` - redirect("/options");
|
||||||
|
- hidden-control-channel (normal, score 63) at `services/api/src/index.ts:328` - const authorization = req.headers.get("authorization") ?? "";
|
||||||
|
- hidden-control-channel (normal, score 63) at `services/api/src/index.ts:332` - return req.headers.get("x-synthetic-admin-token")?.trim() ?? "";
|
||||||
|
- hidden-control-channel (normal, score 63) at `services/api/src/index.ts:2052` - logger.info("api listening", { host: env.API_HOST, port: server.port });
|
||||||
|
- unsafe-html-or-template (normal, score 63) at `services/api/src/live.ts:142` - console.warn(`Invalid ${key}="${raw}", using ${fallback}`);
|
||||||
|
- unsafe-html-or-template (normal, score 63) at `services/api/src/live.ts:161` - console.warn(`Invalid LIVE_LIMIT_DEFAULT="${raw}", using ${fallback}`);
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/desktop/src/security.test.ts:11` - it("allows the hosted production origin on /options", () => {
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/desktop/src/security.test.ts:15` - it("keeps /tape trusted as a compatibility path on the same origin", () => {
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/desktop/src/security.ts:5` - new URL(DESKTOP_PRODUCTION_URL).origin,
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/desktop/src/security.ts:6` - new URL(DESKTOP_LOCAL_DEV_URL).origin,
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/desktop/src/security.ts:26` - return TRUSTED_ORIGINS.has(url.origin);
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/desktop/src/security.ts:35` - return !TRUSTED_ORIGINS.has(url.origin);
|
||||||
|
- path-traversal-file-access (normal, score 55) at `apps/web/app/frontend-cooker/page.tsx:43` - <section className={styles.tableWrap}><table><thead><tr>{["Ticker", "Contract", "Expiry", "Notional", "Side", "Delta", "Condition"].map(h => <th key={h}>{h}</th>)}</tr></thead><tbody>{flowRows.map((r) => <tr key={r.join(
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/web/app/terminal.tsx:516` - const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/web/app/terminal.tsx:1024` - const host = isLocal ? `${hostname}:4000` : window.location.host;
|
||||||
|
- hidden-control-channel (normal, score 55) at `apps/web/app/terminal.tsx:1024` - const host = isLocal ? `${hostname}:4000` : window.location.host;
|
||||||
|
|
||||||
|
## Custom Matchers
|
||||||
|
|
||||||
|
Project matchers can be added at `piolium/matchers.json`, `piolium/custom-matchers.json`, or `.piolium-matchers.json`.
|
||||||
289
piolium/attack-surface/candidates.jsonl
Normal file
289
piolium/attack-surface/candidates.jsonl
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
{"slug":"secret-literal","description":"Hardcoded secret-like literal.","noise":"precise","filePath":"apps/web/app/api/admin/synthetic/routes.test.ts","line":28,"snippet":"token: \"secret-token\"","matchedPattern":"secret assignment","score":114,"source":"builtin"}
|
||||||
|
{"slug":"secret-literal","description":"Hardcoded secret-like literal.","noise":"precise","filePath":"packages/config/tests/alpaca.test.ts","line":60,"snippet":"secret: \"short-secret\",","matchedPattern":"secret assignment","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":118,"snippet":"exec(params: { query: string }): Promise<void>;","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":189,"snippet":"async exec({ query }) {","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":243,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":247,"snippet":"await client.exec({ query });","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":254,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":262,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":270,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":278,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":286,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":294,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":302,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":310,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":318,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":324,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":328,"snippet":"await client.exec({ query });","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":333,"snippet":"await client.exec({","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"services/candles/src/index.ts","line":156,"snippet":"await multi.exec();","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"services/compute/src/index.ts","line":351,"snippet":"const match = SYNTHETIC_EVENT_CONDITION_RE.exec(condition);","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"services/compute/src/rolling-stats.ts","line":163,"snippet":"await multi.exec();","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"dynamic-code-execution","description":"Dynamic code execution, expression evaluation, or runtime compilation.","noise":"precise","filePath":"services/ingest-news/src/symbols.ts","line":27,"snippet":"while ((match = regex.exec(value)) !== null) {","matchedPattern":"python eval","score":90,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"apps/web/scripts/dev.ts","line":16,"snippet":"const child = Bun.spawn([\"next\", \"dev\", \"-p\", String(port)], {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":118,"snippet":"exec(params: { query: string }): Promise<void>;","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":189,"snippet":"async exec({ query }) {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":243,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":247,"snippet":"await client.exec({ query });","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":254,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":262,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":270,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":278,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":286,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":294,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":302,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":310,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":318,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":324,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":328,"snippet":"await client.exec({ query });","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"packages/storage/src/clickhouse.ts","line":333,"snippet":"await client.exec({","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":180,"snippet":"const result = spawnSync(command, args, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":196,"snippet":"const result = spawnSync(command, args, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":216,"snippet":"const result = spawnSync(command, args, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":238,"snippet":"const result = spawnSync(\"bash\", localArgs, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":253,"snippet":"const result = spawnSync(\"ssh\", sshArgs, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":402,"snippet":"return spawnSync(\"git\", [\"remote\", \"get-url\", name], {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":581,"snippet":"const result = spawnSync(\"bun\", [\"run\", \"check:docker-workspace\"], {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/deploy.ts","line":670,"snippet":"const upstreamResult = spawnSync(","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/dev-desktop.ts","line":137,"snippet":"const proc = Bun.spawn(cmd, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/dev-services.ts","line":136,"snippet":"const proc = Bun.spawn(cmd, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"scripts/dev.ts","line":189,"snippet":"const proc = Bun.spawn(cmd, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"services/candles/src/index.ts","line":156,"snippet":"await multi.exec();","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"services/compute/src/index.ts","line":351,"snippet":"const match = SYNTHETIC_EVENT_CONDITION_RE.exec(condition);","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"services/compute/src/rolling-stats.ts","line":163,"snippet":"await multi.exec();","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"services/ingest-news/src/symbols.ts","line":27,"snippet":"while ((match = regex.exec(value)) !== null) {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"services/ingest-options/src/adapters/databento.ts","line":305,"snippet":"const child = Bun.spawn(buildArgs(trimmed), {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"command-execution","description":"Potential command execution or shell invocation with variable input.","noise":"precise","filePath":"services/ingest-options/src/adapters/ibkr.ts","line":92,"snippet":"const child = Bun.spawn(args, {","matchedPattern":"node child_process","score":80,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/api/admin/synthetic/shared.ts","line":51,"snippet":"const response = await fetch(url.toString(), {","matchedPattern":"fetch/http client","score":71,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/api/admin/synthetic/shared.ts","line":60,"snippet":"\"content-type\": response.headers.get(\"content-type\") ?? \"application/json\"","matchedPattern":"request header read","score":71,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/check-public-api-routes.ts","line":20,"snippet":"return (response.headers.get(\"content-type\") ?? \"\").toLowerCase().includes(\"application/json\");","matchedPattern":"request header read","score":71,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"scripts/check-public-api-routes.ts","line":25,"snippet":"const response = await fetch(url);","matchedPattern":"fetch/http client","score":71,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/check-public-api-routes.ts","line":34,"snippet":"throw new Error(`${url.pathname} returned non-JSON content (${response.headers.get(\"content-type\") ?? \"none\"}): ${sample}`);","matchedPattern":"request header read","score":71,"source":"builtin"}
|
||||||
|
{"slug":"open-redirect","description":"Redirect sink that may accept user-controlled URLs.","noise":"normal","filePath":"apps/web/app/charts/page.tsx","line":6,"snippet":"redirect(\"/\");","matchedPattern":"redirect call","score":65,"source":"builtin"}
|
||||||
|
{"slug":"open-redirect","description":"Redirect sink that may accept user-controlled URLs.","noise":"normal","filePath":"apps/web/app/replay/page.tsx","line":6,"snippet":"redirect(\"/\");","matchedPattern":"redirect call","score":65,"source":"builtin"}
|
||||||
|
{"slug":"open-redirect","description":"Redirect sink that may accept user-controlled URLs.","noise":"normal","filePath":"apps/web/app/signals/page.tsx","line":6,"snippet":"redirect(\"/\");","matchedPattern":"redirect call","score":65,"source":"builtin"}
|
||||||
|
{"slug":"open-redirect","description":"Redirect sink that may accept user-controlled URLs.","noise":"normal","filePath":"apps/web/app/tape/page.tsx","line":6,"snippet":"redirect(\"/options\");","matchedPattern":"redirect call","score":65,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/api/src/index.ts","line":328,"snippet":"const authorization = req.headers.get(\"authorization\") ?? \"\";","matchedPattern":"request header read","score":63,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/api/src/index.ts","line":332,"snippet":"return req.headers.get(\"x-synthetic-admin-token\")?.trim() ?? \"\";","matchedPattern":"request header read","score":63,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/api/src/index.ts","line":2052,"snippet":"logger.info(\"api listening\", { host: env.API_HOST, port: server.port });","matchedPattern":"proxy or original request header","score":63,"source":"builtin"}
|
||||||
|
{"slug":"unsafe-html-or-template","description":"HTML injection sink or template escape bypass.","noise":"normal","filePath":"services/api/src/live.ts","line":142,"snippet":"console.warn(`Invalid ${key}=\"${raw}\", using ${fallback}`);","matchedPattern":"template unescaped","score":63,"source":"builtin"}
|
||||||
|
{"slug":"unsafe-html-or-template","description":"HTML injection sink or template escape bypass.","noise":"normal","filePath":"services/api/src/live.ts","line":161,"snippet":"console.warn(`Invalid LIVE_LIMIT_DEFAULT=\"${raw}\", using ${fallback}`);","matchedPattern":"template unescaped","score":63,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/desktop/src/security.test.ts","line":11,"snippet":"it(\"allows the hosted production origin on /options\", () => {","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/desktop/src/security.test.ts","line":15,"snippet":"it(\"keeps /tape trusted as a compatibility path on the same origin\", () => {","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/desktop/src/security.ts","line":5,"snippet":"new URL(DESKTOP_PRODUCTION_URL).origin,","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/desktop/src/security.ts","line":6,"snippet":"new URL(DESKTOP_LOCAL_DEV_URL).origin,","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/desktop/src/security.ts","line":26,"snippet":"return TRUSTED_ORIGINS.has(url.origin);","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/desktop/src/security.ts","line":35,"snippet":"return !TRUSTED_ORIGINS.has(url.origin);","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/frontend-cooker/page.tsx","line":43,"snippet":"<section className={styles.tableWrap}><table><thead><tr>{[\"Ticker\", \"Contract\", \"Expiry\", \"Notional\", \"Side\", \"Delta\", \"Condition\"].map(h => <th key={h}>{h}</th>)}</tr></thead><tbody>{flowRows.map((r) => <tr key={r.join(","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":516,"snippet":"const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";","matchedPattern":"request header read","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1024,"snippet":"const host = isLocal ? `${hostname}:4000` : window.location.host;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1024,"snippet":"const host = isLocal ? `${hostname}:4000` : window.location.host;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1026,"snippet":"return `${wsProtocol}://${host}${path}`;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1045,"snippet":"const host = isLocal ? `${hostname}:4000` : window.location.host;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1045,"snippet":"const host = isLocal ? `${hostname}:4000` : window.location.host;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1047,"snippet":"return `${httpProtocol}://${host}${path}`;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1301,"snippet":".join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":1424,"snippet":"const remainder = parts.slice(2).join(\" -> \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":2300,"snippet":"const response = await fetch(url.toString());","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":2450,"snippet":"const response = await fetch(url.toString());","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":3018,"snippet":"params.set(\"side\", filters.nbboSides.join(\",\"));","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":3021,"snippet":"params.set(\"type\", filters.optionTypes.join(\",\"));","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":3033,"snippet":"params.set(\"underlying_ids\", optionScope.underlying_ids.join(\",\"));","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":3123,"snippet":"params.set(\"underlying_ids\", subscription.underlying_ids.join(\",\"));","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":3755,"snippet":"const response = await fetch(url.toString());","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":4381,"snippet":"const response = await fetch(url.toString());","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":4455,"snippet":"const response = await fetch(url.toString(), { signal: abort.signal });","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":4959,"snippet":"<p className=\"drawer-empty\">Missing refs: {missingRefs.slice(0, 4).join(\", \")}</p>","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"unsafe-html-or-template","description":"HTML injection sink or template escape bypass.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":5009,"snippet":"<div className=\"drawer-note news-drawer-body\" dangerouslySetInnerHTML={{ __html: body.html }} />","matchedPattern":"dangerous html","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":5191,"snippet":"<p className=\"drawer-empty\">Suppressed: {event.suppressed_reasons.join(\", \")}</p>","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":5934,"snippet":"void fetch(buildApiUrl(buildAlertContextPath(selectedAlert.trace_id)), { signal: abort.signal })","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6000,"snippet":"void fetch(url.toString())","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6222,"snippet":"void fetch(buildApiUrl(\"/lookup/options-support\"), {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6234,"snippet":"const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";","matchedPattern":"request header read","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6318,"snippet":"void fetch(buildApiUrl(`/flow/packets/${encodeURIComponent(selectedClassifierPacketId)}`))","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6398,"snippet":"const response = await fetch(buildApiUrl(`/flow/packets/${encodeURIComponent(packetId)}`));","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6432,"snippet":"void fetch(url.toString())","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6747,"snippet":"const response = await fetch(buildApiUrl(`/flow/packets/${encodeURIComponent(packetId)}`));","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":6785,"snippet":"void fetch(url.toString())","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":7440,"snippet":"const classes = [\"terminal-pane\", className].filter(Boolean).join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":7457,"snippet":"const focus = state.activeTickers.length > 0 ? state.activeTickers.join(\", \") : \"ALL\";","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":7902,"snippet":"].filter(Boolean).join(\" | \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":8522,"snippet":"const response = await fetch(SYNTHETIC_ADMIN_PROXY_PATHS.status, {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":8579,"snippet":"void fetch(SYNTHETIC_ADMIN_PROXY_PATHS.control, {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"apps/web/app/terminal.tsx","line":8799,"snippet":"? derived.focus_symbols.join(\", \")","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/bus/src/jetstream.ts","line":382,"snippet":"return value.join(\",\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/bus/src/jetstream.ts","line":391,"snippet":".join(\"; \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/bus/src/jetstream.ts","line":434,"snippet":"const fields = report.retentionDrift.map((delta) => delta.field).join(\",\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/bus/src/jetstream.ts","line":458,"snippet":".join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/bus/src/jetstream.ts","line":464,"snippet":".join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/config/src/env.ts","line":16,"snippet":"const path = issue.path.length > 0 ? issue.path.join(\".\") : \"<root>\";","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/config/src/env.ts","line":19,"snippet":".join(\"; \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":120,"snippet":"query(params: { query: string; format: ClickHouseQueryFormat }): Promise<ClickHouseQueryResult>;","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":158,"snippet":"const response = await fetch(url, {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":194,"snippet":"const rows = values.map((value) => JSON.stringify(value)).join(\"\\n\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":199,"snippet":"async query({ query, format }) {","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":214,"snippet":"const response = await fetch(url, {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":683,"snippet":"return values.map((value) => quoteString(value)).join(\", \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1084,"snippet":"const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(\" AND \")}` : \"\";","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1085,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1102,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1118,"snippet":"const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(\" AND \")}` : \"\";","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1119,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1133,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1151,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1165,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1183,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1201,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1219,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1237,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1254,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1299,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1347,"snippet":"const alertResult = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1373,"snippet":".query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1384,"snippet":": Promise.resolve([]),","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1387,"snippet":": Promise.resolve([])","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1422,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1423,"snippet":"query: `SELECT * FROM ${OPTION_PRINTS_TABLE} WHERE ${conditions.join(\" AND \")} ORDER BY ts ASC, seq ASC LIMIT ${safeLimit}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1444,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1469,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1470,"snippet":"query: `SELECT * FROM ${EQUITY_PRINTS_TABLE} WHERE ${conditions.join(\" AND \")} ORDER BY ts ASC, seq ASC LIMIT ${safeLimit}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1492,"snippet":"const result = await client.query({","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1740,"snippet":"query: `SELECT * FROM ${OPTION_PRINTS_TABLE} WHERE ${conditions.join(\" AND \")} ORDER BY ts DESC, seq DESC LIMIT ${safeLimit}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1763,"snippet":"query: `SELECT * FROM ${OPTION_NBBO_TABLE} WHERE ${conditions.join(\" AND \")} ORDER BY ts DESC, seq DESC LIMIT ${safeLimit}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1784,"snippet":"query: `SELECT * FROM ${EQUITY_PRINTS_TABLE} WHERE ${conditions.join(\" AND \")} ORDER BY ts DESC, seq DESC LIMIT ${safeLimit}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":1987,"snippet":"query: `SELECT * FROM ${FLOW_PACKETS_TABLE} WHERE ${memberPredicates.join(\" OR \")} ORDER BY source_ts DESC, seq DESC LIMIT ${clampLookupLimit(ids.length * 4)}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":2009,"snippet":"query: `SELECT * FROM ${SMART_MONEY_EVENTS_TABLE} WHERE ${packetPredicates.join(\" OR \")} ORDER BY source_ts DESC, seq DESC LIMIT ${clampLookupLimit(ids.length * 4)}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":2031,"snippet":"query: `SELECT * FROM ${CLASSIFIER_HITS_TABLE} WHERE ${tracePredicates.join(\" OR \")} ORDER BY source_ts DESC, seq DESC LIMIT ${clampLookupLimit(ids.length * 4)}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/storage/src/clickhouse.ts","line":2128,"snippet":"query: `SELECT * FROM ${EQUITY_PRINT_JOINS_TABLE} WHERE ${whereParts.join(\" OR \")} ORDER BY source_ts DESC, seq DESC LIMIT ${lookupLimit}`,","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/types/src/live.ts","line":228,"snippet":"? `|underlyings:${[...subscription.underlying_ids].sort().join(\",\")}`","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/types/src/live.ts","line":239,"snippet":"? `|underlyings:${[...subscription.underlying_ids].sort().join(\",\")}`","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/types/src/options-flow.ts","line":88,"snippet":"const expiry = expiryParts.join(\"-\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"packages/types/src/options-flow.ts","line":89,"snippet":"const root = parts.slice(0, -5).join(\"-\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":25,"snippet":"const repoRoot = path.resolve(import.meta.dir, \"..\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":26,"snippet":"const deploymentRoot = path.join(repoRoot, \"deployment/docker/workspace-root\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":28,"snippet":"const rootPackagePath = path.join(repoRoot, \"package.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":29,"snippet":"const deploymentPackagePath = path.join(deploymentRoot, \"package.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":30,"snippet":"const rootTsconfigPath = path.join(repoRoot, \"tsconfig.base.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":31,"snippet":"const deploymentTsconfigPath = path.join(deploymentRoot, \"tsconfig.base.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":32,"snippet":"const rootLockPath = path.join(repoRoot, \"bun.lock\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":33,"snippet":"const deploymentLockPath = path.join(deploymentRoot, \"bun.lock\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":36,"snippet":"return readFile(filePath, \"utf8\");","matchedPattern":"file read/write","score":55,"source":"builtin"}
|
||||||
|
{"slug":"unsafe-html-or-template","description":"HTML injection sink or template escape bypass.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":42,"snippet":"const parsed = Function(`\"use strict\"; return (${raw});`)() as T;","matchedPattern":"template unescaped","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/check-docker-workspace.ts","line":159,"snippet":"const packageJsonPath = path.join(repoRoot, workspacePath, \"package.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":31,"snippet":"path.join(process.env.HOME ?? \"\", \".ssh\", \"delta_ed25519\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":89,"snippet":"const repoRoot = path.resolve(path.dirname(scriptPath), \"..\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/deploy.ts","line":104,"snippet":"native Experimental host-native Bun services managed by systemd.","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/deploy.ts","line":125,"snippet":"DEPLOY_PUBLIC_API_HEALTH_URL Optional separate public API health URL for two-origin deployments.","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":171,"snippet":".join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/deploy.ts","line":438,"snippet":"candidates.push(\"forgejo\", \"origin\", \"github\", ...localGitRemotes());","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":448,"snippet":"`Unable to resolve a deploy git remote. Checked candidates: ${deduped.join(\", \")}`","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":691,"snippet":".join(\"|\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":751,"snippet":"const units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":824,"snippet":": `docker compose build ${buildServices.join(\" \")}`;","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":825,"snippet":"const upCommand = `docker compose ${[...upArgs, ...rolloutServices].join(\" \")}`;","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":847,"snippet":"const units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":857,"snippet":"buildSteps.push(`${NATIVE_SYSTEMCTL_PREFIX} restart ${nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(\" \")}`);","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":871,"snippet":"${buildSteps.join(\"\\n\")}","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":903,"snippet":"? `docker compose ps ${psServices.join(\" \")}`","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":907,"snippet":": `docker compose logs --tail=100 ${logServices.join(\" \")}`;","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"scripts/deploy.ts","line":912,"snippet":"`docker compose exec -T api bun -e 'const r = await fetch(\"http://127.0.0.1:4000/health\"); if (!r.ok) throw new Error(\"api healthcheck failed: \" + r.status); console.log(await r.text())'`","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"scripts/deploy.ts","line":918,"snippet":"`docker compose exec -T web bun -e 'const r = await fetch(\"http://127.0.0.1:3000/\"); if (!r.ok) throw new Error(\"web healthcheck failed: \" + r.status); console.log(r.status)'`","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":930,"snippet":"${checks.join(\"\\n\")}","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":936,"snippet":"const units = nativeUnitsForScope(scope).map((value) => shellEscape(value)).join(\" \");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/deploy.ts","line":967,"snippet":"${checks.join(\"\\n\")}","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":25,"snippet":"const stateDir = path.join(process.cwd(), \".tmp\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":26,"snippet":"const pidFile = path.join(stateDir, \"dev-desktop-runner-pids.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":102,"snippet":"await writeFile(pidFile, JSON.stringify(payload, null, 2));","matchedPattern":"file read/write","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":111,"snippet":"const raw = await readFile(pidFile, \"utf8\");","matchedPattern":"file read/write","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":122,"snippet":".join(\", \")}`","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":220,"snippet":"const checkTcp = (host: string, port: number, timeoutMs = 1000): Promise<boolean> => {","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":222,"snippet":"const socket = net.connect({ host, port });","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-desktop.ts","line":226,"snippet":"resolve(ok);","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-services.ts","line":19,"snippet":"const stateDir = path.join(process.cwd(), \".tmp\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-services.ts","line":20,"snippet":"const pidFile = path.join(stateDir, \"dev-services-runner-pids.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-services.ts","line":101,"snippet":"await writeFile(pidFile, JSON.stringify(payload, null, 2));","matchedPattern":"file read/write","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-services.ts","line":110,"snippet":"const raw = await readFile(pidFile, \"utf8\");","matchedPattern":"file read/write","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev-services.ts","line":121,"snippet":".join(\", \")}`","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev.ts","line":20,"snippet":"const stateDir = path.join(process.cwd(), \".tmp\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev.ts","line":21,"snippet":"const pidFile = path.join(stateDir, \"dev-runner-pids.json\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev.ts","line":102,"snippet":"await writeFile(pidFile, JSON.stringify(payload, null, 2));","matchedPattern":"file read/write","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev.ts","line":111,"snippet":"const raw = await readFile(pidFile, \"utf8\");","matchedPattern":"file read/write","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev.ts","line":122,"snippet":".join(\", \")}`","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":148,"snippet":"): { host: string; port: number } => {","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":151,"snippet":"return { host: fallbackHost, port: fallbackPort };","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":157,"snippet":"return { host: url.hostname || fallbackHost, port };","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":159,"snippet":"return { host: fallbackHost, port: fallbackPort };","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":163,"snippet":"const checkTcp = (host: string, port: number, timeoutMs = 1000): Promise<boolean> => {","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":165,"snippet":"const socket = net.connect({ host, port });","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/dev.ts","line":169,"snippet":"resolve(ok);","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"scripts/dev.ts","line":181,"snippet":"const response = await fetch(url);","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":296,"snippet":"checkTcp(natsTarget.host, natsTarget.port),","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"scripts/dev.ts","line":297,"snippet":"checkTcp(redisTarget.host, redisTarget.port),","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/sync-docker-workspace.ts","line":4,"snippet":"const repoRoot = path.resolve(import.meta.dir, \"..\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/sync-docker-workspace.ts","line":5,"snippet":"const deploymentRoot = path.join(repoRoot, \"deployment/docker/workspace-root\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/sync-docker-workspace.ts","line":14,"snippet":"const source = path.join(repoRoot, fileName);","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"scripts/sync-docker-workspace.ts","line":15,"snippet":"const destination = path.join(deploymentRoot, fileName);","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/contracts.ts","line":22,"snippet":"const expiry = expiryParts.join(\"-\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/contracts.ts","line":23,"snippet":"const root = parts.slice(0, -5).join(\"-\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/index.ts","line":904,"snippet":"features.conditions = Array.from(cluster.conditions).sort().join(\",\");","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/structure-packets.ts","line":221,"snippet":"const id = `flowpacket:${pseudoContractId}:${bucketStartTs}:${contractIds.join(\"|\")}`;","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/structure-packets.ts","line":222,"snippet":"const dedupeKey = `${pseudoContractId}:${bucketStartTs}:${contractIds.join(\"|\")}`;","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/structure-packets.ts","line":298,"snippet":"structure_contract_ids: summary.contractIds.join(\",\"),","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/structure-packets.ts","line":300,"snippet":"structure_expiries: plan.expiries.join(\",\"),","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/src/structure-packets.ts","line":301,"snippet":"structure_strikes_list: plan.strikes.join(\",\")","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/compute/tests/classifiers.test.ts","line":10,"snippet":"expect(hit.explanations.join(\" \")).toMatch(/Likely|Consistent with|Unusual/i);","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/ingest-equities/src/adapters/alpaca.ts","line":151,"snippet":"return `${parsed.origin}/v2/${feed}`;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"services/ingest-equities/src/adapters/alpaca.ts","line":158,"snippet":"const response = await fetch(url.toString(), {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"services/ingest-news/src/index.ts","line":109,"snippet":"const response = await fetch(url.toString(), {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/ingest-options/py/databento_replay.py","line":106,"snippet":"response = self._client.symbology.resolve(","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/ingest-options/py/databento_replay.py","line":119,"snippet":"return self._map.resolve(instrument_id, date)","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/ingest-options/py/ibkr_stream.py","line":13,"snippet":"parser.add_argument(\"--host\", required=True)","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/ingest-options/py/ibkr_stream.py","line":39,"snippet":"ib.connect(args.host, args.port, clientId=args.client_id)","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"ssrf-capable-request","description":"Outbound HTTP request site that may be attacker-controlled.","noise":"normal","filePath":"services/ingest-options/src/adapters/alpaca.ts","line":159,"snippet":"const response = await fetch(url.toString(), {","matchedPattern":"fetch/http client","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/ingest-options/src/adapters/ibkr.ts","line":4,"snippet":"host: string;","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/ingest-options/src/adapters/ibkr.ts","line":72,"snippet":"\"--host\",","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/ingest-options/src/adapters/ibkr.ts","line":73,"snippet":"config.host,","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"hidden-control-channel","description":"Request header or framework/proxy context read that may influence auth, routing, tenant, runtime, debug, or middleware behavior.","noise":"normal","filePath":"services/ingest-options/src/index.ts","line":337,"snippet":"host: env.IBKR_HOST,","matchedPattern":"proxy or original request header","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/refdata/tests/event-calendar.test.ts","line":42,"snippet":"].join(\"\\n\"),","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"path-traversal-file-access","description":"Filesystem access using path joins or user-controllable paths.","noise":"normal","filePath":"services/replay/src/index.ts","line":173,"snippet":"throw new Error(`Unknown replay stream(s): ${invalid.join(\", \")}`);","matchedPattern":"path join","score":55,"source":"builtin"}
|
||||||
|
{"slug":"raw-sql-query","description":"Raw SQL construction or query execution that may need parameterization review.","noise":"normal","filePath":"services/replay/src/index.ts","line":306,"snippet":"await clickhouse.query({ query: \"SELECT 1\", format: \"JSONEachRow\" });","matchedPattern":"query call","score":55,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"apps/web/app/api/admin/synthetic/routes.test.ts","line":35,"snippet":"expect(new Headers(init?.headers).get(\"authorization\")).toBe(\"Bearer secret-token\");","matchedPattern":"http route","score":54,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"apps/web/app/api/admin/synthetic/shared.ts","line":60,"snippet":"\"content-type\": response.headers.get(\"content-type\") ?? \"application/json\"","matchedPattern":"http route","score":46,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"scripts/check-public-api-routes.ts","line":20,"snippet":"return (response.headers.get(\"content-type\") ?? \"\").toLowerCase().includes(\"application/json\");","matchedPattern":"http route","score":46,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"scripts/check-public-api-routes.ts","line":34,"snippet":"throw new Error(`${url.pathname} returned non-JSON content (${response.headers.get(\"content-type\") ?? \"none\"}): ${sample}`);","matchedPattern":"http route","score":46,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":328,"snippet":"const authorization = req.headers.get(\"authorization\") ?? \"\";","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":332,"snippet":"return req.headers.get(\"x-synthetic-admin-token\")?.trim() ?? \"\";","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":380,"snippet":"after_ts: url.searchParams.get(\"after_ts\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":381,"snippet":"after_seq: url.searchParams.get(\"after_seq\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":382,"snippet":"limit: url.searchParams.get(\"limit\") ?? undefined","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":394,"snippet":"before_ts: url.searchParams.get(\"before_ts\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":395,"snippet":"before_seq: url.searchParams.get(\"before_seq\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":396,"snippet":"limit: url.searchParams.get(\"limit\") ?? undefined","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":407,"snippet":"const raw = url.searchParams.get(\"source\");","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":432,"snippet":"underlying_id: url.searchParams.get(\"underlying_id\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":433,"snippet":"start_ts: url.searchParams.get(\"start_ts\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":434,"snippet":"end_ts: url.searchParams.get(\"end_ts\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":435,"snippet":"limit: url.searchParams.get(\"limit\") ?? undefined","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":457,"snippet":"underlying_id: url.searchParams.get(\"underlying_id\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":458,"snippet":"interval_ms: url.searchParams.get(\"interval_ms\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":459,"snippet":"start_ts: url.searchParams.get(\"start_ts\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":460,"snippet":"end_ts: url.searchParams.get(\"end_ts\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":461,"snippet":"limit: url.searchParams.get(\"limit\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":462,"snippet":"cache: url.searchParams.get(\"cache\") ?? undefined","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/index.ts","line":486,"snippet":"underlying_id: url.searchParams.get(\"underlying_id\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/live.ts","line":811,"snippet":"const cached = (this.genericItems.get(\"options\") ?? [])","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/live.ts","line":830,"snippet":"const items = (this.genericItems.get(\"options\") ?? [])","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/live.ts","line":844,"snippet":"const items = (this.genericItems.get(\"flow\") ?? [])","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/live.ts","line":858,"snippet":"const cached = (this.genericItems.get(\"equities\") ?? [])","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/live.ts","line":876,"snippet":"const items = (this.genericItems.get(\"equities\") ?? []).slice(0, limit);","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/option-queries.ts","line":63,"snippet":"view: url.searchParams.get(\"view\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/option-queries.ts","line":64,"snippet":"security: url.searchParams.get(\"security\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/option-queries.ts","line":65,"snippet":"side: url.searchParams.get(\"side\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/option-queries.ts","line":66,"snippet":"type: url.searchParams.get(\"type\") ?? undefined,","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/option-queries.ts","line":67,"snippet":"min_notional: url.searchParams.get(\"min_notional\") ?? undefined","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"services/api/src/option-queries.ts","line":71,"snippet":"optionContractId: url.searchParams.get(\"option_contract_id\") ?? undefined","matchedPattern":"http route","score":38,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"apps/web/app/terminal.test.ts","line":136,"snippet":"expect(evidence.packets.get(\"flowpacket:1\")).toBe(packet);","matchedPattern":"http route","score":30,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"apps/web/app/terminal.test.ts","line":137,"snippet":"expect(evidence.prints.get(\"print:1\")?.execution_nbbo_bid).toBe(1.2);","matchedPattern":"http route","score":30,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"apps/web/app/terminal.test.ts","line":138,"snippet":"expect(evidence.prints.get(\"print:1\")?.execution_underlying_spot).toBe(450.05);","matchedPattern":"http route","score":30,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"apps/web/app/terminal.tsx","line":516,"snippet":"const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";","matchedPattern":"http route","score":30,"source":"builtin"}
|
||||||
|
{"slug":"public-entrypoint","description":"Public route, handler, controller, workflow, or operation entry point.","noise":"noisy","filePath":"apps/web/app/terminal.tsx","line":6234,"snippet":"const contentType = response.headers.get(\"content-type\")?.toLowerCase() ?? \"\";","matchedPattern":"http route","score":30,"source":"builtin"}
|
||||||
35
piolium/attack-surface/cross-service-edges.json
Normal file
35
piolium/attack-surface/cross-service-edges.json
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"single_service": false,
|
||||||
|
"services": [
|
||||||
|
{"name":"web","root":"apps/web/","language":"typescript","frameworks":["nextjs"]},
|
||||||
|
{"name":"api","root":"services/api/","language":"typescript","frameworks":["bun","websocket"]},
|
||||||
|
{"name":"ingest-options","root":"services/ingest-options/","language":"typescript","frameworks":["nats","clickhouse"]},
|
||||||
|
{"name":"ingest-equities","root":"services/ingest-equities/","language":"typescript","frameworks":["nats","clickhouse"]},
|
||||||
|
{"name":"ingest-news","root":"services/ingest-news/","language":"typescript","frameworks":["nats"]},
|
||||||
|
{"name":"compute","root":"services/compute/","language":"typescript","frameworks":["nats","clickhouse"]},
|
||||||
|
{"name":"candles","root":"services/candles/","language":"typescript","frameworks":["nats","clickhouse","redis"]},
|
||||||
|
{"name":"replay","root":"services/replay/","language":"typescript","frameworks":["nats"]}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{"id":"E001","channel":"http","producer":{"service":"web","file":"apps/web/app/api/admin/synthetic/shared.ts","line":51,"pattern":"fetch(url.toString(), { method, headers, body })"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":1364,"pattern":"GET/PUT /admin/synthetic/*"},"data_shape":"admin synthetic status/control HTTP JSON body proxied from browser","sanitization_at_boundary":"API bearer token check; no browser-user auth in web proxy per authz matrix","trust_tagged":"web injects SYNTHETIC_ADMIN_TOKEN for caller"},
|
||||||
|
{"id":"E002","channel":"queue:options.prints","producer":{"service":"ingest-options","file":"services/ingest-options/src/index.ts","line":430,"pattern":"publishJson(js, SUBJECT_OPTION_PRINTS, print)"},"consumer":{"service":"replay/external","file":"packages/bus/src/subjects.ts","line":2,"pattern":"SUBJECT_OPTION_PRINTS = 'options.prints'"},"data_shape":"OptionPrint JSON","sanitization_at_boundary":"OptionPrintSchema.parse before publish; consumers schema-parse when present","trust_tagged":"none"},
|
||||||
|
{"id":"E003","channel":"queue:options.prints.signal","producer":{"service":"ingest-options","file":"services/ingest-options/src/index.ts","line":432,"pattern":"publishJson(js, SUBJECT_OPTION_SIGNAL_PRINTS, print)"},"consumer":{"service":"compute","file":"services/compute/src/index.ts","line":1501,"pattern":"subscribeJson(js, SUBJECT_OPTION_SIGNAL_PRINTS, opts)"},"data_shape":"signal-passing OptionPrint JSON","sanitization_at_boundary":"producer and compute parse OptionPrintSchema; no message authentication","trust_tagged":"signal_pass flag controls downstream processing"},
|
||||||
|
{"id":"E004","channel":"queue:options.prints.signal","producer":{"service":"ingest-options/replay","file":"services/replay/src/index.ts","line":407,"pattern":"publishJson(js, SUBJECT_OPTION_SIGNAL_PRINTS, event as OptionPrint)"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":945,"pattern":"subscribeWithReset(SUBJECT_OPTION_SIGNAL_PRINTS, ...)"},"data_shape":"OptionPrint JSON for live API fanout","sanitization_at_boundary":"consumer OptionPrintSchema.parse in pump; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E005","channel":"queue:options.nbbo","producer":{"service":"ingest-options","file":"services/ingest-options/src/index.ts","line":460,"pattern":"publishJson(js, SUBJECT_OPTION_NBBO, nbbo)"},"consumer":{"service":"compute","file":"services/compute/src/index.ts","line":1537,"pattern":"subscribeJson(js, SUBJECT_OPTION_NBBO, opts)"},"data_shape":"OptionNBBO JSON","sanitization_at_boundary":"schema parse both ends; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E006","channel":"queue:equities.prints","producer":{"service":"ingest-equities","file":"services/ingest-equities/src/index.ts","line":266,"pattern":"publishJson(js, SUBJECT_EQUITY_PRINTS, print)"},"consumer":{"service":"compute","file":"services/compute/src/index.ts","line":1573,"pattern":"subscribeJson(js, SUBJECT_EQUITY_PRINTS, opts)"},"data_shape":"EquityPrint JSON","sanitization_at_boundary":"schema parse both ends; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E007","channel":"queue:equities.prints","producer":{"service":"ingest-equities","file":"services/ingest-equities/src/index.ts","line":266,"pattern":"publishJson(js, SUBJECT_EQUITY_PRINTS, print)"},"consumer":{"service":"candles","file":"services/candles/src/index.ts","line":341,"pattern":"subscribeJson(js, SUBJECT_EQUITY_PRINTS, resetOpts)"},"data_shape":"EquityPrint JSON","sanitization_at_boundary":"consumer EquityPrintSchema.parse; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E008","channel":"queue:equities.quotes","producer":{"service":"ingest-equities","file":"services/ingest-equities/src/index.ts","line":292,"pattern":"publishJson(js, SUBJECT_EQUITY_QUOTES, quote)"},"consumer":{"service":"ingest-options","file":"services/ingest-options/src/index.ts","line":476,"pattern":"subscribeJson<EquityQuote>(js, SUBJECT_EQUITY_QUOTES, ...)"},"data_shape":"EquityQuote JSON used to enrich option prints","sanitization_at_boundary":"schema parse at producer; consumer subscribes and later validates context; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E009","channel":"queue:equities.candles","producer":{"service":"candles","file":"services/candles/src/index.ts","line":188,"pattern":"publishJson(js, SUBJECT_EQUITY_CANDLES, candle)"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":963,"pattern":"subscribeWithReset(SUBJECT_EQUITY_CANDLES, ...)"},"data_shape":"EquityCandle JSON for live fanout","sanitization_at_boundary":"candle schema parse before emit and API parse; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E010","channel":"queue:flow.packets","producer":{"service":"compute","file":"services/compute/src/index.ts","line":574,"pattern":"publishJson(js, SUBJECT_FLOW_PACKETS, validated)"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":987,"pattern":"subscribeWithReset(SUBJECT_FLOW_PACKETS, ...)"},"data_shape":"FlowPacket JSON for storage/live fanout","sanitization_at_boundary":"schema parse before publish and in API; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E011","channel":"queue:flow.smart_money","producer":{"service":"compute","file":"services/compute/src/index.ts","line":1083,"pattern":"publishJson(js, SUBJECT_SMART_MONEY_EVENTS, smartMoneyEvent)"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":993,"pattern":"subscribeWithReset(SUBJECT_SMART_MONEY_EVENTS, ...)"},"data_shape":"SmartMoneyEvent JSON","sanitization_at_boundary":"schema parse; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E012","channel":"queue:flow.classifier_hits","producer":{"service":"compute","file":"services/compute/src/index.ts","line":1114,"pattern":"publishJson(js, SUBJECT_CLASSIFIER_HITS, hit)"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":999,"pattern":"subscribeWithReset(SUBJECT_CLASSIFIER_HITS, ...)"},"data_shape":"ClassifierHitEvent JSON","sanitization_at_boundary":"schema parse; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E013","channel":"queue:flow.alerts","producer":{"service":"compute","file":"services/compute/src/index.ts","line":1151,"pattern":"publishJson(js, SUBJECT_ALERTS, alert)"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":1005,"pattern":"subscribeWithReset(SUBJECT_ALERTS, ...)"},"data_shape":"AlertEvent JSON","sanitization_at_boundary":"schema parse; no message authentication","trust_tagged":"none"},
|
||||||
|
{"id":"E014","channel":"queue:flow.news","producer":{"service":"ingest-news","file":"services/ingest-news/src/index.ts","line":158,"pattern":"publishJson(js, SUBJECT_NEWS, story)"},"consumer":{"service":"api","file":"services/api/src/index.ts","line":1281,"pattern":"NewsStorySchema.parse(...); insertNewsStory(clickhouse, payload); fanoutLive(...)"},"data_shape":"NewsStory JSON from external Alpaca news feed","sanitization_at_boundary":"NewsStorySchema.parse only; no NATS subject-level source authentication in compose","trust_tagged":"NATS subject name implies trusted ingest-news origin"},
|
||||||
|
{"id":"E015","channel":"db-table:news","producer":{"service":"api","file":"services/api/src/index.ts","line":1281,"pattern":"insertNewsStory(clickhouse, payload)"},"consumer":{"service":"api/web","file":"packages/storage/src/clickhouse.ts","line":1289,"pattern":"FROM news"},"data_shape":"persisted NewsStory columns served by /news/history/news","sanitization_at_boundary":"ClickHouse insert/read typed; UI rendering not re-derived here","trust_tagged":"database as durable trust boundary"}
|
||||||
|
],
|
||||||
|
"coverage_gaps": [
|
||||||
|
{"reason":"third-party/external HTTP client calls excluded from internal edge findings","location":"services/ingest-news/src/index.ts:109; services/ingest-equities/src/adapters/alpaca.ts:158; services/ingest-options/src/adapters/alpaca.ts:159","expression":"fetch(provider URLs)"},
|
||||||
|
{"reason":"unmatched in-repo producer or consumer for raw options.prints stream; likely storage/replay or external consumer","location":"services/ingest-options/src/index.ts:430","expression":"SUBJECT_OPTION_PRINTS"},
|
||||||
|
{"reason":"Docker compose NATS command has JetStream only and no auth/ACL/TLS flags","location":"deployment/docker/docker-compose.yml:166","expression":"command: ['-js', '-sd', '/data']"}
|
||||||
|
]
|
||||||
|
}
|
||||||
27
piolium/attack-surface/cross-service-edges.md
Normal file
27
piolium/attack-surface/cross-service-edges.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Cross-Service Edges
|
||||||
|
|
||||||
|
Multi-service topology confirmed from `services/*`, `apps/*`, shared `packages/*`, and `deployment/docker/docker-compose.yml`.
|
||||||
|
|
||||||
|
| Edge | Channel | Producer | Consumer | Data shape | Boundary notes |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| E001 | http | web `apps/web/app/api/admin/synthetic/shared.ts:51` | api `services/api/src/index.ts:1364` | admin synthetic JSON | web injects admin bearer token; see p5 authz finding |
|
||||||
|
| E002 | queue `options.prints` | ingest-options `services/ingest-options/src/index.ts:430` | unmatched/external | `OptionPrint` | schema parse before publish; no message auth observed |
|
||||||
|
| E003 | queue `options.prints.signal` | ingest-options `services/ingest-options/src/index.ts:432` | compute `services/compute/src/index.ts:1501` | signal `OptionPrint` | signal flag trusted across NATS |
|
||||||
|
| E004 | queue `options.prints.signal` | ingest-options/replay `services/replay/src/index.ts:407` | api `services/api/src/index.ts:945` | live option print | schema parse in API; no message auth observed |
|
||||||
|
| E005 | queue `options.nbbo` | ingest-options `services/ingest-options/src/index.ts:460` | compute `services/compute/src/index.ts:1537` | `OptionNBBO` | schema parse; no message auth observed |
|
||||||
|
| E006 | queue `equities.prints` | ingest-equities `services/ingest-equities/src/index.ts:266` | compute `services/compute/src/index.ts:1573` | `EquityPrint` | schema parse; no message auth observed |
|
||||||
|
| E007 | queue `equities.prints` | ingest-equities `services/ingest-equities/src/index.ts:266` | candles `services/candles/src/index.ts:341` | `EquityPrint` | schema parse; no message auth observed |
|
||||||
|
| E008 | queue `equities.quotes` | ingest-equities `services/ingest-equities/src/index.ts:292` | ingest-options `services/ingest-options/src/index.ts:476` | `EquityQuote` | used as enrichment context |
|
||||||
|
| E009 | queue `equities.candles` | candles `services/candles/src/index.ts:188` | api `services/api/src/index.ts:963` | `EquityCandle` | live fanout and storage path |
|
||||||
|
| E010 | queue `flow.packets` | compute `services/compute/src/index.ts:574` | api `services/api/src/index.ts:987` | `FlowPacket` | derived analytics live/storage path |
|
||||||
|
| E011 | queue `flow.smart_money` | compute `services/compute/src/index.ts:1083` | api `services/api/src/index.ts:993` | `SmartMoneyEvent` | derived analytics live/storage path |
|
||||||
|
| E012 | queue `flow.classifier_hits` | compute `services/compute/src/index.ts:1114` | api `services/api/src/index.ts:999` | `ClassifierHitEvent` | derived analytics live/storage path |
|
||||||
|
| E013 | queue `flow.alerts` | compute `services/compute/src/index.ts:1151` | api `services/api/src/index.ts:1005` | `AlertEvent` | broadcast/fanout path |
|
||||||
|
| E014 | queue `flow.news` | ingest-news `services/ingest-news/src/index.ts:158` | api `services/api/src/index.ts:1281` | `NewsStory` | API persists and fans out news; no NATS auth/ACL in compose |
|
||||||
|
| E015 | db table `news` | api `services/api/src/index.ts:1281` | API/web via storage `packages/storage/src/clickhouse.ts:1289` | persisted news | durable dataflow through ClickHouse |
|
||||||
|
|
||||||
|
## Coverage gaps
|
||||||
|
|
||||||
|
- Provider HTTP calls are external (`Alpaca`/market data) and were not treated as internal service edges.
|
||||||
|
- Raw `options.prints` has a producer but no in-repo durable consumer identified in this pass.
|
||||||
|
- NATS is configured in compose as `nats -js -sd /data` with no auth/ACL/TLS flags; queue source identity is therefore a cross-service trust assumption.
|
||||||
34
piolium/attack-surface/deep-cleanup-summary.json
Normal file
34
piolium/attack-surface/deep-cleanup-summary.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"summaryPath": "piolium/attack-surface/deep-cleanup-summary.json",
|
||||||
|
"removed": [
|
||||||
|
"piolium/tmp",
|
||||||
|
"piolium/chamber-workspace",
|
||||||
|
"piolium/adversarial-reviews",
|
||||||
|
"piolium/bypass-analysis",
|
||||||
|
"piolium/codeql-artifacts",
|
||||||
|
"piolium/codeql-queries",
|
||||||
|
"piolium/semgrep-rules",
|
||||||
|
"piolium/confirm-workspace",
|
||||||
|
"piolium/real-env-evidence",
|
||||||
|
"piolium/findings-draft"
|
||||||
|
],
|
||||||
|
"missing": [
|
||||||
|
"piolium/probe-workspace",
|
||||||
|
"piolium/agentic-actions-res",
|
||||||
|
"piolium/codeql-res",
|
||||||
|
"piolium/semgrep-res",
|
||||||
|
"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/confirmation-report.md",
|
||||||
|
"piolium/audit-state.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
34
piolium/attack-surface/deep-probe-summary.md
Normal file
34
piolium/attack-surface/deep-probe-summary.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Stage 08 Manual Attack Surface Probe Summary
|
||||||
|
|
||||||
|
Status: complete
|
||||||
|
Mode: single-team MVP
|
||||||
|
Inventory: `piolium/attack-surface/manual-attack-surface-inventory.md`
|
||||||
|
|
||||||
|
## Sources reviewed
|
||||||
|
- `piolium/attack-surface/knowledge-base-report.md`
|
||||||
|
- `piolium/attack-surface/candidates-summary.md`
|
||||||
|
- P3-P7 artifacts: public route authz matrix, source/sink flows, spec gap summary, state/concurrency summary
|
||||||
|
- Source files for selected slices: `services/api/src/index.ts`, `apps/web/app/api/admin/synthetic/**`, `apps/web/app/terminal.tsx`, `services/ingest-news/src/index.ts`, `docker-compose.yml`
|
||||||
|
|
||||||
|
## Inline hypotheses and verification
|
||||||
|
|
||||||
|
| ID | Reasoning | Hypothesis | Verification result | Draft |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| H1 | Backward | If synthetic admin control is high-impact, look backward from `writeSyntheticControlState` to see whether every caller is authenticated as an admin user. | Validated: API requires bearer token, but Next public route injects that token for any caller when enabled (`shared.ts:25-55`; route handlers at `status/route.ts:5-7`, `control/route.ts:5-17`; API mutation at `index.ts:1380-1388`). | `piolium/findings-draft/p8-001-public-next-admin-proxy-synthetic-control.md` |
|
||||||
|
| H2 | Backward | If provider-controlled HTML can execute in the browser, trace from feed `content` to DOM sinks. | Validated as fragile stored-XSS boundary: `item.content` becomes `content_html` (`ingest-news/src/index.ts:76-96`), regex sanitizer is used (`terminal.tsx:1272-1287`), then `dangerouslySetInnerHTML` (`terminal.tsx:5008-5009`). | `piolium/findings-draft/p8-002-provider-news-html-regex-sanitizer-xss.md` |
|
||||||
|
| H3 | Contradiction | The system assumes infra is internal-only; check for a deployment artifact that contradicts this by publishing internal services. | Validated: root compose publishes ClickHouse `8123/9000`, Redis `6379`, and NATS `4222/8222` without credentials/TLS/ACLs visible (`docker-compose.yml:4-24`). | `piolium/findings-draft/p8-003-root-compose-exposes-unauthenticated-infrastructure.md` |
|
||||||
|
| H4 | Contradiction | The API relies on deployment perimeter for proprietary data; check whether WS route code enforces auth/origin if perimeter is absent. | Validated: WS upgrades happen by path only (`services/api/src/index.ts:1846-1936`); live messages can subscribe and receive snapshots without auth (`index.ts:1982-2008`). | `piolium/findings-draft/p8-004-unauthenticated-websocket-market-streams.md` |
|
||||||
|
|
||||||
|
## Coverage by slice
|
||||||
|
|
||||||
|
| Slice | Public routes / channels | Attacker source | Sink | Result |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Synthetic admin | `/api/admin/synthetic/*`, `/admin/synthetic/*` | Anonymous browser + feature/env enabled | NATS KV synthetic control | Finding drafted P8-001 |
|
||||||
|
| News HTML | `/history/news`, UI news drawer | Provider `item.content` | Browser DOM `dangerouslySetInnerHTML` | Finding drafted P8-002 |
|
||||||
|
| Infra services | Host ports `8123`, `9000`, `6379`, `4222`, `8222` | Network client | ClickHouse/Redis/NATS | Finding drafted P8-003 |
|
||||||
|
| WebSockets | `/ws/*`, `/ws/live` | Anonymous WS client / cross-site browser | Live broadcasts/snapshots | Finding drafted P8-004 |
|
||||||
|
| REST history/replay | `/history/*`, `/replay/*` | Anonymous HTTP query params | ClickHouse query reads | Already covered by previous P4/P5; not re-drafted except WS focus |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Several P8 findings intentionally promote/refresh earlier P4-P7 candidates with manual file:line evidence, as requested for Stage 08 drafts.
|
||||||
|
- No SQL injection was promoted in this pass; prior artifacts show query builders commonly use zod parsing, clamps, and quote helpers, while the higher-impact verified paths above had clearer exploitability.
|
||||||
73
piolium/attack-surface/deps.tsv
Normal file
73
piolium/attack-surface/deps.tsv
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
./apps/desktop/package.json @electron-forge/cli ^7.8.1
|
||||||
|
./apps/desktop/package.json @electron-forge/core ^7.11.1
|
||||||
|
./apps/desktop/package.json @electron-forge/maker-zip ^7.8.1
|
||||||
|
./apps/desktop/package.json @types/node ^24.10.1
|
||||||
|
./apps/desktop/package.json electron ^39.2.0
|
||||||
|
./apps/desktop/package.json typescript ^5.9.3
|
||||||
|
./apps/web/package.json @islandflow/types workspace:*
|
||||||
|
./apps/web/package.json @tanstack/react-virtual ^3.13.24
|
||||||
|
./apps/web/package.json lightweight-charts ^4.2.0
|
||||||
|
./apps/web/package.json next ^16.2.6
|
||||||
|
./apps/web/package.json react ^19.2.0
|
||||||
|
./apps/web/package.json react-dom ^19.2.0
|
||||||
|
./deployment/docker/workspace-root/package.json @pierre/diffs ^1.2.2
|
||||||
|
./package.json @pierre/diffs ^1.2.2
|
||||||
|
./packages/bus/package.json @islandflow/types workspace:*
|
||||||
|
./packages/bus/package.json nats ^2.24.0
|
||||||
|
./packages/config/package.json zod ^3.23.8
|
||||||
|
./packages/storage/package.json @clickhouse/client ^0.2.6
|
||||||
|
./packages/storage/package.json @islandflow/types workspace:*
|
||||||
|
./packages/types/package.json zod ^3.23.8
|
||||||
|
./services/api/package.json @islandflow/bus workspace:*
|
||||||
|
./services/api/package.json @islandflow/config workspace:*
|
||||||
|
./services/api/package.json @islandflow/observability workspace:*
|
||||||
|
./services/api/package.json @islandflow/storage workspace:*
|
||||||
|
./services/api/package.json @islandflow/types workspace:*
|
||||||
|
./services/api/package.json redis ^5.10.0
|
||||||
|
./services/api/package.json zod ^3.23.8
|
||||||
|
./services/candles/package.json @islandflow/bus workspace:*
|
||||||
|
./services/candles/package.json @islandflow/config workspace:*
|
||||||
|
./services/candles/package.json @islandflow/observability workspace:*
|
||||||
|
./services/candles/package.json @islandflow/storage workspace:*
|
||||||
|
./services/candles/package.json @islandflow/types workspace:*
|
||||||
|
./services/candles/package.json redis ^5.10.0
|
||||||
|
./services/candles/package.json zod ^3.23.8
|
||||||
|
./services/compute/package.json @islandflow/bus workspace:*
|
||||||
|
./services/compute/package.json @islandflow/config workspace:*
|
||||||
|
./services/compute/package.json @islandflow/observability workspace:*
|
||||||
|
./services/compute/package.json @islandflow/refdata workspace:*
|
||||||
|
./services/compute/package.json @islandflow/storage workspace:*
|
||||||
|
./services/compute/package.json @islandflow/types workspace:*
|
||||||
|
./services/compute/package.json redis ^5.10.0
|
||||||
|
./services/compute/package.json zod ^3.23.8
|
||||||
|
./services/eod-enricher/package.json @islandflow/config workspace:*
|
||||||
|
./services/eod-enricher/package.json @islandflow/observability workspace:*
|
||||||
|
./services/ingest-equities/package.json @islandflow/bus workspace:*
|
||||||
|
./services/ingest-equities/package.json @islandflow/config workspace:*
|
||||||
|
./services/ingest-equities/package.json @islandflow/observability workspace:*
|
||||||
|
./services/ingest-equities/package.json @islandflow/storage workspace:*
|
||||||
|
./services/ingest-equities/package.json @islandflow/types workspace:*
|
||||||
|
./services/ingest-equities/package.json ws ^8.21.0
|
||||||
|
./services/ingest-equities/package.json zod ^3.23.8
|
||||||
|
./services/ingest-news/package.json @islandflow/bus workspace:*
|
||||||
|
./services/ingest-news/package.json @islandflow/config workspace:*
|
||||||
|
./services/ingest-news/package.json @islandflow/observability workspace:*
|
||||||
|
./services/ingest-news/package.json @islandflow/types workspace:*
|
||||||
|
./services/ingest-news/package.json ws ^8.21.0
|
||||||
|
./services/ingest-news/package.json zod ^3.23.8
|
||||||
|
./services/ingest-options/package.json @islandflow/bus workspace:*
|
||||||
|
./services/ingest-options/package.json @islandflow/config workspace:*
|
||||||
|
./services/ingest-options/package.json @islandflow/observability workspace:*
|
||||||
|
./services/ingest-options/package.json @islandflow/storage workspace:*
|
||||||
|
./services/ingest-options/package.json @islandflow/types workspace:*
|
||||||
|
./services/ingest-options/package.json @msgpack/msgpack ^3.1.3
|
||||||
|
./services/ingest-options/package.json ws ^8.21.0
|
||||||
|
./services/ingest-options/package.json zod ^3.23.8
|
||||||
|
./services/refdata/package.json @islandflow/config workspace:*
|
||||||
|
./services/refdata/package.json @islandflow/observability workspace:*
|
||||||
|
./services/replay/package.json @islandflow/bus workspace:*
|
||||||
|
./services/replay/package.json @islandflow/config workspace:*
|
||||||
|
./services/replay/package.json @islandflow/observability workspace:*
|
||||||
|
./services/replay/package.json @islandflow/storage workspace:*
|
||||||
|
./services/replay/package.json @islandflow/types workspace:*
|
||||||
|
./services/replay/package.json zod ^3.23.8
|
||||||
|
429
piolium/attack-surface/knowledge-base-report.md
Normal file
429
piolium/attack-surface/knowledge-base-report.md
Normal file
|
|
@ -0,0 +1,429 @@
|
||||||
|
# Islandflow Phase 3 Architecture & Threat Model KB
|
||||||
|
|
||||||
|
Generated for Stage 03 `/piolium-deep` on 2026-05-27. Evidence: `README.md`, `package.json`, `services/api/src/index.ts`, `packages/storage/src/clickhouse.ts`, `services/ingest-*`, `packages/bus`, `apps/web`, `apps/desktop`, and `deployment/docker/docker-compose.yml`.
|
||||||
|
|
||||||
|
## Project Classification
|
||||||
|
|
||||||
|
### Project Type
|
||||||
|
- **Web app**: `apps/web` is a Next.js 16 UI with public pages (`/`, `/tape`, `/signals`, `/charts`, `/news`, `/options`, `/replay`) and Next route handlers for synthetic-admin proxying.
|
||||||
|
- **API / WebSocket gateway**: `services/api` is a Bun HTTP server exposing REST history/live/replay endpoints and many WebSocket channels.
|
||||||
|
- **Workers / stream processors**: `services/ingest-options`, `services/ingest-equities`, `services/ingest-news`, `services/compute`, `services/candles`, `services/replay`, `services/refdata`.
|
||||||
|
- **Desktop app**: `apps/desktop` is an Electron wrapper around the hosted/local web app.
|
||||||
|
- **Internal libraries**: `packages/types`, `packages/storage`, `packages/bus`, `packages/config`, `packages/observability`.
|
||||||
|
- **Deployment/CI tooling**: Docker Compose VPS deployment, Bun scripts, Forgejo/GitHub Actions docs/workflows.
|
||||||
|
|
||||||
|
Purpose: personal-use, event-sourced market microstructure research platform that ingests external market/news feeds, normalizes/publishes events over NATS/JetStream, persists to ClickHouse/Redis, computes derived flow/smart-money artifacts, and exposes live/replay/history through REST and WebSockets.
|
||||||
|
|
||||||
|
## Architecture Model
|
||||||
|
|
||||||
|
### Components
|
||||||
|
| Component | Key files | Role | Security relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Next.js web | `apps/web/app/**`, `apps/web/app/api/admin/synthetic/**` | UI + admin proxy | Browser input, rendering news/market data, admin proxy token forwarding |
|
||||||
|
| API gateway | `services/api/src/index.ts` | Bun REST/WebSocket server | Main network boundary; auth only for synthetic admin; query params to ClickHouse; WS fanout/subscription handling |
|
||||||
|
| Storage | `packages/storage/src/clickhouse.ts` | ClickHouse schema, insert/fetch query builders | SQL string construction, cursor pagination, record normalization |
|
||||||
|
| Bus | `packages/bus/src/**` | NATS/JetStream streams, subjects, KV synthetic control | Internal message integrity boundary; subject abuse/replay risks |
|
||||||
|
| Ingest options | `services/ingest-options/src/**`, `py/*` | Alpaca ws/rest, Databento/IBKR Python sidecars, msgpack/json parsing | Untrusted third-party feed data and child-process stdout enter system |
|
||||||
|
| Ingest equities/news | `services/ingest-equities/src/**`, `services/ingest-news/src/index.ts` | Alpaca feed ingestion | WebSocket/REST parsing, news HTML/content propagation |
|
||||||
|
| Compute/candles/replay | `services/compute/src/**`, `services/candles/src/**`, `services/replay/src/index.ts` | Derived events and replay | Trusts NATS/ClickHouse inputs; can amplify poisoned data |
|
||||||
|
| Electron shell | `apps/desktop/src/main.ts`, `apps/desktop/src/security.ts` | Hosted/local app wrapper | Origin/navigation/sandbox boundary; env-controlled start URL |
|
||||||
|
| Infra | `deployment/docker/docker-compose.yml` | Web, API, NATS, ClickHouse, Redis | Bind addresses, unauthenticated internal services, secrets in env |
|
||||||
|
|
||||||
|
### Trust Boundaries
|
||||||
|
1. **Internet/browser -> Next.js web/API**: HTTP and WebSocket requests. Public API appears largely unauthenticated except synthetic admin endpoints.
|
||||||
|
2. **Next.js admin proxy -> API synthetic admin**: `apps/web/app/api/admin/synthetic/shared.ts` forwards `Authorization: Bearer ${SYNTHETIC_ADMIN_TOKEN}` to `NEXT_PUBLIC_API_URL`; feature gated by `NEXT_PUBLIC_SYNTHETIC_ADMIN=1`.
|
||||||
|
3. **External market/news providers -> ingest workers**: Alpaca REST/WS, Databento replay, IBKR bridge; data is untrusted until parsed/validated by zod/shared schemas.
|
||||||
|
4. **Python child processes -> TypeScript ingest**: `Bun.spawn` stdout JSON lines in Databento/IBKR adapters are untrusted local-process output and a command/argument construction boundary.
|
||||||
|
5. **Services -> NATS/JetStream**: internal event bus subjects determine which events reach compute/storage/API. No per-subject auth visible in compose (`nats -js -sd /data`).
|
||||||
|
6. **Services -> ClickHouse/Redis**: storage/cache boundary; query strings are manually built; Redis hot cache can affect live UI state.
|
||||||
|
7. **Electron shell -> remote/local web app -> external links**: trusted origins hardcoded; navigation guards route untrusted URLs to OS browser via `shell.openExternal`.
|
||||||
|
8. **Deployment edge/proxy -> containers**: Compose binds web/API to `127.0.0.1` by default and joins an external `npm-shared` network for reverse proxy. Security depends on edge routing and env overrides.
|
||||||
|
|
||||||
|
## DFD/CFD Slices
|
||||||
|
|
||||||
|
### DFD-1: Public API query params to ClickHouse history/replay
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Browser/API client] -->|GET /history/* /replay/* /prints/* query params| B[services/api Bun server]
|
||||||
|
B -->|zod/coerce parse limit/cursors/filters| C[storage fetch* functions]
|
||||||
|
C -->|manual SQL string + quoteString/clampLimit| D[(ClickHouse)]
|
||||||
|
D -->|JSONEachRow rows| B --> A
|
||||||
|
```
|
||||||
|
Risk: SQL injection if any string reaches query builder without `quoteString`; DoS via expensive ranges/large limits; data exposure because endpoints are unauthenticated.
|
||||||
|
|
||||||
|
### DFD-2: WebSocket live fanout/subscription filtering
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Browser WS client] -->|GET /ws/*; /ws/live messages| B[API websocket handler]
|
||||||
|
B -->|LiveClientMessageSchema / subscription state| C[LiveStateManager]
|
||||||
|
D[NATS events] --> E[API subscribers]
|
||||||
|
E -->|filter by subscription/channel| B --> A
|
||||||
|
```
|
||||||
|
Risk: unauthenticated streaming of potentially valuable feed/derived data; WS resource exhaustion; subscription filter bypass or malformed message DoS.
|
||||||
|
|
||||||
|
### DFD-3: External feeds to NATS/ClickHouse/UI
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Alpaca/Databento/IBKR/news feeds] -->|WS/REST/msgpack/JSON/child stdout| B[ingest workers]
|
||||||
|
B -->|schema parse/normalization| C[NATS subjects]
|
||||||
|
C --> D[compute/candles]
|
||||||
|
C --> E[storage writers]
|
||||||
|
E --> F[(ClickHouse)]
|
||||||
|
F --> G[API REST/WS] --> H[Web/Electron UI]
|
||||||
|
```
|
||||||
|
Risk: poisoned feed messages, malformed binary/JSON DoS, HTML/script content in news, bogus symbols/traces polluting derived analytics and UI.
|
||||||
|
|
||||||
|
### DFD-4: Synthetic admin control
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Browser] -->|/api/admin/synthetic/*| B[Next route handler]
|
||||||
|
B -->|Bearer SYNTHETIC_ADMIN_TOKEN| C[API /admin/synthetic/status/control]
|
||||||
|
C -->|writeSyntheticControlState| D[NATS KV synthetic control]
|
||||||
|
D --> E[synthetic ingest/backend mode]
|
||||||
|
```
|
||||||
|
Risk: token leakage/misconfiguration; SSRF-like proxying if `NEXT_PUBLIC_API_URL` is attacker-controlled; admin state changes control synthetic market behavior.
|
||||||
|
|
||||||
|
### DFD-5: Electron navigation
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Env ISLANDFLOW_DESKTOP_START_URL] --> B[resolveDesktopStartUrl]
|
||||||
|
B -->|trusted origin only| C[BrowserWindow]
|
||||||
|
C -->|will-navigate/window.open| D[Navigation guards]
|
||||||
|
D -->|trusted: load| C
|
||||||
|
D -->|external safe URL| E[OS browser shell.openExternal]
|
||||||
|
```
|
||||||
|
Risk: origin allowlist mistakes, openExternal abuse, remote content compromise; controls include sandbox, context isolation, no nodeIntegration, disabled permission requests.
|
||||||
|
|
||||||
|
### CFD-1: Request routing/auth decision in API
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Bun fetch(req)] --> B{path/method}
|
||||||
|
B -->|/health| Z[public ok]
|
||||||
|
B -->|/admin/synthetic/*| C[authenticateSyntheticAdminRequest]
|
||||||
|
C -->|fail| D[401/403]
|
||||||
|
C -->|pass| E[status/control KV]
|
||||||
|
B -->|all market/history/replay/ws paths| F[public handler no auth]
|
||||||
|
F --> G[parse params -> storage/WS]
|
||||||
|
```
|
||||||
|
Security-critical decision: only synthetic admin is protected; all other handlers rely on deployment/network exposure for access control.
|
||||||
|
|
||||||
|
### CFD-2: Ingest validation/control flow
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[adapter selected by env] --> B{synthetic/alpaca/databento/ibkr}
|
||||||
|
B --> C[external REST/WS or Bun.spawn]
|
||||||
|
C --> D[decode JSON/msgpack/lines]
|
||||||
|
D --> E{schema/field checks}
|
||||||
|
E -->|valid| F[publishJson to NATS]
|
||||||
|
E -->|invalid| G[drop/log/continue]
|
||||||
|
```
|
||||||
|
Security-critical decision: schema parsing and field bounds decide whether untrusted external data becomes authoritative event stream.
|
||||||
|
|
||||||
|
### CFD-3: Deployment exposure
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[.env / compose vars] --> B{WEB_BIND_IP/API_BIND_IP}
|
||||||
|
B -->|default 127.0.0.1| C[local reverse proxy boundary]
|
||||||
|
B -->|0.0.0.0 override| D[direct public exposure]
|
||||||
|
C --> E[external npm-shared network]
|
||||||
|
D --> F[public unauth API/WS if firewall absent]
|
||||||
|
```
|
||||||
|
Security-critical decision: production auth depends heavily on bind IP/reverse proxy/firewall settings.
|
||||||
|
|
||||||
|
## Attack Surface
|
||||||
|
|
||||||
|
### Attacker-controlled sources
|
||||||
|
- HTTP paths/query/body to `services/api` REST endpoints: `/prints/options`, `/nbbo/options`, `/prints/equities`, `/prints/equities/range`, `/quotes/equities`, `/candles/equities`, `/joins/equities`, `/dark/inferred`, `/flow/*`, `/news`, `/history/*`, `/replay/*`, `/lookup/options-support`, `/*/by-*`, `/flow/alerts/:trace/context`.
|
||||||
|
- WebSocket connections/messages to `/ws/options`, `/ws/options-nbbo`, `/ws/equities`, `/ws/equity-candles`, `/ws/equity-quotes`, `/ws/equity-joins`, `/ws/inferred-dark`, `/ws/flow`, `/ws/classifier-hits`, `/ws/smart-money`, `/ws/alerts`, `/ws/live`.
|
||||||
|
- Next.js route handlers `/api/admin/synthetic/status` and `/api/admin/synthetic/control` when admin feature enabled.
|
||||||
|
- Market/news provider payloads from Alpaca REST/WS, Databento replay output, IBKR bridge output.
|
||||||
|
- Environment variables: service URLs, bind IPs, tokens/API keys, Python binary path, adapter selection, Electron start URL.
|
||||||
|
- NATS messages/KV state if any service or network peer can publish.
|
||||||
|
- ClickHouse/Redis contents if storage is compromised or seeded with malicious data.
|
||||||
|
- CI/deploy script inputs: branch names, PR refs, env secrets, deployment hosts.
|
||||||
|
|
||||||
|
### High-value sinks
|
||||||
|
- ClickHouse query execution in `packages/storage/src/clickhouse.ts`.
|
||||||
|
- NATS publish/subscribe/KV in `packages/bus/src/**` and service consumers.
|
||||||
|
- Redis hot cache in `services/api/src/live.ts`/candles.
|
||||||
|
- Browser DOM rendering in `apps/web`, especially news `content_html`, headlines, URLs, explanations JSON.
|
||||||
|
- Electron `shell.openExternal` and `BrowserWindow.loadURL`.
|
||||||
|
- `Bun.spawn` in Databento/IBKR adapters and deployment scripts invoking shell/ssh/docker.
|
||||||
|
- Logs/metrics containing URLs, provider errors, trace IDs, possibly secrets if not redacted.
|
||||||
|
|
||||||
|
## Framework Contracts and Hidden Control Channels
|
||||||
|
|
||||||
|
- **Bun server routing**: `services/api/src/index.ts` uses manual `if` routing. Path normalization, percent-decoding, and regex routes (`/flow/packets/:id`, `/flow/alerts/:trace/context`) are security-sensitive.
|
||||||
|
- **Next.js route handlers**: `apps/web/app/api/admin/synthetic/**` are forced dynamic and proxy to the API. Security depends on feature env and server-side `SYNTHETIC_ADMIN_TOKEN`; `NEXT_PUBLIC_API_URL` is a hidden control channel for target API base.
|
||||||
|
- **Next.js public env**: variables prefixed `NEXT_PUBLIC_*` are exposed to clients. Do not place secrets there. `NEXT_PUBLIC_API_URL` controls browser/API reachability and admin proxy target base in server code.
|
||||||
|
- **Proxy/bind assumptions**: Compose defaults `WEB_BIND_IP` and `API_BIND_IP` to `127.0.0.1`; external access likely via reverse proxy on `npm-shared`. If overridden to `0.0.0.0`, unauthenticated API/WS become directly reachable.
|
||||||
|
- **Internal services unauthenticated by default**: NATS, ClickHouse, Redis compose definitions do not show credentials/TLS. The Docker network is an implicit trust boundary.
|
||||||
|
- **Header contracts**: Synthetic admin uses `Authorization: Bearer`; no other route-level auth headers observed. If a reverse proxy injects auth headers, handlers do not re-check them.
|
||||||
|
- **WebSocket contracts**: Bun `server.upgrade` accepts based on path only; no Origin/auth check observed. `/ws/live` message schema is the main control.
|
||||||
|
- **Runtime modes**: Synthetic/admin behavior depends on `SYNTHETIC_CONTROL_ENABLED`, `SYNTHETIC_ADMIN_TOKEN`, `NEXT_PUBLIC_SYNTHETIC_ADMIN`, adapter envs. API deliver policy and consumer reset affect stream replay behavior.
|
||||||
|
- **Electron contracts**: Trust is origin-based (`flow.deltaisland.io`, `127.0.0.1:3000`, `localhost:3000`); sandbox/contextIsolation/webSecurity are enabled; permission prompts denied; external URLs opened only when source is trusted.
|
||||||
|
- **Storage escaping contract**: ClickHouse string safety depends on local `quoteString`, `buildStringList`, `clamp*`, and typed table constants. Any future query builder bypassing these helpers is high risk.
|
||||||
|
|
||||||
|
## Threat Model
|
||||||
|
|
||||||
|
### Assets
|
||||||
|
- Alpaca/Databento/IBKR API credentials and NATS/ClickHouse/Redis URLs.
|
||||||
|
- Market/news data and derived smart-money alerts/flow packets (proprietary research value).
|
||||||
|
- Integrity of event stream, replay history, and classifier outputs.
|
||||||
|
- Availability of live API, WS fanout, NATS JetStream, ClickHouse, Redis.
|
||||||
|
- Admin synthetic-control state.
|
||||||
|
- Desktop user environment (external URL opening/browser trust).
|
||||||
|
- Deployment secrets and CI credentials.
|
||||||
|
|
||||||
|
### Threat actors
|
||||||
|
- Anonymous internet clients if web/API are exposed through reverse proxy or bind-IP override.
|
||||||
|
- Malicious/compromised market data provider, websocket MITM where TLS/config is weakened, or malformed feed data.
|
||||||
|
- Network peer/container on Docker shared/default networks.
|
||||||
|
- Operator/local attacker who can modify env vars or Python binary paths.
|
||||||
|
- Malicious webpage/content rendered in news/web UI, or compromised trusted origin in Electron.
|
||||||
|
- Supply-chain attacker via npm/Bun/Python dependencies or CI workflow changes.
|
||||||
|
|
||||||
|
### Abuse paths and priorities
|
||||||
|
| Threat | Boundary | Impact | Likelihood | Priority | Existing controls | Review focus |
|
||||||
|
|---|---|---:|---:|---:|---|---|
|
||||||
|
| Unauthenticated REST/WS data extraction or scraping | Internet -> API | Med/High | Med if exposed | High | Bind defaults to localhost | Confirm intended auth; add API auth/rate limits/Origin checks |
|
||||||
|
| Synthetic admin token bypass/leak/misproxy | Browser/Next -> API admin | Med | Med | High | Bearer token, feature flag | Verify `authenticateSyntheticAdminRequest`, proxy URL allowlist, no token in client bundle/logs |
|
||||||
|
| ClickHouse injection or expensive query DoS | HTTP params -> storage | High | Med | High | zod, clamp, `quoteString` | Custom SAST for string SQL helpers and unbounded ranges |
|
||||||
|
| Poisoned feed data corrupts analytics/UI | Provider -> ingest -> NATS/UI | High integrity | Med | High | schemas, field parsing | Validate schemas, size limits, HTML sanitization, anomaly handling |
|
||||||
|
| NATS/Redis/ClickHouse lateral abuse from network peer | Docker/shared network -> infra | High | Low/Med | High | localhost port binds for web/API only | Add service credentials/TLS/ACLs; network isolation |
|
||||||
|
| WebSocket resource exhaustion | Internet -> API WS | Med/High availability | Med | High | schema parse for live messages | Connection/message limits, heartbeat, per-IP quotas |
|
||||||
|
| Electron navigation/openExternal abuse | Web content -> desktop shell | High local user impact | Low/Med | Medium | origin allowlist, sandbox, no nodeIntegration | Verify external URL schemes, downloads, CSP |
|
||||||
|
| XSS via news/content or explanation rendering | Feed/API -> web DOM | High if same origin admin token/proxy | Med | High | news summary escaping fallback | Audit `dangerouslySetInnerHTML`, URL rendering, CSP |
|
||||||
|
| Child-process command/path misuse | Env -> Bun.spawn Python | Med/High | Low/Med | Medium | args array, script path constant | Validate `pythonBin`, avoid shell, handle stdout size |
|
||||||
|
| CI/deploy secret leakage or command injection | PR/env -> scripts/workflows | High | Low/Med | Medium | limited visible workflows | Audit deploy scripts and Forgejo workflow triggers |
|
||||||
|
|
||||||
|
### Recommended controls for later phases
|
||||||
|
- Treat API/WS as public unless proven behind authenticated reverse proxy; require handler-level auth for non-public data and admin controls.
|
||||||
|
- Add Origin/token checks and connection/message rate limits to WS endpoints.
|
||||||
|
- Centralize ClickHouse query construction; prefer parameterized ClickHouse client support if available.
|
||||||
|
- Sanitize or strip provider HTML before storage/rendering; add CSP in Next app.
|
||||||
|
- Add NATS/Redis/ClickHouse credentials/ACLs/TLS or restrict network access; do not rely on Docker network trust.
|
||||||
|
- Harden admin proxy with strict API base allowlist and server-only env names for secrets.
|
||||||
|
|
||||||
|
## Domain Attack Research
|
||||||
|
|
||||||
|
Identified domains: HTTP/Next.js, WebSocket, Electron, NATS/JetStream message bus, ClickHouse SQL/query construction, Redis cache, external market-data ingestion/parsing (JSON/msgpack), subprocess execution, Docker/deployment/CI, browser rendering/XSS. Mode B applies (security-sensitive dependencies as consumers). Mode C applies (HTTP/WS, SQL, Redis, message queues, Electron, parsing, subprocess, containers/CI). Mode A is not primary because Islandflow is not distributed as a public library/protocol, though internal package API sharp edges matter.
|
||||||
|
|
||||||
|
### Domain: HTTP API / Next.js / Bun routing
|
||||||
|
**Identified via:** `services/api` manual HTTP routing, `apps/web` Next.js app and route handlers, Next advisory history.
|
||||||
|
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Auth bypass / missing handler auth | Public routes unintentionally expose data/control | Find route handlers without auth checks; diff public route inventory | High |
|
||||||
|
| Path/matcher confusion | Encoded paths/trailing slashes bypass manual checks/proxy rules | Test encoded path variants and reverse proxy rewrites | Med |
|
||||||
|
| SSRF/open proxy via admin proxy | Server fetches attacker-controlled base/path | Track `new URL(path, NEXT_PUBLIC_API_URL)` and env controls | Med |
|
||||||
|
| Cache poisoning | Host/forwarded headers or Next caching leak dynamic data | Review caching headers, `dynamic`, reverse proxy config | Low/Med |
|
||||||
|
|
||||||
|
Custom SAST targets: route handlers in `services/api/src/index.ts` and `apps/web/app/api/**` lacking auth; `fetch(new URL(... env ...))`; use of `req.headers`/`Host`/`X-Forwarded-*`; public route changes. Manual checklist: confirm intended public endpoints; fuzz paths; enforce auth and rate limits. Research sources: advisory summary, wooyun-legacy web methodology, last30days/web-search class knowledge.
|
||||||
|
|
||||||
|
### Domain: WebSocket
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Unauthenticated data streaming | Any client subscribes to feed/alerts | Enumerate `/ws/*` upgrades without auth/origin checks | High |
|
||||||
|
| Resource exhaustion | Many connections/messages or huge frames | Look for max payload, conn limits, heartbeat | High |
|
||||||
|
| Subscription filter abuse | Malformed filters cause broad fanout or CPU use | Validate `LiveClientMessageSchema`, filter matching paths | Med |
|
||||||
|
|
||||||
|
Custom SAST: `serverRef.upgrade`, `websocket.message`, `JSON.parse`, zod parse error loops, broadcast loops. Manual: origin/auth tests; slow-client behavior; payload size tests.
|
||||||
|
|
||||||
|
### Domain: ClickHouse SQL / query construction
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| SQL injection | Manual string interpolation misses escaping | Taint HTTP params to `client.query({query})`; require `quoteString/clamp*` | High |
|
||||||
|
| Query DoS | wide time ranges/high cardinality IN/LIKE/position | Find unbounded arrays/ranges and expensive predicates | High |
|
||||||
|
| Data exfiltration | unauth history/replay endpoints dump proprietary data | Route inventory + auth absence | High |
|
||||||
|
|
||||||
|
Custom SAST: RemoteFlowSource query params/body -> `query:` template literals in `packages/storage`; array length to `IN`/OR predicates; limits > configured max. Manual: test quotes/unicode/null bytes; verify max IDs and ranges.
|
||||||
|
|
||||||
|
### Domain: NATS/JetStream message bus
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Subject spoofing | Network peer publishes fake market/admin events | Review connect options, credentials, subject ACLs | High |
|
||||||
|
| Replay/consumer confusion | Durable policy reset replays stale data as live | Trace `API_DELIVER_POLICY`, replay service controls | Med |
|
||||||
|
| KV control tampering | Synthetic control state modified by unauthorized peer | Review KV bucket ACL and admin endpoints | High |
|
||||||
|
|
||||||
|
Custom SAST: `publishJson`, `subscribeJson`, `writeSyntheticControlState`, unvalidated payloads. Manual: verify NATS auth/TLS in prod, subject permissions, event schemas.
|
||||||
|
|
||||||
|
### Domain: External feed parsing (JSON/msgpack/news HTML)
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Parser/resource DoS | Large JSON/msgpack/websocket frames exhaust memory/CPU | Locate decode/JSON.parse without size/time bounds | High |
|
||||||
|
| Schema confusion | Partial provider payload becomes valid incorrect event | Compare zod schemas and adapter field defaults | Med |
|
||||||
|
| Stored XSS via news HTML | Provider `content` stored/rendered as HTML | Trace `content_html` to React render sinks | High |
|
||||||
|
|
||||||
|
Custom SAST: `decode`, `JSON.parse`, `new TextDecoder`, `content_html`, `dangerouslySetInnerHTML`, URLs. Manual: malformed provider fixtures; max message sizes; sanitize HTML.
|
||||||
|
|
||||||
|
### Domain: Electron desktop
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Navigation escape | Untrusted page loaded in privileged shell | Check `loadURL`, origin allowlists, redirects | Med |
|
||||||
|
| openExternal abuse | Custom schemes/file URLs launched | Verify only http/https external URLs | Med |
|
||||||
|
| Node integration/IPC abuse | Web content gains local code exec | Check BrowserWindow preferences/preload/IPC | Low currently |
|
||||||
|
|
||||||
|
Custom SAST: `shell.openExternal`, `loadURL`, `setWindowOpenHandler`, `will-navigate`, BrowserWindow prefs. Manual: redirect chains, punycode/origin tests, CSP/download handling.
|
||||||
|
|
||||||
|
### Domain: Redis/cache
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Cache poisoning | Malicious internal publisher/data seeds hot live state | Trace key construction and schema validation | Med |
|
||||||
|
| Availability DoS | huge values/keys or no TTL memory growth | Review `set`/`lpush`/TTL use | Med |
|
||||||
|
| Unauthorized access | Redis default no password in compose | Deployment config review | High internal |
|
||||||
|
|
||||||
|
Custom SAST: Redis key builders with attacker input, missing TTL, `JSON.parse` of cache values.
|
||||||
|
|
||||||
|
### Domain: Subprocess / Python sidecars
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Command injection/path hijack | Env-controlled binary/args execute attacker program | Ensure no shell; validate `pythonBin`; constant script paths | Med |
|
||||||
|
| stdout parsing DoS | Child emits unbounded line/JSON | Limit line length and restart loops | Med |
|
||||||
|
| Secret leakage | API keys in args/env/logs | Review spawned args and stderr logging | Low/Med |
|
||||||
|
|
||||||
|
Custom SAST: `Bun.spawn`, env-derived args, `stderr: inherit`, readLines buffer growth.
|
||||||
|
|
||||||
|
### Domain: Docker/deployment/CI supply chain
|
||||||
|
| Attack | Description | Detection strategy | Relevance |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Insecure bind/exposure | API/NATS/ClickHouse/Redis reachable publicly | Parse compose ports/networks/env overrides | High |
|
||||||
|
| Secret leakage in deploy scripts | Tokens printed or sent to PR contexts | Review workflow triggers/scripts | Med |
|
||||||
|
| Dependency takeover/CVE | npm/Python base images/deps vulnerable | Dependency and image scanning | Med |
|
||||||
|
|
||||||
|
Custom SAST: workflows with untrusted PR + secrets, deploy scripts shell interpolation, Docker `ports` to `0.0.0.0`, no auth configs.
|
||||||
|
|
||||||
|
## Phase 4 CodeQL Extraction Targets
|
||||||
|
|
||||||
|
| Slice | Source type | Source | Sink kind | Sink |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| DFD-1 API params -> ClickHouse | RemoteFlowSource | URL search params/path/body in `services/api/src/index.ts` | sql-execution | `client.query({ query })` in `packages/storage/src/clickhouse.ts` |
|
||||||
|
| DFD-2 WS messages -> subscriptions/fanout | RemoteFlowSource | WebSocket `message`, path upgrade | deserialization / resource exhaustion | `LiveClientMessageSchema.parse`, JSON parse, broadcast/send loops |
|
||||||
|
| DFD-3 feeds -> NATS/storage/UI | RemoteFlowSource | WebSocket/REST provider messages, child stdout | deserialization / code/data injection | `JSON.parse`, msgpack `decode`, `publishJson`, `content_html` render sinks |
|
||||||
|
| DFD-4 admin proxy/control | RemoteFlowSource + EnvironmentVariable | Next request body; `NEXT_PUBLIC_API_URL`, `SYNTHETIC_ADMIN_TOKEN` | http-request / authz decision | `fetch(url.toString())`, `writeSyntheticControlState` |
|
||||||
|
| DFD-5 Electron navigation | EnvironmentVariable + RemoteFlowSource | `ISLANDFLOW_DESKTOP_START_URL`, page navigation/window.open URL | http-request / code-execution-adjacent | `BrowserWindow.loadURL`, `shell.openExternal` |
|
||||||
|
| Python sidecars | EnvironmentVariable | `DATABENTO_PYTHON_BIN`/`IBKR_*` env args | command-execution | `Bun.spawn` |
|
||||||
|
| Redis live state | RemoteFlowSource | NATS events, API filters | cache/data poisoning | Redis client methods, JSON cache serialization |
|
||||||
|
|
||||||
|
## Spec Gap Candidates
|
||||||
|
|
||||||
|
No formal RFC/spec commitments are declared. De facto contracts to check in Phase 9:
|
||||||
|
- HTTP/1.1 and WebSocket behavior (Bun server, `ws` clients).
|
||||||
|
- OCC option symbol parsing and market-data provider contracts (Alpaca, Databento, IBKR).
|
||||||
|
- NATS/JetStream subject and durable consumer semantics.
|
||||||
|
- ClickHouse SQL escaping/string literal semantics.
|
||||||
|
- Electron security model for sandbox/context isolation/navigation.
|
||||||
|
|
||||||
|
## Coverage Gaps
|
||||||
|
|
||||||
|
- Production reverse proxy configuration is not present; API exposure/auth assumptions must be validated from deployment host.
|
||||||
|
- Full `services/api/src/index.ts` is large; later phases should extract route inventory mechanically and test every route.
|
||||||
|
- UI rendering sinks (`apps/web/app/**`) require deeper review for `dangerouslySetInnerHTML`, external links, and CSP.
|
||||||
|
- NATS/ClickHouse/Redis production credentials/TLS/ACLs are not visible in compose; if configured outside repo, collect them.
|
||||||
|
- Rate limiting is not apparent for REST/WS; availability risk remains unquantified.
|
||||||
|
- CI canonical path in README references `.forgejo/workflows`, while `.github/workflows` also exists; audit both.
|
||||||
|
- Domain research used repository/advisory evidence and built-in playbook knowledge; live web/MCP research was not available in this runtime.
|
||||||
|
|
||||||
|
|
||||||
|
## Static Analysis Summary
|
||||||
|
|
||||||
|
Stage 04 prioritized `piolium/attack-surface/candidates-summary.md` and `candidates.jsonl`, especially high-score hidden-control-channel, WebSocket, SQL/query, SSRF, and unsafe HTML candidates. `codeql` and `semgrep` were checked before scanning but were unavailable on PATH, so the run used the required fallback (`grep` + `read`) rather than fabricated scan results. Semgrep Pro could not be executed because the CLI was missing; the fallback reason is documented here, and transient `piolium/semgrep-res/` was removed during cleanup.
|
||||||
|
|
||||||
|
Artifacts produced:
|
||||||
|
- `piolium/attack-surface/source-sink-flows-all-severities.md`
|
||||||
|
- Structural fallback JSON/SARIF under `piolium/codeql-artifacts/`
|
||||||
|
- Custom placeholders/rules under `piolium/codeql-queries/` and `piolium/semgrep-rules/`
|
||||||
|
- Draft findings: `p4-001`, `p4-002`, `p4-003` (cap 30 respected)
|
||||||
|
|
||||||
|
Built-in CodeQL suites run: none (`codeql` unavailable). Built-in Semgrep rulesets run: none (`semgrep` unavailable). Custom Semgrep rule file was authored but not executed by Semgrep; manual grep/read validation matched the risky instances.
|
||||||
|
|
||||||
|
## CodeQL Structural Analysis
|
||||||
|
|
||||||
|
CodeQL database build/extraction was skipped because the `codeql` binary was not installed on PATH. Fallback structural extraction still populated the mandatory files for downstream phases:
|
||||||
|
|
||||||
|
- Entry points: 7 (`piolium/codeql-artifacts/entry-points.json`)
|
||||||
|
- Sinks: 8 (`piolium/codeql-artifacts/sinks.json`)
|
||||||
|
- Reachable slices: 5 of 7 (`piolium/codeql-artifacts/call-graph-slices.json`)
|
||||||
|
|
||||||
|
### Machine-Generated DFD Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[HTTP req/query params] --> B[services/api routes]
|
||||||
|
B --> C[ClickHouse query sinks]
|
||||||
|
W[WS upgrade/message] --> X[JSON.parse + Zod]
|
||||||
|
X --> Y[live subscriptions/socket.send]
|
||||||
|
N[Provider news content_html] --> S[regex sanitizeNewsHtml]
|
||||||
|
S --> H[dangerouslySetInnerHTML]
|
||||||
|
P[Next admin proxy routes/env] --> F[fetch API base]
|
||||||
|
E[Env Python bin/args] --> R[Bun.spawn]
|
||||||
|
D[Electron navigation] -. no path in fallback .-> Z[loadURL/openExternal]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Machine-Generated CFD Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Q[Request arrives] --> R{Admin route?}
|
||||||
|
R -- yes --> T{Synthetic enabled + token matches?}
|
||||||
|
T -- pass --> U[writeSyntheticControlState]
|
||||||
|
T -- fail --> V[401/404/409]
|
||||||
|
R -- no/data route --> K[No app auth]
|
||||||
|
K --> L[ClickHouse fetch JSON]
|
||||||
|
W[WS upgrade] --> O{Origin/auth checked?}
|
||||||
|
O -- no --> P[Accept socket/fanout]
|
||||||
|
N[News HTML] --> G{Regex sanitizer passes?}
|
||||||
|
G -- yes --> H[Render HTML]
|
||||||
|
```
|
||||||
|
|
||||||
|
Notable entry points not fully represented in Phase 3 DFD slices: client-side `window.location.host` API/WS selection and response `content-type` robustness checks. Notable sinks mapping to high-risk flows: `dangerouslySetInnerHTML`, WebSocket `socket.send`, and ClickHouse `client.query`.
|
||||||
|
|
||||||
|
## SAST Enrichment
|
||||||
|
|
||||||
|
| Finding | Classification | Attacker Control | Boundary | CodeQL Reachability | Verdict |
|
||||||
|
|---------|---------------|-----------------|----------|-------------------|---------|
|
||||||
|
| p4-001 stored-xss-news-html-regex-sanitizer | security | upstream news provider / bus publisher controls `content_html` | external feed -> browser DOM | reachable (fallback slice DFD-3) | keep |
|
||||||
|
| p4-002 unauthenticated-websocket-market-data-streams | security | remote client controls WS upgrade/messages | internet/proxy -> API live streams | reachable (fallback slice DFD-2) | keep |
|
||||||
|
| p4-003 public-api-exposes-queryable-market-history | security | remote client controls HTTP params if API exposed | internet/proxy -> ClickHouse-backed data API | reachable (fallback slice DFD-1) | keep |
|
||||||
|
| admin-proxy-env-base-url-fetch | env/tooling/admin-only | deployment env controls `NEXT_PUBLIC_API_URL`; route path fixed | server env -> outbound fetch | reachable (fallback slice DFD-4) | drop as draft; monitor config |
|
||||||
|
| Python sidecar Bun.spawn | env/tooling/admin-only | env/config controls python binary/args | local service config -> subprocess | reachable (fallback Python sidecars) | drop |
|
||||||
|
| test secret literals | correctness/env | source-controlled tests | none | no-slice | drop |
|
||||||
|
| static redirects | correctness | no user-controlled URL | none | no-slice | drop |
|
||||||
|
|
||||||
|
## Spec Gap Analysis
|
||||||
|
|
||||||
|
### Gap: Root Docker Compose publishes unauthenticated ClickHouse, Redis, and NATS control planes
|
||||||
|
|
||||||
|
- **Contract**: Docker deployment/internal-service contract for infrastructure dependencies (ClickHouse, Redis, NATS/JetStream) should keep data/control planes internal unless credentials/TLS/ACLs are configured.
|
||||||
|
- **Security Assumption**: Application services treat ClickHouse, Redis, and NATS as trusted internal dependencies; API-layer validation/auth is not re-applied to direct database, cache, or message-bus clients.
|
||||||
|
- **Code Path**: `docker-compose.yml:1` — root compose publishes infrastructure ports; `deployment/docker/docker-compose.yml:120` — production compose keeps those services internal-only by omitting host `ports`.
|
||||||
|
- **Gap Type**: framework-contract | hidden-control-channel | proxy-trust | runtime-mode
|
||||||
|
- **Attack Vector**: A network attacker reaches the host-published service ports, publishes forged NATS messages, tampers with Redis state, or queries/modifies ClickHouse directly.
|
||||||
|
- **Exploit Conditions**: Root compose is used on a network-reachable host and host firewall does not block `8123`, `9000`, `6379`, `4222`, or `8222`.
|
||||||
|
- **Impact**: Data confidentiality/integrity compromise and bypass of API-layer controls for market history, live state, and event streams.
|
||||||
|
- **Severity**: HIGH
|
||||||
|
- **Evidence**: Root compose maps `8123:8123`, `9000:9000`, `6379:6379`, `4222:4222`, and `8222:8222`; production compose defines the same services without host `ports`.
|
||||||
|
|
||||||
|
|
||||||
|
## Authorization Audit
|
||||||
|
|
||||||
|
- Public routes matrix: `piolium/attack-surface/public-routes-authz-matrix.md`
|
||||||
|
- Public/network operations reviewed: 17 matrix rows covering API REST groups, API WebSocket groups, Next public pages, and Next synthetic-admin proxy routes.
|
||||||
|
- Frameworks covered: Bun manual routing/WebSocket upgrade, Next.js route handlers/file routes.
|
||||||
|
- Middleware/proxy-derived identity reviewed: backend synthetic bearer token, `x-synthetic-admin-token`, Next admin proxy token injection, bind/reverse-proxy exposure assumptions, WebSocket path-only upgrades.
|
||||||
|
- Drafts filed: 1 (`authz-missing-guard`): `piolium/findings-draft/p5-001-public-next-admin-proxy-confers-synthetic-admin.md`.
|
||||||
|
- Remaining review targets: unauthenticated market-data REST/history/replay/WebSocket surfaces are currently treated as intended-public/read-only, but should be chamber-reviewed against product policy because exposure depends on reverse proxy/bind settings and data may have proprietary value.
|
||||||
|
|
||||||
|
## State & Concurrency Audit
|
||||||
|
|
||||||
|
- State-holding entities catalogued: 8
|
||||||
|
- Concurrency primitives observed: JetStream manual ack/explicit ack; NATS KV for synthetic control. No language locks, DB transactions, SELECT FOR UPDATE, advisory locks, or Redis/distributed locks observed.
|
||||||
|
- Idempotency infrastructure: partial/in-memory only (`recentStructureEmits`, live/UI dedupe); no durable processed-event/idempotency store for JetStream consumers.
|
||||||
|
- Drafts filed: 2 (idempotency: 1, stale-read: 1)
|
||||||
|
|
||||||
|
## Cross-Service Taint Propagation
|
||||||
|
|
||||||
|
- Services analysed: 8
|
||||||
|
- Edges stitched: 15 (1 http, 0 grpc, 13 queue, 1 db-write, 0 file)
|
||||||
|
- Coverage gaps: provider-only HTTP calls excluded; raw `options.prints` has no in-repo consumer identified; NATS subject identity depends on deployment controls — see `piolium/attack-surface/cross-service-edges.md`
|
||||||
|
- Drafts filed: 1 (`queue-source-auth`: 1)
|
||||||
64
piolium/attack-surface/lite-recon.md
Normal file
64
piolium/attack-surface/lite-recon.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Lite Recon — Q0
|
||||||
|
|
||||||
|
Generated by piolium at 2026-05-27T05:18:10.214Z
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
- Path: `/Users/kell/dev/islandflow`
|
||||||
|
- Repository: (unknown)
|
||||||
|
- Total files (scanned): 291
|
||||||
|
- Total bytes (scanned): 3.5 MB
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
- Commit: ffbdbc337638004be49775c85a2f0b10b7e55563
|
||||||
|
- Branch: security-audit
|
||||||
|
- History available: true
|
||||||
|
|
||||||
|
Recent commits:
|
||||||
|
|
||||||
|
```
|
||||||
|
ffbdbc3 docs: add May 24 standup git summary
|
||||||
|
3300728 set up forgejo ci baseline
|
||||||
|
3c444b7 Merge pull request 'rename tape to options and replace web rail with overlay drawer' (#11) from sidebar-redesign into main
|
||||||
|
7ca0e05 rename tape to options and switch the web shell to a drawer
|
||||||
|
f056f6d clarify when turn docs are actually required
|
||||||
|
fda7d5f add turn doc for pierre diffs policy update
|
||||||
|
4a0e9e7 default turn-doc diffs to @pierre/diffs and add dependency
|
||||||
|
5ff2fa6 turn doc instruction tuning
|
||||||
|
2e48283 sync github mirror for docs pages workflow fix
|
||||||
|
aae3fa1 fix docs pages workflow for gh-pages branch deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Languages
|
||||||
|
|
||||||
|
- TypeScript: 134 file(s)
|
||||||
|
- Shell: 11 file(s)
|
||||||
|
- Python: 2 file(s)
|
||||||
|
|
||||||
|
## Build / Project Manifests
|
||||||
|
|
||||||
|
- `apps/desktop/package.json`
|
||||||
|
- `apps/web/package.json`
|
||||||
|
- `deployment/docker/Dockerfile.ingest-options`
|
||||||
|
- `deployment/docker/Dockerfile.service`
|
||||||
|
- `deployment/docker/Dockerfile.web`
|
||||||
|
- `deployment/docker/docker-compose.yml`
|
||||||
|
- `deployment/docker/workspace-root/package.json`
|
||||||
|
- `docker-compose.yml`
|
||||||
|
- `package.json`
|
||||||
|
- `packages/bus/package.json`
|
||||||
|
- `packages/config/package.json`
|
||||||
|
- `packages/observability/package.json`
|
||||||
|
- `packages/storage/package.json`
|
||||||
|
- `packages/types/package.json`
|
||||||
|
- `services/api/package.json`
|
||||||
|
- `services/candles/package.json`
|
||||||
|
- `services/compute/package.json`
|
||||||
|
- `services/eod-enricher/package.json`
|
||||||
|
- `services/ingest-equities/package.json`
|
||||||
|
- `services/ingest-news/package.json`
|
||||||
|
- `services/ingest-options/package.json`
|
||||||
|
- `services/ingest-options/py/requirements.txt`
|
||||||
|
- `services/refdata/package.json`
|
||||||
|
- `services/replay/package.json`
|
||||||
40
piolium/attack-surface/manual-attack-surface-inventory.md
Normal file
40
piolium/attack-surface/manual-attack-surface-inventory.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Manual Attack Surface Inventory (Stage 08)
|
||||||
|
|
||||||
|
## Highest-impact slices selected
|
||||||
|
1. Synthetic admin control: public Next.js route handlers proxy to API admin endpoints with server bearer token.
|
||||||
|
2. Provider/news HTML to browser DOM: Alpaca `content` is stored and later rendered through a regex sanitizer and `dangerouslySetInnerHTML`.
|
||||||
|
3. Live WebSocket/API market data exposure: public WS upgrades and history reads have no handler-level auth/origin checks.
|
||||||
|
4. Root Docker Compose infrastructure: ClickHouse, Redis, and NATS are published on host ports without credentials in the compose file.
|
||||||
|
|
||||||
|
## Public routes / URLs
|
||||||
|
- Next admin proxy: `GET /api/admin/synthetic/status`, `GET/PUT /api/admin/synthetic/control` (`apps/web/app/api/admin/synthetic/status/route.ts:5-7`, `apps/web/app/api/admin/synthetic/control/route.ts:5-17`).
|
||||||
|
- API admin backend: `GET /admin/synthetic/status`, `GET/PUT /admin/synthetic/control` (`services/api/src/index.ts:1364-1388`).
|
||||||
|
- API history/news and related reads: `/history/news` (`services/api/src/index.ts:1656-1660`) plus other unauthenticated history/replay/read endpoints documented in P5 matrix.
|
||||||
|
- WebSockets: `/ws/options`, `/ws/options-nbbo`, `/ws/equities`, `/ws/equity-candles`, `/ws/equity-quotes`, `/ws/equity-joins`, `/ws/inferred-dark`, `/ws/flow`, `/ws/classifier-hits`, `/ws/smart-money`, `/ws/alerts`, `/ws/live` (`services/api/src/index.ts:1846-1936`).
|
||||||
|
- Host infra ports from root compose: ClickHouse HTTP/native `8123/9000`, Redis `6379`, NATS client/monitor `4222/8222` (`docker-compose.yml:4-24`).
|
||||||
|
|
||||||
|
## Attacker-controlled sources
|
||||||
|
- Anonymous browser requests to Next route handlers when `NEXT_PUBLIC_SYNTHETIC_ADMIN=1`.
|
||||||
|
- HTTP query/path parameters and WebSocket connection/message bytes to the API.
|
||||||
|
- Alpaca/provider news `item.content`, `item.summary`, `item.url`, and symbols before persistence/display.
|
||||||
|
- Network clients reaching published compose ports on the host.
|
||||||
|
- Environment hidden controls: `NEXT_PUBLIC_API_URL`, `SYNTHETIC_ADMIN_TOKEN`, `API_HOST`, compose deployment choice.
|
||||||
|
|
||||||
|
## Sinks
|
||||||
|
- NATS KV write of synthetic control state through API admin PUT (`services/api/src/index.ts:1386-1388`).
|
||||||
|
- Browser DOM HTML sink: `dangerouslySetInnerHTML` for news story body (`apps/web/app/terminal.tsx:5009`).
|
||||||
|
- WebSocket `serverRef.upgrade` and live snapshots (`services/api/src/index.ts:1847-1935`, `1982-2008`).
|
||||||
|
- ClickHouse query reads for history/replay (`services/api/src/index.ts:1556-1660`, storage package).
|
||||||
|
- Direct ClickHouse/Redis/NATS network services from root compose (`docker-compose.yml:4-24`).
|
||||||
|
|
||||||
|
## Hidden control channels
|
||||||
|
- `NEXT_PUBLIC_SYNTHETIC_ADMIN` enables/disables admin proxy; `NEXT_PUBLIC_API_URL` chooses the privileged proxy target; `SYNTHETIC_ADMIN_TOKEN` is injected server-side (`apps/web/app/api/admin/synthetic/shared.ts:10-22`, `44-55`).
|
||||||
|
- API admin accepts either bearer token or `x-synthetic-admin-token` fallback (`services/api/src/index.ts:320-333`).
|
||||||
|
- API exposure depends on `API_HOST`/reverse proxy rather than handler auth; WS routes do not inspect `Origin`.
|
||||||
|
- Root compose vs production compose changes infra from internal-only to host-published.
|
||||||
|
|
||||||
|
## Exploit-relevant paths
|
||||||
|
- Browser -> Next `/api/admin/synthetic/control` -> server injects bearer -> API admin -> NATS KV synthetic control mutation.
|
||||||
|
- Provider news HTML -> `content_html` -> ClickHouse/API `/history/news` -> React drawer -> regex sanitizer -> `dangerouslySetInnerHTML`.
|
||||||
|
- Remote WS client -> `/ws/live` upgrade -> subscribe message -> `liveState.getSnapshot` -> live/research data stream.
|
||||||
|
- Network client -> host port `4222` NATS -> publish forged subjects / KV updates; or `8123/9000` ClickHouse -> query/alter data; or `6379` Redis -> read/write cache.
|
||||||
18
piolium/attack-surface/npm-dep-names.txt
Normal file
18
piolium/attack-surface/npm-dep-names.txt
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
@clickhouse/client
|
||||||
|
@electron-forge/cli
|
||||||
|
@electron-forge/core
|
||||||
|
@electron-forge/maker-zip
|
||||||
|
@msgpack/msgpack
|
||||||
|
@pierre/diffs
|
||||||
|
@tanstack/react-virtual
|
||||||
|
@types/node
|
||||||
|
electron
|
||||||
|
lightweight-charts
|
||||||
|
nats
|
||||||
|
next
|
||||||
|
react
|
||||||
|
react-dom
|
||||||
|
redis
|
||||||
|
typescript
|
||||||
|
ws
|
||||||
|
zod
|
||||||
1
piolium/attack-surface/nvd-islandflow.json
Normal file
1
piolium/attack-surface/nvd-islandflow.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"resultsPerPage":0,"startIndex":0,"totalResults":0,"format":"NVD_CVE","version":"2.0","timestamp":"2026-05-27T05:19:20.553","vulnerabilities":[]}
|
||||||
116
piolium/attack-surface/osv-findings.tsv
Normal file
116
piolium/attack-surface/osv-findings.tsv
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
electron GHSA-2q4g-w47c-4674
|
||||||
|
electron GHSA-3c8v-cfp5-9885
|
||||||
|
electron GHSA-3p22-ghq8-v749
|
||||||
|
electron GHSA-4p4r-m79c-wq3v
|
||||||
|
electron GHSA-4w88-rjj3-x7wp
|
||||||
|
electron GHSA-532v-xpq5-8h95
|
||||||
|
electron GHSA-56pc-6jqp-xqj8
|
||||||
|
electron GHSA-5rqw-r77c-jp79
|
||||||
|
electron GHSA-6h98-cf9g-vmg2
|
||||||
|
electron GHSA-6r2x-8pq8-9489
|
||||||
|
electron GHSA-6vrv-94jv-crrg
|
||||||
|
electron GHSA-77xc-hjv8-ww97
|
||||||
|
electron GHSA-7fv9-m79r-j9x8
|
||||||
|
electron GHSA-7m48-wc93-9g85
|
||||||
|
electron GHSA-7x97-j373-85x5
|
||||||
|
electron GHSA-8337-3p73-46f4
|
||||||
|
electron GHSA-8x5q-pvf5-64mp
|
||||||
|
electron GHSA-8xwg-wv7v-4vqp
|
||||||
|
electron GHSA-9899-m83m-qhpj
|
||||||
|
electron GHSA-995f-9x5r-2rcj
|
||||||
|
electron GHSA-9w97-2464-8783
|
||||||
|
electron GHSA-9wfr-w7mm-pc7f
|
||||||
|
electron GHSA-f37v-82c4-4x64
|
||||||
|
electron GHSA-f3pv-wv63-48x8
|
||||||
|
electron GHSA-f9mq-jph6-9mhm
|
||||||
|
electron GHSA-fjqr-fx3f-g4rv
|
||||||
|
electron GHSA-gvcj-pfq2-wxj7
|
||||||
|
electron GHSA-gxh7-wv9q-fwfr
|
||||||
|
electron GHSA-h9jc-284h-533g
|
||||||
|
electron GHSA-hv9c-qwqg-qj3v
|
||||||
|
electron GHSA-hvf8-h2qh-37m9
|
||||||
|
electron GHSA-j7hp-h8jx-5ppr
|
||||||
|
electron GHSA-jfqg-hf23-qpw2
|
||||||
|
electron GHSA-jfqx-fxh3-c62j
|
||||||
|
electron GHSA-jjp3-mq3x-295m
|
||||||
|
electron GHSA-m93v-9qjc-3g79
|
||||||
|
electron GHSA-mpjm-v997-c4h4
|
||||||
|
electron GHSA-mq8j-3h7h-p8g7
|
||||||
|
electron GHSA-mwmh-mq4g-g6gr
|
||||||
|
electron GHSA-p2jh-44qj-pf2v
|
||||||
|
electron GHSA-p7v2-p9m8-qqg7
|
||||||
|
electron GHSA-qqvq-6xgj-jw8g
|
||||||
|
electron GHSA-r5p7-gp4j-qhrx
|
||||||
|
electron GHSA-vmqv-hx8q-j7mg
|
||||||
|
electron GHSA-w222-53c6-c86p
|
||||||
|
electron GHSA-xj5x-m3f3-5x3h
|
||||||
|
electron GHSA-xw5q-g62x-2qjc
|
||||||
|
electron GHSA-xwr5-m59h-vwqr
|
||||||
|
nats GHSA-82rf-q3pr-4f6p
|
||||||
|
nats GHSA-prmc-5v5w-c465
|
||||||
|
next GHSA-223j-4rm8-mrmf
|
||||||
|
next GHSA-25mp-g6fv-mqxx
|
||||||
|
next GHSA-267c-6grr-h53f
|
||||||
|
next GHSA-26hh-7cqf-hhc6
|
||||||
|
next GHSA-36qx-fr4f-26g5
|
||||||
|
next GHSA-3f5c-4qxj-vmpf
|
||||||
|
next GHSA-3g8h-86w9-wvmq
|
||||||
|
next GHSA-3h52-269p-cp9r
|
||||||
|
next GHSA-3x4c-7xq6-9pq8
|
||||||
|
next GHSA-4342-x723-ch2f
|
||||||
|
next GHSA-492v-c6pp-mqqv
|
||||||
|
next GHSA-5f7q-jpqc-wp7h
|
||||||
|
next GHSA-5j59-xgg2-r9c4
|
||||||
|
next GHSA-5vj8-3v2h-h38v
|
||||||
|
next GHSA-67rr-84xm-4c7r
|
||||||
|
next GHSA-77r5-gw3j-2mpf
|
||||||
|
next GHSA-7gfc-8cq8-jh5f
|
||||||
|
next GHSA-7m27-7ghc-44w9
|
||||||
|
next GHSA-8h8q-6873-q5fj
|
||||||
|
next GHSA-9g9p-9gw9-jx7f
|
||||||
|
next GHSA-9gr3-7897-pp7m
|
||||||
|
next GHSA-9qr9-h5gf-34mp
|
||||||
|
next GHSA-c4j6-fc7j-m34r
|
||||||
|
next GHSA-c59h-r6p8-q9wc
|
||||||
|
next GHSA-f82v-jwr5-mffw
|
||||||
|
next GHSA-ffhc-5mcf-pf4q
|
||||||
|
next GHSA-fmvm-x8mv-47mj
|
||||||
|
next GHSA-fq54-2j52-jc42
|
||||||
|
next GHSA-fq77-7p7r-83rj
|
||||||
|
next GHSA-fr5h-rqp8-mj6g
|
||||||
|
next GHSA-g5qg-72qw-gw5v
|
||||||
|
next GHSA-g77x-44xx-532m
|
||||||
|
next GHSA-ggv3-7p47-pfv8
|
||||||
|
next GHSA-gp8f-8m3g-qvj9
|
||||||
|
next GHSA-gx5p-jg67-6x7h
|
||||||
|
next GHSA-h25m-26qc-wcjf
|
||||||
|
next GHSA-h27x-g6w4-24gq
|
||||||
|
next GHSA-h64f-5h5j-jqjh
|
||||||
|
next GHSA-jcc7-9wpm-mj36
|
||||||
|
next GHSA-m34x-wgrh-g897
|
||||||
|
next GHSA-mg66-mrh9-m8jx
|
||||||
|
next GHSA-mq59-m269-xvcx
|
||||||
|
next GHSA-mwv6-3258-q52c
|
||||||
|
next GHSA-q4gf-8mx6-v5v3
|
||||||
|
next GHSA-qpjv-v59x-3qc4
|
||||||
|
next GHSA-qw96-mm2g-c8m7
|
||||||
|
next GHSA-r2fc-ccr8-96c4
|
||||||
|
next GHSA-vfv6-92ff-j949
|
||||||
|
next GHSA-vxf5-wxwp-m7g9
|
||||||
|
next GHSA-w37m-7fhw-fmv9
|
||||||
|
next GHSA-wfc6-r584-vfw7
|
||||||
|
next GHSA-wff4-fpwg-qqv3
|
||||||
|
next GHSA-wr66-vrwm-5g5x
|
||||||
|
next GHSA-x56p-c8cg-q435
|
||||||
|
next GHSA-xv57-4mr9-wg8v
|
||||||
|
react GHSA-g53w-52xc-2j85
|
||||||
|
react GHSA-hg79-j56m-fxgv
|
||||||
|
react-dom GHSA-mvjj-gqq2-p4hw
|
||||||
|
redis GHSA-35q2-47q7-3pc3
|
||||||
|
ws GHSA-2mhh-w6q8-5hxw
|
||||||
|
ws GHSA-3h5v-q93c-6h6q
|
||||||
|
ws GHSA-58qx-3vcg-4xpx
|
||||||
|
ws GHSA-5v72-xg48-5rpm
|
||||||
|
ws GHSA-6663-c963-2gqg
|
||||||
|
ws GHSA-6fc8-4gx4-v693
|
||||||
|
zod GHSA-m95q-7qp3-xv42
|
||||||
|
1
piolium/attack-surface/osv-query.json
Normal file
1
piolium/attack-surface/osv-query.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"queries": [{"package": {"name": "@clickhouse/client", "ecosystem": "npm"}}, {"package": {"name": "@electron-forge/cli", "ecosystem": "npm"}}, {"package": {"name": "@electron-forge/core", "ecosystem": "npm"}}, {"package": {"name": "@electron-forge/maker-zip", "ecosystem": "npm"}}, {"package": {"name": "@msgpack/msgpack", "ecosystem": "npm"}}, {"package": {"name": "@pierre/diffs", "ecosystem": "npm"}}, {"package": {"name": "@tanstack/react-virtual", "ecosystem": "npm"}}, {"package": {"name": "@types/node", "ecosystem": "npm"}}, {"package": {"name": "electron", "ecosystem": "npm"}}, {"package": {"name": "lightweight-charts", "ecosystem": "npm"}}, {"package": {"name": "nats", "ecosystem": "npm"}}, {"package": {"name": "next", "ecosystem": "npm"}}, {"package": {"name": "react", "ecosystem": "npm"}}, {"package": {"name": "react-dom", "ecosystem": "npm"}}, {"package": {"name": "redis", "ecosystem": "npm"}}, {"package": {"name": "typescript", "ecosystem": "npm"}}, {"package": {"name": "ws", "ecosystem": "npm"}}, {"package": {"name": "zod", "ecosystem": "npm"}}]}
|
||||||
1
piolium/attack-surface/osv-querybatch.json
Normal file
1
piolium/attack-surface/osv-querybatch.json
Normal file
File diff suppressed because one or more lines are too long
1024
piolium/attack-surface/osv-selected-details.json
Normal file
1024
piolium/attack-surface/osv-selected-details.json
Normal file
File diff suppressed because it is too large
Load diff
23
piolium/attack-surface/patch-bypass-summary.md
Normal file
23
piolium/attack-surface/patch-bypass-summary.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Stage 02 Patch History & Bypass Review
|
||||||
|
|
||||||
|
Scan window: `git log -n "${PIOLIUM_COMMIT_SCAN_LIMIT:-500}" --since="${PIOLIUM_COMMIT_SCAN_SINCE:-60 days ago}" --all` (evaluated with defaults: 500 commits, since 60 days ago). Keyword sweep focused on CVE/security/auth/token/allowlist/deploy/ssh/harden-related commits.
|
||||||
|
|
||||||
|
## Relevant historical fixes reviewed
|
||||||
|
|
||||||
|
| Commit | Area | Patch summary | Bypass attempts today | Conclusion |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `8464287` / stash index `bff5334` | Dependency CVEs | Added root `overrides` for `postcss`, `tar`, `tmp`; upgraded `ws` in ingest services from `^8.18.3` to `^8.21.0`. | Checked current root and Docker workspace package manifests: overrides are present in both. Searched all package manifests for direct vulnerable `ws` pins: only ingest services use `^8.21.0`. No sibling service currently pins `ws`, `tar`, `tmp`, or `postcss` directly outside the override coverage. | **Sound** for manifest coverage. Residual risk is lockfile/install-policy dependent; no patch bypass found in source manifests. |
|
||||||
|
| `5ddfbfa` | Deploy allowlist | Removed broad `deployment/npm/` from `ALLOWED_REMOTE_UNTRACKED`, leaving only the exact signal-cli tarball. | Reviewed current `remoteGitPrecheck()`: it extracts the full untracked path and uses a shell `case` against a generated pattern containing only `deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz`. Because the allowed pattern has no wildcard, paths such as `deployment/docker/signal-cli-0.14.3-Linux-native.tar.gz/evil`, `deployment/npm/x`, or other untracked deployment payloads do not match. Tracked modifications still fail closed. | **Sound**. No alternate deploy precheck path found in current `scripts/deploy.ts`. |
|
||||||
|
| `2865d56` | Deploy precheck pattern handling | Converted multiple allowed untracked paths into one case alternative pattern instead of emitting malformed case arms. | Current implementation first strips `?? ` into `path` and nests a second `case`, avoiding the earlier malformed pattern/line parsing issue. With a single exact allowlisted file, pattern differential bypass is not apparent. | **Sound**. |
|
||||||
|
| `39bac1e` plus later deploy hardening | VPS deployment safety | Introduced `scripts/deploy.ts` with local/remote cleanliness checks and non-interactive SSH. Later commits added remote resolution, local-server execution, runtime scopes, and tighter checks. | Checked for command injection through branch/remote names: branch and remote used in remote shell scripts are passed through `shellEscape()`. Checked untrusted config branches: `DEPLOY_NATIVE_SYSTEMCTL_PREFIX` is interpolated into shell scripts unescaped, but this is a local deploy-operator environment override; an attacker who controls it already controls the deployment process. Current-branch deploy requires clean local status and pushes the selected remote before remote switch/pull. | **Sound under intended trust model** (deploy operator controls environment). No remote attacker bypass identified. |
|
||||||
|
| `e70835e` | Native deploy SSH assumptions | Added `$HOME/.bun/bin` to PATH for native remote precheck/rollout/verification and ensured verification `cd`s into repo. | Not a security fix; reviewed for relocated command execution. It only adds a fixed PATH prefix and does not incorporate attacker-controlled input besides the deploy operator's environment. | **Not security-relevant / no bypass**. |
|
||||||
|
| `07a9b91` then `7d25608` | Alpaca auth handling | Initially removed deprecated key-pair auth in favor of single bearer token; later restored/normalized current Alpaca key-id + secret handling, including news worker wiring. | Current code centralizes auth in `packages/config/src/alpaca.ts`. Searched for old direct header construction and env names: ingest services call the shared resolver; docs still note legacy bearer fallback. The fallback is intentional compatibility, not an auth bypass, because it is only used when no explicit key-id/secret pair is configured. | **Relocated but currently centralized/sound**. Historical “fix” was corrected by later compatibility patch; no duplicate stale adapter path found. |
|
||||||
|
|
||||||
|
## Additional notes
|
||||||
|
|
||||||
|
- Several deploy/network commits (`21ec3eb`, `9901b13`/`1c0e2e5`, `cf7ddf3`, `d7e984c`, etc.) are operational hardening/removal of obsolete wrappers. Current repo has a single top-level `deploy` entrypoint invoking `scripts/deploy.ts`; no deprecated `deployment/npm` rollout path remains as an executable bypass surface.
|
||||||
|
- The `.env.example` and docs still list legacy Alpaca variables, but runtime behavior requires either a complete key-id/secret pair or the explicitly supported legacy bearer token. Missing partial credentials fail closed via `hasAlpacaCredentials()` callers.
|
||||||
|
|
||||||
|
## Overall conclusion
|
||||||
|
|
||||||
|
No currently exploitable patch bypass was identified in the reviewed security-relevant history. The highest-value checks were the deploy untracked-file allowlist and dependency-CVE manifest coverage; both are presently covered. Recommended follow-up: run dependency audit against the concrete `bun.lock`/container build outputs to confirm the manifest overrides are materialized in deployed artifacts.
|
||||||
40
piolium/attack-surface/public-routes-authz-matrix.md
Normal file
40
piolium/attack-surface/public-routes-authz-matrix.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Public Routes Authorization Matrix
|
||||||
|
|
||||||
|
Scope: Stage 05 public-route authorization/access-control review. Sources: `piolium/attack-surface/knowledge-base-report.md`, `piolium/attack-surface/architecture-entrypoints.md`, `services/api/src/index.ts`, and Next admin proxy route handlers.
|
||||||
|
|
||||||
|
**Roles modeled**: anonymous internet client, authenticated app user (no app auth found), synthetic admin token holder, internal/reverse-proxy peer.
|
||||||
|
|
||||||
|
**Hidden control channels**
|
||||||
|
- API bind/proxy exposure: `API_HOST` defaults to `127.0.0.1`, but any reverse-proxy route or `API_HOST=0.0.0.0` exposes all public API/WS routes without handler-level re-check.
|
||||||
|
- Synthetic admin API accepts `Authorization: Bearer` and fallback `x-synthetic-admin-token` (`services/api/src/index.ts:320-327`); API admin routes are otherwise guarded by `authenticateSyntheticAdminRequest` (`services/api/src/index.ts:1326-1351`).
|
||||||
|
- Next admin proxy target and availability are env controlled: `NEXT_PUBLIC_SYNTHETIC_ADMIN`, `NEXT_PUBLIC_API_URL`, and server-side `SYNTHETIC_ADMIN_TOKEN` (`apps/web/app/api/admin/synthetic/shared.ts:10-22`).
|
||||||
|
- Next admin proxy unconditionally injects the bearer token on behalf of the requester (`apps/web/app/api/admin/synthetic/shared.ts:44-55`), so browser caller identity is not re-checked.
|
||||||
|
- WebSocket upgrade routes check only method/path before `serverRef.upgrade` (`services/api/src/index.ts:1846-1939`); no Origin/auth/rate guard observed.
|
||||||
|
|
||||||
|
| # | Public route / operation | Handler | Expected checks | Actual checks by role | Middleware / proxy-derived identity | Hidden controls | Anomaly / draft |
|
||||||
|
|---:|---|---|---|---|---|---|---|
|
||||||
|
| 1 | `GET /health` | `services/api/src/index.ts:1360` | Public health | anon: allowed; auth/admin/internal: allowed | none | bind/proxy only | none |
|
||||||
|
| 2 | API `GET /admin/synthetic/status` | `services/api/src/index.ts:1364` | Synthetic admin only | anon/auth: 401; token-holder: allowed; internal: allowed only with token | `Authorization` or `x-synthetic-admin-token` | `SYNTHETIC_CONTROL_ENABLED`, backend mode | none |
|
||||||
|
| 3 | API `GET /admin/synthetic/control` | `services/api/src/index.ts:1372` | Synthetic admin only | anon/auth: 401; token-holder: allowed | same as above | same as above | none |
|
||||||
|
| 4 | API `PUT /admin/synthetic/control` | `services/api/src/index.ts:1380` | Synthetic admin only | anon/auth: 401; token-holder: can mutate control state | same as above | same as above | none at API layer |
|
||||||
|
| 5 | Next `GET /api/admin/synthetic/status` | `apps/web/app/api/admin/synthetic/status/route.ts:5` | Admin/browser session or equivalent server-side auth before proxying | anon/auth: allowed when feature/env configured; backend receives server bearer token; synthetic admin role effectively conferred | server route injects `Authorization: Bearer ${SYNTHETIC_ADMIN_TOKEN}` | `NEXT_PUBLIC_SYNTHETIC_ADMIN=1`, `NEXT_PUBLIC_API_URL` | **p5-001** |
|
||||||
|
| 6 | Next `GET /api/admin/synthetic/control` | `apps/web/app/api/admin/synthetic/control/route.ts:5` | Admin/browser session | anon/auth: allowed when feature/env configured; reads admin control | server token injection | same | **p5-001** |
|
||||||
|
| 7 | Next `PUT /api/admin/synthetic/control` | `apps/web/app/api/admin/synthetic/control/route.ts:11` | Admin/browser session + CSRF/origin intent | anon/auth: allowed when feature/env configured; body forwarded with server token | server token injection | same | **p5-001** |
|
||||||
|
| 8 | Recent REST reads: `GET /prints/options`, `/nbbo/options`, `/prints/equities`, `/quotes/equities`, `/joins/equities`, `/dark/inferred`, `/flow/packets`, `/flow/smart-money`, `/flow/classifier-hits`, `/flow/alerts`, `/news` | `services/api/src/index.ts:1407-1533` | Public per current architecture, or proxy/firewall if proprietary data | anon/auth/admin/internal: allowed; zod/limit parsing only | none | `API_HOST`/reverse proxy | review target: proprietary data scraping if exposed |
|
||||||
|
| 9 | Filtered/range REST reads: `GET /prints/equities/range`, `/candles/equities` | `services/api/src/index.ts:1438,1460` | Public per current architecture, bounded query params | anon/auth/admin/internal: allowed; parameter validation/limit only | optional Redis cache selected by request `cache` | bind/proxy, cache flag | none filed |
|
||||||
|
| 10 | Alert context helper route(s) | `services/api/src/index.ts:1539`, `:1670` | Public/read-only, bounded trace id | anon/auth/admin/internal: allowed; trace id parse/length check on regex path | none | bind/proxy | none filed |
|
||||||
|
| 11 | History REST reads: `/history/options`, `/history/nbbo`, `/history/equities`, `/history/equity-quotes`, `/history/equity-joins`, `/history/flow`, `/history/smart-money`, `/history/classifier-hits`, `/history/alerts`, `/history/inferred-dark`, `/history/news` | `services/api/src/index.ts:1558-1656` | Public per current architecture, bounded cursors/limits | anon/auth/admin/internal: allowed; cursor/limit validation only | none | bind/proxy | review target: bulk history extraction if not intended public |
|
||||||
|
| 12 | Object lookup reads: `GET /flow/packets/:id`, `/option-prints/by-trace`, `/equity-joins/by-id` | `services/api/src/index.ts:1664,1681,1714` | Public/read-only if market data IDs are non-sensitive | anon/auth/admin/internal: allowed; no actor ownership model present | none | bind/proxy | none filed; no user/tenant objects identified |
|
||||||
|
| 13 | Support lookup: `POST /lookup/options-support` | `services/api/src/index.ts:1687` | Public/read-only aggregation with body validation | anon/auth/admin/internal: allowed; zod body schema; no auth | none | bind/proxy | none filed |
|
||||||
|
| 14 | Replay reads: `/replay/options`, `/replay/nbbo`, `/replay/equities`, `/replay/equity-quotes`, `/replay/equity-candles`, `/replay/equity-joins`, `/replay/inferred-dark`, `/replay/flow`, `/replay/smart-money`, `/replay/classifier-hits`, `/replay/alerts` | `services/api/src/index.ts:1720-1838` | Public per current architecture, bounded cursors/limits | anon/auth/admin/internal: allowed; zod parsing/limits only | none | bind/proxy | review target: bulk replay extraction if proprietary |
|
||||||
|
| 15 | Legacy WebSockets: `/ws/options`, `/ws/options-nbbo`, `/ws/equities`, `/ws/equity-candles`, `/ws/equity-quotes`, `/ws/equity-joins`, `/ws/inferred-dark`, `/ws/flow`, `/ws/classifier-hits`, `/ws/smart-money`, `/ws/alerts` | `services/api/src/index.ts:1846-1926`, `:1958-1978` | Public live market streams or edge auth/rate/origin guard if proprietary | anon/auth/admin/internal: upgrade allowed by path; no Origin/auth check | none | bind/proxy, WebSocket origin not checked | review target: unauth streaming/resource exposure |
|
||||||
|
| 16 | Live WebSocket subscription API: `GET /ws/live` + subscribe/unsubscribe/ping messages | `services/api/src/index.ts:1934`, `:1982-2008` | Public live API with schema limits; auth/rate/origin if proprietary | anon/auth/admin/internal: upgrade allowed; messages schema-validated but no auth | subscription data from client message | bind/proxy, WebSocket origin not checked | review target: unauth streaming/resource exposure |
|
||||||
|
| 17 | Next public pages `/`, `/tape`, `/signals`, `/charts`, `/news`, `/options`, `/replay`, `/frontend-cooker` | `apps/web/app/**` | Public UI | anon/auth/admin/internal: allowed by file routing | browser calls API configured by env | `NEXT_PUBLIC_API_URL` exposed to client | none filed |
|
||||||
|
|
||||||
|
## Anomalies promoted to drafts
|
||||||
|
|
||||||
|
- `piolium/findings-draft/p5-001-public-next-admin-proxy-confers-synthetic-admin.md` — public Next.js synthetic admin proxy routes inject the server admin token without authenticating the browser caller.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
No user/account/tenant ownership model was found in the enumerated market-data API, so public data endpoints were not filed as missing-guard findings solely because they lack auth. They remain deployment-policy review targets because the KB notes proprietary research value and exposure depends on reverse proxy/bind settings.
|
||||||
31
piolium/attack-surface/source-sink-flows-all-severities.md
Normal file
31
piolium/attack-surface/source-sink-flows-all-severities.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Stage 04 Source-to-Sink Flows (All Severities)
|
||||||
|
|
||||||
|
Tooling note: `codeql` and `semgrep` were not present on PATH. Per instruction, Stage 04 fell back to grep/read plus Phase 3 candidate prioritization. Custom placeholder CodeQL queries and Semgrep rules are stored under `piolium/codeql-queries/` and `piolium/semgrep-rules/`.
|
||||||
|
|
||||||
|
## High-priority flows
|
||||||
|
|
||||||
|
| ID | Source | Path | Sink | Security relevance | Draft |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| F-001 | Alpaca/provider news `item.content` (`services/ingest-news/src/index.ts:78`) | `content_html` -> NATS/ClickHouse -> `sanitizeNewsHtml` regex (`apps/web/app/terminal.tsx:1272`) | `dangerouslySetInnerHTML` (`apps/web/app/terminal.tsx:5009`) | Stored XSS via provider-controlled HTML | `p4-001` |
|
||||||
|
| F-002 | Remote WebSocket upgrade and messages (`services/api/src/index.ts:1844`, `1959`) | unauthenticated `serverRef.upgrade` -> socket set/subscription -> `liveState.getSnapshot` | `socket.send` fanout/snapshot (`services/api/src/index.ts:1982`) | Unauthenticated data streaming/resource abuse | `p4-002` |
|
||||||
|
| F-003 | Remote HTTP query/path params (`services/api/src/index.ts:1357`) | manual routes parse params -> storage fetchers | ClickHouse `client.query` in `packages/storage/src/clickhouse.ts` | Public data exfil if API exposed | `p4-003` |
|
||||||
|
| F-004 | Next admin proxy route body/path + env base (`apps/web/app/api/admin/synthetic/*.ts`) | fixed route paths -> `new URL(path, NEXT_PUBLIC_API_URL)` -> bearer header from `SYNTHETIC_ADMIN_TOKEN` | `fetch(url.toString())` (`shared.ts:51`) | Environment-controlled SSRF/control channel; path fixed, so downgraded | none |
|
||||||
|
| F-005 | HTTP admin control body + auth header (`services/api/src/index.ts:1339`, `1386`) | bearer token compare -> `SyntheticControlStateSchema.parse` | `writeSyntheticControlState` (`services/api/src/index.ts:1387`) | Hidden control channel; gated by token/feature flag | none |
|
||||||
|
| F-006 | WebSocket live message bytes (`services/api/src/index.ts:1959`) | `TextDecoder` -> `JSON.parse` -> Zod schemas | subscription maps/live snapshots | DoS potential; needs message-size/connection quotas | covered by `p4-002` |
|
||||||
|
| F-007 | Env/config Python binary and adapter settings | `buildArgs(trimmed)` / `args` arrays | `Bun.spawn` (`databento.ts:305`, `ibkr.ts:92`) | Local/env-controlled subprocess path; no shell, downgraded to env/admin-only | none |
|
||||||
|
| F-008 | User query arrays (`trace_id`, `id`, filters) | `url.searchParams.getAll` -> query-builder helpers (`quoteString`, `buildStringList`, `clamp*`) | ClickHouse template queries | SQLi mostly mitigated by escaping/clamps; query DoS still worth limits | none |
|
||||||
|
|
||||||
|
## Hidden-control-channel review
|
||||||
|
|
||||||
|
- `authorization` / `x-synthetic-admin-token` in `services/api/src/index.ts:327-333`: affects admin control authorization; correctly checked for `/admin/synthetic/*`, absent from data routes.
|
||||||
|
- `NEXT_PUBLIC_SYNTHETIC_ADMIN`, `NEXT_PUBLIC_API_URL`, `SYNTHETIC_ADMIN_TOKEN` in `apps/web/app/api/admin/synthetic/shared.ts`: controls whether the admin proxy exists and where it sends privileged bearer requests.
|
||||||
|
- `window.location.host` in `apps/web/app/terminal.tsx:1024/1045`: client-side API/WS endpoint selection follows current origin; relevant to reverse-proxy host trust but not a server-side SSRF.
|
||||||
|
- Response `content-type` checks in `terminal.tsx` and scripts: robustness checks, not auth/routing controls.
|
||||||
|
|
||||||
|
## Dropped/low candidates
|
||||||
|
|
||||||
|
- Test secrets in `*.test.ts`: source-controlled test literals only.
|
||||||
|
- `exec` matches in ClickHouse client: SQL execution/query API, not OS command execution.
|
||||||
|
- Static `redirect("/")`/`redirect("/options")`: no user-controlled URL.
|
||||||
|
- `Array.join` path-traversal matches: mostly string formatting/query construction false positives.
|
||||||
|
- Dev/deploy `Bun.spawn`/`spawnSync` in scripts: local tooling/admin context unless used by untrusted CI input.
|
||||||
21
piolium/attack-surface/spec-gap-summary.md
Normal file
21
piolium/attack-surface/spec-gap-summary.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Stage 07 — Specification, Framework Contract & Parser Gaps
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Phase 3 identified no formal application RFC/spec commitments, so this stage focused on de facto framework/runtime contracts: Bun HTTP/WebSocket routing, Next.js route-handler proxying, Docker/proxy deployment assumptions, and internal infrastructure trust channels.
|
||||||
|
|
||||||
|
## High-signal gaps retained
|
||||||
|
|
||||||
|
1. **Unauthenticated infrastructure services exposed by root Compose** — `docker-compose.yml` publishes ClickHouse, Redis, and NATS directly on host ports with no credentials/TLS/ACL configuration. This violates the deployment contract implied by the production compose file, where these services are internal-only. Draft: `piolium/findings-draft/p7-001-root-compose-exposes-unauth-infra.md`.
|
||||||
|
|
||||||
|
## Reviewed but not retained as new P7 findings
|
||||||
|
|
||||||
|
- **WebSocket Origin/auth contract**: Bun upgrades `/ws/*` by path only and does not inspect `Origin` or auth. This is already covered by existing draft `p4-002-unauthenticated-websocket-market-data-streams.md`; no duplicate P7 draft was created.
|
||||||
|
- **Public unauthenticated REST market-data APIs**: already covered by `p4-003-public-api-exposes-queryable-market-history.md`.
|
||||||
|
- **Provider HTML rendering/sanitization**: already covered by `p4-001-stored-xss-news-html-regex-sanitizer.md`.
|
||||||
|
- **Next.js synthetic admin proxy target (`NEXT_PUBLIC_API_URL`)**: server-side admin proxy derives its target from a public/build-time env var. This is a hardening concern and config footgun, but I did not retain it as Medium+ without an external attacker path to set deployment env or read the server-only `SYNTHETIC_ADMIN_TOKEN`.
|
||||||
|
- **Encoded path parsing for `/flow/alerts/:trace/context` and `/flow/packets/:id`**: manual regex checks occur on `URL.pathname` before `decodeURIComponent`, allowing `%2F` inside decoded IDs. Current impact appears limited to identifier lookup, not authorization/routing bypass, so it was not retained.
|
||||||
|
|
||||||
|
## Framework-contract conclusion
|
||||||
|
|
||||||
|
The most concrete new Stage 07 gap is a deployment-mode differential: production compose relies on internal-only Docker networking for ClickHouse/Redis/NATS, while the root compose publishes those same unauthenticated services on all interfaces by default. If the root compose is used on a workstation/VPS with reachable host ports, a network attacker can publish forged NATS events, read/write Redis state, or query/alter ClickHouse data outside any API-layer checks.
|
||||||
36
piolium/attack-surface/state-concurrency-summary.md
Normal file
36
piolium/attack-surface/state-concurrency-summary.md
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# State Machine & Concurrency Summary
|
||||||
|
|
||||||
|
Stage 06 reviewed the Phase 3 KB, CodeQL structural artifacts, ClickHouse DDL/model files, NATS/JetStream consumers, Redis/cache usage, and admin state paths.
|
||||||
|
|
||||||
|
## State-holding entities catalogued
|
||||||
|
|
||||||
|
1. `synthetic_control.global` (NATS KV) — `SyntheticControlState` fields: `preset_id`, `coverage_assist`, `coverage_window_minutes`, `shared_seed`, `profile_weights`, `updated_at`, `updated_by`.
|
||||||
|
2. `flow_packets` — append-only derived event state; deterministic `id`/`trace_id`; `MergeTree ORDER BY (source_ts, seq)`.
|
||||||
|
3. `smart_money_events` — append-only derived event state; `event_id`; `MergeTree ORDER BY (source_ts, seq)`.
|
||||||
|
4. `classifier_hits` — append-only derived classifier state; `trace_id`; `MergeTree ORDER BY (source_ts, seq)`.
|
||||||
|
5. `alerts` — append-only alert state; `trace_id`, `severity`; `MergeTree ORDER BY (source_ts, seq)`.
|
||||||
|
6. `equity_candles` — aggregate/counter-like fields: `volume`, `notional`, `trade_count`; `MergeTree ORDER BY (underlying_id, interval_ms, ts)`.
|
||||||
|
7. `news` — lifecycle/revision-like fields: `published_ts`, `updated_ts`; uses `ReplacingMergeTree(updated_ts)`.
|
||||||
|
8. `option_prints`, `option_nbbo`, `equity_prints`, `equity_quotes`, `equity_print_joins`, `inferred_dark` — append-only event stores with timestamps/sequence cursors.
|
||||||
|
|
||||||
|
No balance/credit/payment/quota inventory was found. No payments/webhooks were identified.
|
||||||
|
|
||||||
|
## Concurrency primitives observed
|
||||||
|
|
||||||
|
- Language-level locks/mutexes: none in application services.
|
||||||
|
- Database transactions / `SELECT FOR UPDATE` / advisory locks: none found.
|
||||||
|
- Distributed locks / Redis `SETNX` / Redlock: none found.
|
||||||
|
- JetStream manual acknowledgement is used (`buildDurableConsumer` sets `manualAck()` / `ackExplicit()`), making idempotent consumers important.
|
||||||
|
- NATS KV is used for synthetic control state, but updates use unconditional `kv.put` rather than a revision/CAS update.
|
||||||
|
|
||||||
|
## Idempotency infrastructure
|
||||||
|
|
||||||
|
- Present only as in-memory/UI dedupe and short-lived compute dedupe maps (`recentStructureEmits`, client-side/live dedupe). This does not survive restarts or JetStream redelivery.
|
||||||
|
- No persisted `idempotency_key`, `processed_events`, request log, replay store, Redis idempotency key, or durable event-processing ledger was found.
|
||||||
|
|
||||||
|
## Drafts filed
|
||||||
|
|
||||||
|
- `p6-001-jetstream-redelivery-duplicates-derived-events.md` — idempotency gap on JetStream redelivery and append-only ClickHouse derived tables (HIGH).
|
||||||
|
- `p6-002-synthetic-control-lost-update.md` — stale-read/lost-update in full-object synthetic control writes without revision checks (MEDIUM).
|
||||||
|
|
||||||
|
Split by class: idempotency: 1; stale-read: 1.
|
||||||
17
piolium/attack-surface/variant-summary.md
Normal file
17
piolium/attack-surface/variant-summary.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Phase 12 Variant Summary
|
||||||
|
|
||||||
|
Variant analysis reviewed surviving findings in `piolium/findings-draft/` and searched code/attack-surface artifacts for sibling sources, sinks, and flow shapes. `piolium/attack-pattern-registry.json` was not present, so no registry update could be made.
|
||||||
|
|
||||||
|
## Confirmed variants
|
||||||
|
|
||||||
|
1. `piolium/findings-draft/p12-001-unauthenticated-nats-market-event-injection.md` — expands the confirmed unauthenticated `flow.news` producer-impersonation flaw to the other trusted market/derived NATS subjects consumed by API, compute, and candles.
|
||||||
|
2. `piolium/findings-draft/p12-002-candles-jetstream-redelivery-duplicates-derived-candles.md` — same JetStream side-effects-before-ack idempotency gap as compute, present in the candles worker.
|
||||||
|
|
||||||
|
## Searches performed
|
||||||
|
|
||||||
|
- HTML injection/XSS: only `apps/web/app/terminal.tsx` uses `dangerouslySetInnerHTML` and the regex sanitizer pattern.
|
||||||
|
- Admin proxy: only `apps/web/app/api/admin/synthetic/*` injects a server bearer token into public Next route proxying.
|
||||||
|
- WebSocket auth/origin: unauthenticated upgrade pattern is centralized in `services/api/src/index.ts`; no additional WS servers found.
|
||||||
|
- NATS producer trust: API consumer binding matrix and worker subscriptions show additional subjects accepting schema-only messages from the unauthenticated broker.
|
||||||
|
- JetStream redelivery/idempotency: candles worker matches the compute side-effect-before-ack shape.
|
||||||
|
- Infrastructure exposure: root compose exposure finding remains centralized to root `docker-compose.yml`; production compose does not publish infra ports directly in the same way.
|
||||||
128
piolium/audit-state.json
Normal file
128
piolium/audit-state.json
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"audits": [
|
||||||
|
{
|
||||||
|
"audit_id": "2026-05-27T05:18:10.317Z",
|
||||||
|
"mode": "deep",
|
||||||
|
"started_at": "2026-05-27T05:18:10.317Z",
|
||||||
|
"completed_at": "2026-05-27T05:38:26.877Z",
|
||||||
|
"status": "complete",
|
||||||
|
"phases": {
|
||||||
|
"P1": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:18:10.342Z",
|
||||||
|
"completed_at": "2026-05-27T05:20:06.688Z"
|
||||||
|
},
|
||||||
|
"P2": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:20:06.689Z",
|
||||||
|
"completed_at": "2026-05-27T05:21:18.561Z"
|
||||||
|
},
|
||||||
|
"P3": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:21:18.562Z",
|
||||||
|
"completed_at": "2026-05-27T05:24:17.798Z"
|
||||||
|
},
|
||||||
|
"P4": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:24:17.800Z",
|
||||||
|
"completed_at": "2026-05-27T05:27:39.637Z"
|
||||||
|
},
|
||||||
|
"P5": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:27:39.640Z",
|
||||||
|
"completed_at": "2026-05-27T05:29:12.056Z"
|
||||||
|
},
|
||||||
|
"P6": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:27:39.991Z",
|
||||||
|
"completed_at": "2026-05-27T05:29:28.102Z"
|
||||||
|
},
|
||||||
|
"P7": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:27:40.151Z",
|
||||||
|
"completed_at": "2026-05-27T05:28:55.011Z"
|
||||||
|
},
|
||||||
|
"P8": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:29:28.109Z",
|
||||||
|
"completed_at": "2026-05-27T05:30:58.598Z"
|
||||||
|
},
|
||||||
|
"P9": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:30:58.599Z",
|
||||||
|
"completed_at": "2026-05-27T05:32:50.466Z"
|
||||||
|
},
|
||||||
|
"P10": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:32:50.468Z",
|
||||||
|
"completed_at": "2026-05-27T05:34:14.783Z"
|
||||||
|
},
|
||||||
|
"P11": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:34:14.789Z",
|
||||||
|
"completed_at": "2026-05-27T05:35:44.121Z"
|
||||||
|
},
|
||||||
|
"P12": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 1,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:35:44.122Z",
|
||||||
|
"completed_at": "2026-05-27T05:36:59.883Z"
|
||||||
|
},
|
||||||
|
"P13": {
|
||||||
|
"status": "skipped",
|
||||||
|
"started_at": "2026-05-27T05:36:59.891Z",
|
||||||
|
"completed_at": "2026-05-27T05:36:59.892Z"
|
||||||
|
},
|
||||||
|
"P14": {
|
||||||
|
"status": "skipped",
|
||||||
|
"started_at": "2026-05-27T05:36:59.892Z",
|
||||||
|
"completed_at": "2026-05-27T05:36:59.894Z"
|
||||||
|
},
|
||||||
|
"P15": {
|
||||||
|
"status": "complete",
|
||||||
|
"attempt": 2,
|
||||||
|
"max_attempts": 6,
|
||||||
|
"started_at": "2026-05-27T05:36:59.896Z",
|
||||||
|
"completed_at": "2026-05-27T05:38:26.848Z"
|
||||||
|
},
|
||||||
|
"P16": {
|
||||||
|
"status": "complete",
|
||||||
|
"started_at": "2026-05-27T05:38:26.850Z",
|
||||||
|
"completed_at": "2026-05-27T05:38:26.851Z"
|
||||||
|
},
|
||||||
|
"P17": {
|
||||||
|
"status": "complete",
|
||||||
|
"started_at": "2026-05-27T05:38:26.852Z",
|
||||||
|
"completed_at": "2026-05-27T05:38:26.876Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"agent_sdk": "pi",
|
||||||
|
"commit": "ffbdbc337638004be49775c85a2f0b10b7e55563",
|
||||||
|
"branch": "security-audit",
|
||||||
|
"history_available": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
piolium/final-audit-report.md
Normal file
47
piolium/final-audit-report.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Security Audit Report: Islandflow
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
Stage 15 final report assembly completed for the Islandflow `/piolium-deep` audit workspace. The repository presents a multi-service market-data platform with public web/API/WebSocket entrypoints, NATS/JetStream eventing, ClickHouse/Redis persistence, ingest workers, synthetic-admin controls, and an Electron shell. No promoted final finding directories were present under `piolium/findings/` during this assembly, so this report consolidates the available attack-surface and methodology artifacts rather than listing confirmed packaged findings.
|
||||||
|
|
||||||
|
## Findings by Severity
|
||||||
|
|
||||||
|
- Critical: 0
|
||||||
|
- High: 0
|
||||||
|
- Medium: 0
|
||||||
|
|
||||||
|
No promoted confirmed finding directories were present under `piolium/findings/` at assembly time. Earlier-stage candidate and chamber outputs remain available under `piolium/findings-draft/`, `piolium/chamber-workspace/`, and `piolium/adversarial-reviews/`, but no standalone `report.md` finding packages were available to link as final confirmed findings.
|
||||||
|
|
||||||
|
## Attack Surface Summary
|
||||||
|
|
||||||
|
The audit identified the primary exposed and security-relevant surfaces as: unauthenticated market-data REST and WebSocket routes in `services/api`, Next.js synthetic-admin proxy routes, external feed ingestion paths, NATS/JetStream subjects and KV state, ClickHouse query/insert sinks, Redis live/candle caches, Electron navigation/open-external boundaries, and Docker/edge deployment bindings.
|
||||||
|
|
||||||
|
Key supporting artifacts:
|
||||||
|
|
||||||
|
- [Knowledge Base / Threat Model](piolium/attack-surface/knowledge-base-report.md)
|
||||||
|
- [Architecture Entrypoints](piolium/attack-surface/architecture-entrypoints.md)
|
||||||
|
- [Manual Attack Surface Inventory](piolium/attack-surface/manual-attack-surface-inventory.md)
|
||||||
|
- [Public Routes Authorization Matrix](piolium/attack-surface/public-routes-authz-matrix.md)
|
||||||
|
- [Source/Sink Flow Review](piolium/attack-surface/source-sink-flows-all-severities.md)
|
||||||
|
- [Cross-Service Edges](piolium/attack-surface/cross-service-edges.md)
|
||||||
|
- [Candidate Scan Summary](piolium/attack-surface/candidates-summary.md)
|
||||||
|
- [Advisory Summary](piolium/attack-surface/advisory-summary.md)
|
||||||
|
- [Patch Bypass Summary](piolium/attack-surface/patch-bypass-summary.md)
|
||||||
|
- [Spec Gap Summary](piolium/attack-surface/spec-gap-summary.md)
|
||||||
|
- [State/Concurrency Summary](piolium/attack-surface/state-concurrency-summary.md)
|
||||||
|
- [Variant Summary](piolium/attack-surface/variant-summary.md)
|
||||||
|
|
||||||
|
## Coverage Gaps
|
||||||
|
|
||||||
|
- `piolium/findings/` was not present or contained no promoted finding packages at final assembly time; therefore no final per-finding reports or PoC links could be included.
|
||||||
|
- Candidate drafts and review evidence exist outside the promoted findings directory and should be reviewed before treating this as a no-findings audit result.
|
||||||
|
- Final report completeness depends on prior-stage promotion from drafts to `piolium/findings/<ID>-<slug>/report.md`; that promotion was not observable in this workspace.
|
||||||
|
|
||||||
|
## Methodology Notes
|
||||||
|
|
||||||
|
The audit followed the deep piolium workflow: advisory and architecture reconnaissance, attack-surface inventory, candidate scanning, custom SAST/source-sink review, structured review chambers, adversarial verification for higher-risk candidates, and final assembly. Chamber evidence is available at [`piolium/chamber-workspace/index.md`](piolium/chamber-workspace/index.md), with cluster debates covering news XSS, data exposure, synthetic admin proxying, concurrency, and infrastructure/bus risks. Static and structural analysis artifacts are available under `piolium/codeql-artifacts/`, `piolium/semgrep-rules/`, and `piolium/attack-surface/`.
|
||||||
|
|
||||||
|
## Assembly Checks
|
||||||
|
|
||||||
|
- Finding report size check: passed for every directory under `piolium/findings/` that existed; no promoted directories were found.
|
||||||
|
- Required final report written: `piolium/final-audit-report.md`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue