mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
feat(web): gesture-control overlay integration (Phase 5, opt-in via CODEMAN_GESTURE=1)
Loads a hand-tracking overlay into the dashboard that detaches a session by pinch-grabbing its tab and pulling it out — driving the existing app.detachSession(id) hook. Bundle (src/web/public/gesture/gesture-codeman.js) is built from the codeman-gesture-control project's src/codeman/entry.ts (esbuild, MediaPipe included) and served same-origin. OFF by default — guarded entirely by CODEMAN_GESTURE=1: - server.ts: injects the module script into the dashboard HTML only (not solo /session/:id popups, which have no tab strip). - auth.ts: widens CSP only under the flag — adds 'wasm-unsafe-eval' (MediaPipe WASM) and the pinned MediaPipe CDNs (cdn.jsdelivr.net wasm, storage.googleapis.com model) to connect-src, plus worker-src 'self' blob:. Production CSP is unchanged when the flag is off. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -154,13 +154,22 @@ export function registerAuthMiddleware(app: FastifyInstance, https: boolean): Au
|
||||
* Register security headers and CORS middleware on every response.
|
||||
*/
|
||||
export function registerSecurityHeaders(app: FastifyInstance, https: boolean): void {
|
||||
// Gesture-control overlay (opt-in via CODEMAN_GESTURE=1) runs MediaPipe, which
|
||||
// needs WebAssembly eval and must fetch its wasm/model from the pinned CDNs.
|
||||
// Computed once; OFF by default so the production CSP is byte-for-byte unchanged.
|
||||
const gesture = process.env.CODEMAN_GESTURE === '1';
|
||||
const scriptSrc = "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net" + (gesture ? " 'wasm-unsafe-eval'" : '');
|
||||
const connectSrc =
|
||||
"connect-src 'self' wss://api.deepgram.com" + (gesture ? ' https://cdn.jsdelivr.net https://storage.googleapis.com' : '');
|
||||
const workerSrc = gesture ? "; worker-src 'self' blob:" : '';
|
||||
const csp =
|
||||
`default-src 'self'; ${scriptSrc}; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; ` +
|
||||
`img-src 'self' data: blob:; ${connectSrc}; font-src 'self' https://cdn.jsdelivr.net; frame-ancestors 'self'${workerSrc}`;
|
||||
|
||||
app.addHook('onRequest', (req, reply, done) => {
|
||||
reply.header('X-Content-Type-Options', 'nosniff');
|
||||
reply.header('X-Frame-Options', 'SAMEORIGIN');
|
||||
reply.header(
|
||||
'Content-Security-Policy',
|
||||
"default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: blob:; connect-src 'self' wss://api.deepgram.com; font-src 'self' https://cdn.jsdelivr.net; frame-ancestors 'self'"
|
||||
);
|
||||
reply.header('Content-Security-Policy', csp);
|
||||
if (https) {
|
||||
reply.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1007,6 +1007,13 @@ export class WebServer extends EventEmitter {
|
||||
const safeId = JSON.stringify(soloSessionId).replace(/</g, '\\u003c');
|
||||
html = html.replace('</head>', `<script>window.__CODEMAN_SOLO__=${safeId};</script>\n</head>`);
|
||||
}
|
||||
// Gesture-control overlay (Phase 5): dashboard only (not solo popups, which
|
||||
// have no tab strip), opt-in via CODEMAN_GESTURE=1. The bundle is served
|
||||
// same-origin from /gesture/ so 'self' covers it; CSP is widened to match in
|
||||
// registerSecurityHeaders under the same flag.
|
||||
if (!soloSessionId && process.env.CODEMAN_GESTURE === '1') {
|
||||
html = html.replace('</head>', `<script type="module" src="/gesture/gesture-codeman.js"></script>\n</head>`);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user