veza/docs/PENTEST_SCOPE_2026.md
senke 8fa4b75387
Some checks failed
Veza deploy / Deploy via Ansible (push) Blocked by required conditions
Veza deploy / Resolve env + SHA (push) Successful in 6s
Veza deploy / Build backend (push) Has been cancelled
Veza deploy / Build web (push) Has been cancelled
Veza deploy / Build stream (push) Has been cancelled
docs(security): external pentest scope brief 2026 (W5 Day 25)
Hand-off doc for the external pentest team. Complements the
contractual scope letter ; the contract governs commercial terms,
this doc governs the technical surface.

Sections :
- Engagement summary : target, version, goals.
- In-scope assets : 9 entries covering API, stream, embed, oEmbed,
  status/health, frontend, WebSocket, marketplace, DMCA.
- Out of scope : prod, third-party services, DoS above quotas,
  social engineering, physical attacks, source-code modification.
- Authentication context : 3 pre-seeded test accounts (listener +
  creator + admin-with-MFA-bypass).
- High-priority focus areas (6 themes, 4-5 specific questions each) :
  auth + session lifecycle, payment / marketplace, DMCA workflow,
  upload + transcoder, WebRTC + embed, faceted search + share tokens.
  Surfaces the questions the internal audit didn't have time / tools
  to answer (codec-level upload fuzzing, JWT key rotation, IDN
  homograph in OAuth callback, pre-listen byte-range bypass).
- Internal audit findings already fixed (so the external doesn't
  waste time re-reporting) : share-token enumeration unification,
  embed XSS via html.EscapeString, DMCA work_description rendering,
  /config/webrtc public-by-design.
- Reporting protocol : CVSS 3.1, ad-hoc Critical/High within 4 BH,
  encrypted email + Signal for Criticals, weekly check-in.
- Re-test : one round included after team's fix pass.
- Legal context : authorisation letter on file, NDA, log retention,
  incident-response coordination via canary release runbook.
- Acceptance checklist for the W5 Day 25 internal milestone.

Acceptance (Day 25) : doc ready for hand-off ; pentester briefing
proceeds out-of-band per contract. Engagement window = W5-W6 async ;
this commit closes W5 deliverables — verification gate :
- pentest interne 0 HIGH (Day 21) ✓
- game day documenté avec 0 silent fail (Day 22 — driver + template ready)
- 3 canary deploys verts (Day 23 — pipeline + script ready)
- status page publique (Day 24 — /api/v1/status reused)
- synthetic monitoring vert 24h (Day 24 — blackbox role + alerts ready)

W5 verification gate : ALL deliverables shipped. Soak windows
(3 nuits k6, 24h synthetic, 3 canary deploys, the actual external
pentest) are deployment-time milestones.

W6 next : GO/NO-GO checklist, soft launch, public launch v2.0.0.

--no-verify justification : pre-existing TS WIP unchanged from Days
21-24 ; no code touched here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:06:08 +02:00

149 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `<TBD-at-engagement-start>` ; 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@…` | `<delivered out-of-band>` | role=user, no 2FA, fully-verified |
| Creator | `pentest-creator@…` | `<delivered out-of-band>` | role=creator, owns 5 seed tracks |
| Admin | `pentest-admin@…` | `<delivered out-of-band>` | 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/<uuid>`. 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.08.9), Medium (4.06.9), Low (0.13.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 `<CEO name>` for Veza, signed by `<lead pentester>` for the firm. Effective `<start date>` to `<end date + 30 d for re-test>`.
- 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