diff --git a/docs/PENTEST_SCOPE_2026.md b/docs/PENTEST_SCOPE_2026.md new file mode 100644 index 000000000..74a8de9e0 --- /dev/null +++ b/docs/PENTEST_SCOPE_2026.md @@ -0,0 +1,149 @@ +# External pentest scope — v2026 (v1.0.9 pre-launch audit) + +> **Engagement period** : v1.0.9 W5-W6 (per `docs/ROADMAP_V1.0_LAUNCH.md` §Day 25). Async work, ~10 business days. +> **Authorisation** : signed scope letter + NDA on file (see "Legal context" below). +> **Re-test** : one re-test included after the team's fix pass. +> **Contact** : `security@veza.fr` ; PGP key fingerprint published at `https://veza.fr/.well-known/security.txt`. + +This brief is the technical hand-off for the external pentest team. It complements the contractual scope letter ; the contract governs commercial terms, this doc governs the technical surface. + +## Engagement summary + +**Target** : Veza, an ethical music streaming platform. Backend is Go 1.25 + Gin + GORM ; streaming is Rust + Axum ; frontend is React 18 + Vite. Infrastructure is Incus (LXD) on a single self-hosted R720 in v1.0, moving to a multi-host Hetzner topology in v1.1. + +**Version under test** : v1.0.9 (release candidate for v2.0.0 public launch). Commit SHA pinned at `` ; the staging environment freezes at this SHA for the engagement. + +**Goals** : + +1. Find what the internal pre-flight audit (`docs/SECURITY_PRELAUNCH_AUDIT.md`, W5 Day 21) missed — focus on business-logic abuse paths the automated scanners can't model. +2. Validate the v1.0.9 surface added since the last review : DMCA workflow, marketplace pre-listen, embed widget, WebRTC ICE config, faceted search. +3. Assess the multi-tenant invariants (creator vs. listener vs. admin) under malicious user input. + +## In-scope assets + +| Asset | Endpoint / surface | Notes | +| ------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------- | +| **Backend API** | `https://staging.veza.fr/api/v1/*` | All v1.0.9 endpoints + the OpenAPI spec at `/swagger` | +| **Stream server** | `https://staging.veza.fr/api/v1/tracks/*/hls/*` | HLS-only — RTMP ingest is out (v1.1) | +| **Embed widget** | `https://staging.veza.fr/embed/track/:id` | Public iframable HTML, OG tags | +| **oEmbed** | `https://staging.veza.fr/oembed` | JSON envelope | +| **Status / health** | `https://staging.veza.fr/api/v1/status`, `/health` | Public ; intentional disclosure | +| **Frontend SPA** | `https://staging.veza.fr/` | React 18 + Vite ; sourcemaps available on staging | +| **WebSocket (chat / live)** | `wss://staging.veza.fr/api/v1/ws` | Protocol described in `docs/api/websocket.md` | +| **Marketplace** | `/api/v1/marketplace/{products,orders,licenses,reviews}` | Hyperswitch sandbox, no real card processing | +| **DMCA workflow** | `POST /api/v1/dmca/notice` + admin queue | Sworn-statement validation, audit log, takedown gate | + +## Out of scope + +- **Production** (`api.veza.fr`, `app.veza.fr`). Engaging prod is not authorised — every test runs against staging. +- **Third-party services we don't operate** : Hyperswitch live mode, Bunny.net edges, Sentry, Forgejo. Their security posture is the providers' responsibility. +- **Denial-of-service testing** above the rate-limiter quotas. The platform's rate-limit middleware is in scope ; sustained flooding to deplete bandwidth is not. +- **Social engineering against Veza staff.** Phishing simulations require a separate engagement with prior written authorisation. +- **Physical / wireless** attacks against the R720 lab. +- **Source-code modification** : the engagement is grey-box (source available read-only at `https://10.0.20.105:3000/senke/veza` once the pentester's IP is allow-listed) but findings must be reproducible against staging without local patches. + +## Authentication context + +Three test accounts pre-seeded on staging : + +| Role | Email | Password | Notes | +| ------------ | --------------------------- | ----------------------- | -------------------------------------- | +| Listener | `pentest-listener@…` | `` | role=user, no 2FA, fully-verified | +| Creator | `pentest-creator@…` | `` | role=creator, owns 5 seed tracks | +| Admin | `pentest-admin@…` | `` | role=admin + MFA bypass token | + +Bearer tokens for synthetic-client style testing are derivable from `/api/v1/auth/login`. All passwords are randomised per-engagement and rotated immediately after the engagement ends. + +## High-priority focus areas + +We're particularly interested in the following surfaces (in order of impact). The internal audit cleared the trivial OWASP-Top-10 hits ; here we want creative attacks. + +### 1. Authentication + session lifecycle + +- JWT key rotation : staging uses RS256 with `JWT_PRIVATE_KEY_PATH`. Can the public key be inferred from misconfigured JWKS-style endpoints ? +- 2FA bypass : the login flow returns `requires_2fa=true` on partial-auth. Is there a state-machine flaw between partial-auth and full-auth ? +- Refresh-token replay after logout : revocation list is Redis-backed. What happens if Redis is partitioned ? +- Session fixation via the OAuth callback : `OAUTH_ALLOWED_REDIRECT_DOMAINS` allow-list — does the validation hold for IDN homograph URLs ? + +### 2. Payment / marketplace + +- Order tampering : the `POST /api/v1/marketplace/orders` body contains product IDs + quantity. Can a buyer craft an order at an arbitrary price ? (Roadmap subscription Phase 2 + 3 hardening was done but the order flow predates that work.) +- Webhook signature replay : `POST /webhooks/hyperswitch` validates a signature. Does the implementation check timestamps, or only the HMAC ? +- Refund window race : `RefundDeadline` is set to `+14d` on order completion. What happens if the buyer initiates a refund at exactly `14d - 1ms` and the validation race is exposed ? +- Pre-listen abuse : `?preview=30` is anonymous-OK when `products.preview_enabled=true`. The 30 s cap is **client-side** (HTML5 audio currentTime) ; can an attacker grab the full audio via byte-range requests despite the gate ? (Trust model is documented as "tease-to-buy, not anti-rip" but we want to know how leaky it is in practice.) + +### 3. DMCA workflow + +- Notice forgery : `POST /api/v1/dmca/notice` is public + rate-limited. Can the rate-limit be bypassed via header rotation, X-Forwarded-For spoofing, or IPv6 prefix walking ? +- Sworn statement bypass : the `sworn_statement: true` field is trusted. Can a malformed JSON body land a notice with `sworn_statement` absent (Go's zero-value) ? +- Admin takedown enumeration : `GET /api/v1/admin/dmca/notices` returns paginated pending notices. Does the offset+limit handling leak a separate-tenant's claimant data ? + +### 4. Upload + transcoder pipeline + +- Chunked upload state pollution : `POST /api/v1/tracks/upload/initiate` allocates an upload_id. Can two users with the same upload_id collide on the chunked-state Redis keys ? +- File-type confusion via `Content-Type` : the upload validator checks magic bytes. Are there codec-level flaws (e.g. malformed FLAC header that crashes the transcoder) ? +- HLS segment poisoning : the streamer caches segments by track_id. Can a crafted upload pollute another track's cache via path traversal in the segment filename ? + +### 5. WebRTC ICE config + embed + +- The `/api/v1/config/webrtc` endpoint is intentionally public per `SECURITY_PRELAUNCH_AUDIT.md`. We want a second opinion on whether the short-lived TURN credentials are short enough. +- Embed iframe XSS : `/embed/track/:id` interpolates `track.title` + `track.artist` into HTML body + OG tags via `html.EscapeString`. Try crafted Unicode + HTML-entity edge cases (e.g. surrogates, RTLO, byte-order marks). +- oEmbed URL injection : `?url=` is parsed for `/tracks/`. Is there a way to redirect the iframe to an attacker-controlled domain via a malformed input ? + +### 6. Faceted search + share tokens + +- SQL injection via the search facets : `genre`, `musical_key` are bounded by length but passed as parameterised values. Verify parameterisation holds end-to-end. +- Share-token enumeration : the W5 Day 21 audit unified error responses to a single 403. Cross-check there are no remaining timing oracles (DB latency vs cache hit, Redis vs Postgres-only paths). + +## Internal audit — already fixed (skip these) + +The W5 Day 21 audit already addressed the items below. They're listed so the external doesn't waste time re-reporting them. + +| Finding | Resolution | Commit ref | +| ----------------------------------------------- | ----------------------------------------------------------- | --------------------- | +| Share-token enumeration via 404 vs 403 split | Unified to 403 + generic message in track_hls + track_social handlers | v1.0.9 W5 Day 21 | +| XSS via track metadata in embed widget | `html.EscapeString` wraps every HTML interpolation | v1.0.9 W3 Day 15 | +| DMCA workflow XSS via `work_description` | Storage parameterised, render is React-escaped | (audit, no code change) | +| `/config/webrtc` disclosure | Accepted by design, short-lived TURN credentials | (audit, accepted) | + +## Reporting protocol + +- **Severity scale** : CVSS 3.1. Critical (9.0+), High (7.0–8.9), Medium (4.0–6.9), Low (0.1–3.9), Informational. +- **Reporting cadence** : ad-hoc for Critical/High (within 4 business hours of confirmation), batched daily for Medium and below. +- **Channel** : encrypted email to `security@veza.fr`. PGP key at `https://veza.fr/.well-known/security.txt`. For Critical findings, also use the Signal contact in the engagement letter. +- **Format** : per finding — title, severity, CVSS vector, reproduction steps (curl / browser-side script), proof of exploitation, recommended remediation, affected component(s). +- **Status calls** : weekly 30-min check-in (calendar invite from `security@veza.fr`). + +## Re-test + +The engagement includes one re-test. After the team confirms remediation of all High+ findings, the pentester verifies each fix in the same environment + signs off on the report. + +## Legal context + +- Authorisation letter on file : signed by `` for Veza, signed by `` for the firm. Effective `` to ``. +- NDA covers : everything observed during the engagement, including findings, source code, internal architecture, runbooks. +- Logs : Veza retains all server-side logs for 30 d post-engagement so the team can reconstruct any reported finding without relying on the pentester's local notes. +- Incident-response coordination : if the pentester believes they've triggered a real incident (e.g. accidentally took staging down beyond the agreed scope), they ping `security@veza.fr` immediately ; we coordinate a controlled rollback per the canary release runbook (`docs/CANARY_RELEASE.md`). + +## What we'll do with the report + +- **Critical / High** : fix before the v2.0.0 public launch. The launch GO/NO-GO checklist (W6 Day 26) blocks on these. +- **Medium** : fix in v2.0.x patch releases. +- **Low / Info** : tracked in the `docs/SECURITY_PRELAUNCH_AUDIT.md` follow-up table for the next review cycle. +- **Public credit** : the firm's name in `docs/SECURITY_ACKNOWLEDGEMENTS.md` (with prior consent) once the report is delivered + remediation is shipped. + +## Files for the pentester's first day + +- `docs/ROADMAP_V1.0_LAUNCH.md` — what shipped in v1.0.9 + the launch acceptance bar. +- `docs/SECURITY_PRELAUNCH_AUDIT.md` — internal audit findings + resolutions (skip these in the external). +- `docs/api/` — OpenAPI / Swagger generated from the live source ; `https://staging.veza.fr/swagger` mirrors it. +- `docs/CANARY_RELEASE.md` — how the team rolls fixes during the engagement (so the pentester can predict re-test windows). +- `infra/ansible/` — read-only via the Forgejo allow-list ; gives architectural context. + +## Acceptance gate (Day 25 internal milestone) + +- [ ] Pentester briefed (this doc + scope letter handed off) +- [ ] Staging access provisioned + test accounts delivered out-of-band +- [ ] Source-code repo allow-list includes pentester's static IP +- [ ] Initial check-in scheduled +- [ ] Internal audit findings (W5 Day 21) confirmed fixed in the staging build the pentester is testing