Appearance
Authority Doc / Public V1 Media Security
Media security defines how CoachMe scans, strips, stores, and serves uploaded bytes without leaking sensitive media.
This page is the proposed Public V1 authority for the security posture of the object-storage byte path: malware scanning, metadata stripping, signed-URL scoping and lifetime, derivative generation, encryption at rest, private preview, abuse and moderation hooks, and CDN posture. It sits between the REST upload contract, the media-processing providers, the tenant fence, and the privacy lifecycle, and it owns none of them.
DraftTwo bucketsFail closedSigned URLs onlyPublic V1
Decision status: Draft 2026-07-18 — pending review
Nothing on this page is approved yet. Every option below marked Proposed is a recommendation for review, not a settled decision. One decision this page depends on is genuinely open and owned elsewhere — the staging/production object-storage service and region, gated on the PDPL residency review — so this page links it and deliberately does not resolve it. Encryption mode, CDN availability, and lifecycle-rule syntax all follow that selection and cannot be locked before it.
Note
This page does not choose the storage provider, define retention windows, or set upload REST shapes. integration-register selects the object-storage service, ClamAV, Sharp, FFmpeg, and the PDF engine; privacy-and-retention owns data classes, purge, and legal hold; api-contract-standard owns the upload-intent handshake and media error codes; multi-tenancy owns the object-key prefix. This page owns only the security treatment of the bytes those contracts move.
Scope
This page owns the security controls applied to stored bytes and the URLs that reach them. Adjacent authorities own the storage selection, the retention policy, the transport contract, and the permission gates.
This Page Owns
- The scan-before-ready rule and the fail-closed posture for uploaded bytes.
- Metadata and EXIF stripping on ingest, and which metadata survives.
- Signed-URL scoping, method restriction, and time-to-live ceilings.
- Thumbnail and derivative generation placement, keying, and access.
- The encryption-at-rest requirement and the private-bucket posture.
- The private-preview rule: previews are reachable only through re-checked signed URLs.
- Abuse and moderation hook points and the quarantine state they drive.
- The Public V1 CDN posture for private media.
- Required security evidence for the media byte path.
This Page Does Not Own
- Object-storage service and region selection, owned by integration-register and configured by environment-runbook. This is the open dependency below.
- The media-processing provider decisions — ClamAV, Sharp/libvips, FFmpeg/ffprobe, and the PDF engine — owned by integration-register.
- Data classes, retention windows, purge lifecycle states, media purge receipts, and legal hold, owned by privacy-and-retention.
- The object-key prefix
workspace/<workspace_id>/…and the tenant fence, owned by multi-tenancy. - Upload-intent, completion, playback, and download REST shapes plus media error codes, owned by api-contract-standard.
- Who may read, play, download, or export a media object, owned by roles-and-permissions.
- Offline upload queueing and mobile cache purge mechanics, owned by offline-sync-protocol.
- Media telemetry names, upload metrics, and processing-age gauges, owned by observability-plan.
- Two-bucket topology, per-environment bucket config, TTL configuration values, and lifecycle rules, owned by environment-runbook.
Open Dependency — Object Storage Service And Region
The security controls on this page assume a storage service that CoachMe does not yet have.
The environment-runbook sets the two-bucket topology (one media bucket, one documents bucket) and gates service and region selection on the residency review, and the integration-register records the staging/production S3-compatible row as Conditional with Blocked evidence: no production service or region is selected, and MinIO is approved for local development only. The development readiness review lists the same call as an open decision waiting on a PDPL residency review, which is a legal determination, not a technical one.
This page does not resolve it. It states which media-security controls are portable across any S3-compatible service and which are provider-shaped and must be re-confirmed once the service is chosen.
| Control | Dependency on the storage decision |
|---|---|
| Scan-before-ready, EXIF stripping, derivative generation | Portable. These run in CoachMe-controlled workers before or independent of the storage backend and do not change with the provider. |
| Signed-URL scoping and TTL ceilings | Portable in intent. The exact signing mechanism and maximum enforceable TTL are provider-shaped and confirmed on selection. |
| Encryption at rest | Provider-shaped. Server-side encryption mode (provider-managed vs customer-managed keys) and in-region key custody depend on the selected service and the residency outcome. |
| CDN posture | Provider-shaped. Whether a CDN can front private objects with signed access in-region depends on the service and region. |
| Lifecycle and purge rule syntax | Owned by privacy-and-retention for policy and environment-runbook for configuration; the object-expiry mechanism is provider-shaped. |
Byte-Path Model
Every uploaded object passes through the same states before any client can read it. The lifecycle states are owned by privacy-and-retention; this page states the security gate at each transition.
| State | Security gate |
|---|---|
PENDING_UPLOAD | The signed upload target is scoped to one object key and one method, and the media record does not yet exist. Nothing is readable. |
| Uploaded, not ready | Bytes exist under the workspace prefix but carry no AVAILABLE record. Scan and metadata strip must complete before the object is addressable by any playback or download endpoint. |
AVAILABLE | Scan passed, metadata stripped, required derivatives exist. Playback and download endpoints may issue re-checked signed URLs. |
QUARANTINED | Scan failed, was unsupported, or a moderation hook flagged the object. No signed URL is ever issued for a quarantined object except through an audited support/admin path. |
PENDING_PURGE / PURGED / FAILED_PURGE | Purge lifecycle owned by privacy-and-retention; this page requires that a purged object issues no further signed URLs. |
Fail closed
An object becomes AVAILABLE only by passing every required control. A scan that times out, a scanner that is unavailable, an unsupported MIME type, or a failed metadata strip must leave the object non-ready or QUARANTINED. Never mark an object ready because a control could not run — the integration-register ClamAV and media-processing rows already require fail-closed behavior, and this page makes it the byte-path invariant.
Malware Scanning
Every uploaded byte stream is scanned before it can become AVAILABLE.
| Concern | Proposed rule |
|---|---|
| Engine | Self-hosted ClamAV worker, already Approved in the integration-register. Signature freshness, resource limits, and container provenance are its operational constraints. |
| Coverage | All uploaded communication files, progress media, voice notes, form attachments, and documents in both buckets. No object class is exempt. |
| Timing | Scan runs after the byte upload completes and before the AVAILABLE record is created. A client never receives a playback or download URL for an unscanned object. |
| Failure | Unsupported, unsafe, timed-out, or unavailable scans quarantine or reject the object. The scanner never blocks upload intent; it blocks readiness. |
| Evidence retained | Normalized scan result plus engine and signature version, per the integration-register ClamAV cache rule. No duplicate scan copy persists after the source object follows its lifecycle. |
| Filenames | The scanner does not require the user filename or user context; minimal job metadata stays inside the operator-controlled worker boundary. |
Metadata And EXIF Stripping
Uploaded media carries metadata that can leak location, device, and timing. It is stripped on ingest, not on serve.
| Concern | Proposed rule |
|---|---|
| Images | Sharp with libvips (Approved in the integration-register) strips EXIF, GPS, and embedded metadata during MIME-confirmed normalization. The stripped, normalized image is the canonical object; the raw upload is not retained beyond job cleanup. |
| Audio and video | FFmpeg/ffprobe strips non-essential metadata tags during validation and rendition; technical metadata needed for playback (duration, codec) is kept as record fields, not embedded provenance. |
| Documents | The selected PDF engine (still Conditional in the integration-register) removes active content and unsafe features during preview generation; document metadata handling is confirmed when the engine is selected. |
| Ordering | Stripping is a readiness gate. An object whose metadata strip failed does not become AVAILABLE. |
| Why on ingest | Stripping before the canonical object is written means no signed URL, derivative, or export can ever expose the original EXIF, and there is no serve-time toggle to get it wrong. |
Signed URLs
Object storage is private. Every read is a short-lived, permission-re-checked, single-object signed URL.
| Concern | Proposed rule |
|---|---|
| No public objects | Buckets grant no public-read access. There is no anonymous object URL for any media or document class. |
| Scope | A signed URL addresses exactly one object key under one workspace/<workspace_id>/… prefix and permits one method — GET for playback/download, PUT/POST for the specific upload target. It never grants list, delete, or prefix-wide access. |
| Upload TTL | Proposed ceiling of 15 minutes for an upload target, sized to a single upload attempt. Configured per environment by environment-runbook. |
| Playback/download TTL | Proposed ceiling of 5 minutes. Short enough that a leaked URL expires before it travels far; long enough to start a download. The exact value is per-environment config, never longer than this ceiling without review. |
| Re-check on issuance | Playback and download endpoints re-run permission, lifecycle, and quarantine checks before signing, per api-contract-standard. A relationship end, workspace suspension, deletion, redaction, or support restriction stops future issuance immediately. |
| No persistence | Signed URLs are never cached beyond expiry, never stored in a durable record, and never bundled into an export archive; exports package bytes through a fresh audited path. privacy-and-retention forbids long-lived signed-URL persistence and roles-and-permissions forbids caching them past expiration. |
Note
The integration-register records that YMove exercise-demo URLs expire after 48 hours and must not be stored. That is a provider URL rule for third-party content, not a CoachMe-issued signed URL. CoachMe-owned private media follows the shorter ceilings above; the two must not be conflated.
Thumbnails And Derivatives
Derivatives are generated server-side and inherit the security posture of the canonical object.
- Thumbnails, previews, poster frames, and policy-safe renditions are produced by the CoachMe-controlled media worker (Sharp/libvips for images, FFmpeg for audio/video, the selected engine for PDF first-page previews), never by the client and never by the storage provider.
- Derivative objects are written under the same
workspace/<workspace_id>/…prefix as their canonical object, so the tenant fence and per-workspace purge from multi-tenancy and privacy-and-retention cover them automatically. - Derivatives are generated from the already-stripped, already-scanned canonical object, so a thumbnail can never reintroduce stripped EXIF or unscanned bytes.
- Derivatives are private and served only through the same re-checked signed-URL path as their canonical object. A thumbnail is not a public preview.
- Generation is deterministic and idempotent, so a re-run produces the same derivative keys; purge removes canonical and derivative objects together.
Encryption At Rest And Private Preview
| Concern | Proposed rule |
|---|---|
| Encryption at rest | Proposed: server-side encryption enabled at the bucket for both buckets, with the key-custody mode (provider-managed vs customer-managed) confirmed against the selected service and the residency outcome. This is provider-shaped and cannot be locked before the storage decision. |
| Encryption in transit | All upload and download traffic uses TLS; the reverse proxy terminates TLS per environment-runbook. |
| Private preview | Progress photos, voice notes, form attachments, and document previews are private. A preview is reachable only through a re-checked signed URL, never through a stable or guessable path. Coach annotation of client media never replaces or exposes the original, per roles-and-permissions. |
| Bucket separation | The two-bucket split (media, documents) from environment-runbook lets encryption, lifecycle, and access policy differ per class; this page does not merge them. |
Abuse And Moderation Hooks
Public V1 provides the hook points and the quarantine mechanism, not an automated content classifier.
| Hook | Proposed behavior |
|---|---|
| User report/flag | A relationship participant can report a message, attachment, or media object. A report moves the object toward QUARANTINED pending review and is an audited event per roles-and-permissions. |
| Quarantine | A quarantined object issues no signed URL through normal product surfaces. Access for review is a target-scoped, reason-coded, time-limited, audited support/admin grant — the grant model is owned by roles-and-permissions, not restated here. |
| Support review | Support/admin playback, download, redaction, or deletion of flagged media follows the privacy-and-retention purge and redaction rules and emits audit events. |
| Automated moderation | Proposed deferral: Public V1 does not run automated image/audio content moderation or classification. The report-and-quarantine hook plus scan-before-ready is the launch control. Automated moderation is revisitable with recorded rationale if abuse volume warrants it and a privacy review of any classifier is completed first. |
CDN Posture
| Concern | Proposed rule |
|---|---|
| Public V1 default | Proposed: no public CDN in front of private media. Signed URLs resolve directly against the in-region storage endpoint. A CDN edge cache of health-adjacent private media adds a residency surface and a cache-invalidation-vs-purge gap that Public V1's single-operator posture and the pending residency review do not justify. |
| Exercise thumbnails and public assets | Non-private, license-cleared assets (the free-exercise-db import and CoachMe-owned placeholders, Approved in the integration-register) may be served with ordinary caching because they carry no user data. This is the only media class exempt from the signed-URL-only rule. |
| Revisit trigger | A CDN for private media is revisitable only after the storage service and region are selected, a residency-compatible signed-access edge configuration is proven, and cache invalidation is reconciled with the privacy-and-retention purge lifecycle. |
Required Evidence
The byte-path controls are proven by tests, not by inspection.
- An uploaded object is not addressable by any playback or download endpoint until scan and metadata strip complete.
- A known test-malware fixture is quarantined and never becomes
AVAILABLE; an unavailable or timed-out scanner fails closed. - A processed image, audio file, and document carry no residual EXIF/GPS or unsafe embedded metadata after ingest.
- A signed upload URL permits only its one object key and method; a signed download URL expires within the TTL ceiling and grants no list or prefix access.
- A playback/download request after relationship end, deletion, or quarantine is refused before any URL is signed.
- A derivative is served only through a re-checked signed URL and is purged together with its canonical object.
- A purged object issues no further signed URLs.
- A reported object moves to
QUARANTINEDand is reachable only through an audited support/admin grant. - Both buckets reject public-read access.
Proposed Locks And Open Questions
Because this is a draft, nothing here is locked yet. On approval, the first group is intended to become locked rules; the second stays deliberately open.
Proposed To Lock On Approval
- Scan-before-ready and fail-closed as the byte-path invariant across both buckets.
- Metadata stripping on ingest, making the stripped/normalized object canonical.
- Private buckets with signed-URL-only access, single-key and method-scoped, with permission re-checked at issuance.
- Short TTL ceilings (proposed 15 min upload, 5 min playback/download), configured per environment but never exceeded without review.
- Derivatives under the same workspace prefix, private, and purged with their canonical object.
- No public CDN for private media; public exercise/placeholder assets excepted.
Deliberately Open
- The object-storage service and region, and therefore encryption key custody and in-region CDN feasibility — owned by the integration-register and gated on the PDPL residency review. Not resolved here.
- The PDF preview engine (Poppler vs PDFium), still Conditional in the integration-register; document metadata handling is confirmed on selection.
- Automated content moderation, deferred with a named revisit trigger.
- Exact TTL values and media-processing limits within the proposed ceilings, set with the performance and integration decisions.
Related Docs
These pages own adjacent rules this page depends on but does not redefine.
Integration Register
Selects the object-storage service and region, ClamAV, Sharp/libvips, FFmpeg, and the PDF engine, with their launch evidence.
Environment Runbook
Owns the two-bucket topology, per-environment bucket config, TTL and lifecycle configuration, encryption settings, and residency gating.
Privacy And Retention
Owns media data classes, the object lifecycle states, purge jobs and receipts, redaction, and legal hold.
Multi-tenancy
Owns the workspace/<workspace_id>/… object-key prefix and the tenant fence that scopes every object and derivative.
API Contract Standard
Owns the upload-intent handshake, completion, playback/download endpoints, and media error codes.
Roles And Permissions
Owns who may read, play, download, export, or moderate media, and the support/admin grant and audit model.
Offline Sync Protocol
Owns offline upload queueing and mobile cache purge that this page's signed-URL rules depend on.
Observability Plan
Owns upload/processing metrics, oldest-processing-age gauges, and failed-upload diagnosis.
Security Plan
Owns the media-URL threat model, rate limits on upload-intent, and the broader security posture this page's byte controls sit inside.
Performance Plan
Owns media volume estimates and the processing-throughput assumptions that size the media workers.
Development Readiness Review
Tracks this page as one of the three architecture docs required before implementation commits to schemas and APIs.