Summary
The backend sends Permissions-Policy: camera=(), microphone=(), geolocation=() on every response, but the shipped frontend includes voice recording and group call flows that call navigator.mediaDevices.getUserMedia(...). When the SPA is served by the backend, the document-level policy can block the microphone and camera APIs before the app's own permission/error handling has a chance to work.
This is separate from #34, which tracks broad CSP connect-src WebSocket destinations. This issue is about the browser feature policy for local media capture.
Evidence
backend/handler/handler.go sets Permissions-Policy to disable both camera and microphone.
backend/main.go wraps the full mux with SecurityHeadersMiddleware, including the static SPA fallback and asset server.
frontend/src/components/ChatInput.tsx starts voice recording with navigator.mediaDevices.getUserMedia({ audio: true }).
frontend/src/components/VideoCall.tsx starts calls with getUserMedia(...) for audio/video, falls back to audio-only capture, and later reacquires video when switching camera.
Impact
Voice messages and group calls can fail under the production backend response headers even when the user grants browser permission and the device has a working microphone/camera. The app will look like media permission or device access is broken, but the root cause is the server policy denying those features for the whole document.
Suggested fix
- Update the production
Permissions-Policy to allow the app's own origin to use microphone and camera, for example using a least-privilege same-origin policy rather than ().
- Keep unrelated sensors such as geolocation disabled unless the app needs them.
- Align backend response headers, frontend expectations, and any static host/proxy headers so they do not drift.
- Add a focused test or release check that the served app shell permits the media features required by voice messages and calls.
Acceptance criteria
- The backend-served app shell no longer disables microphone/camera for the top-level same-origin document.
- Voice recording can request microphone access.
- Group call startup can request microphone/camera access.
- Security header tests assert the intended policy explicitly so this does not regress.
Summary
The backend sends
Permissions-Policy: camera=(), microphone=(), geolocation=()on every response, but the shipped frontend includes voice recording and group call flows that callnavigator.mediaDevices.getUserMedia(...). When the SPA is served by the backend, the document-level policy can block the microphone and camera APIs before the app's own permission/error handling has a chance to work.This is separate from #34, which tracks broad CSP
connect-srcWebSocket destinations. This issue is about the browser feature policy for local media capture.Evidence
backend/handler/handler.gosetsPermissions-Policyto disable bothcameraandmicrophone.backend/main.gowraps the full mux withSecurityHeadersMiddleware, including the static SPA fallback and asset server.frontend/src/components/ChatInput.tsxstarts voice recording withnavigator.mediaDevices.getUserMedia({ audio: true }).frontend/src/components/VideoCall.tsxstarts calls withgetUserMedia(...)for audio/video, falls back to audio-only capture, and later reacquires video when switching camera.Impact
Voice messages and group calls can fail under the production backend response headers even when the user grants browser permission and the device has a working microphone/camera. The app will look like media permission or device access is broken, but the root cause is the server policy denying those features for the whole document.
Suggested fix
Permissions-Policyto allow the app's own origin to use microphone and camera, for example using a least-privilege same-origin policy rather than().Acceptance criteria