# 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