diff --git a/src/components/Utilities/UtilityShellPage.tsx b/src/components/Utilities/UtilityShellPage.tsx index 35357b0..f39f8a7 100644 --- a/src/components/Utilities/UtilityShellPage.tsx +++ b/src/components/Utilities/UtilityShellPage.tsx @@ -51,7 +51,10 @@ export default function UtilityShellPage({tool, ...config}: UtilityShellPageProp features, scriptType = 'module', appPath, - iframeAllow = "camera 'self'", + // Delegate nothing unless a tool asks for it. The default used to be + // "camera 'self'", which handed the camera to every embedded utility and + // forced the Permissions-Policy header to allow it site-wide. + iframeAllow = '', } = config; const iframeSrc = useBaseUrl(appPath ?? `/utility-apps/${slug}/app.html`); diff --git a/src/data/utilityShellPages.tsx b/src/data/utilityShellPages.tsx index 0452d97..1ea9a65 100644 --- a/src/data/utilityShellPages.tsx +++ b/src/data/utilityShellPages.tsx @@ -47,6 +47,13 @@ export type UtilityPageConfig = { * Permissions delegated to the embedded iframe. Capabilities are granted per * utility, never site-wide: only tools that actually need a device get it, * and the matching Permissions-Policy path rule lives in vercel.json. + * + * That rule comes in pairs, and both halves are required: one for the shell + * page, because a same-origin iframe can never hold a capability its parent + * document lacks, and one for /utility-apps//. The shell-page rule also + * has to carry an optional locale segment — the page exists at + * /utilities// and at /ru/utilities// and four more besides, and + * a rule written without it silently covers English only. */ iframeAllow?: string; }; @@ -197,6 +204,8 @@ export const utilityPageConfigs: Record = { 'Local history with import/export', ], scriptType: 'module', + // Live scanning: the only utility on the site that needs a camera. + iframeAllow: "camera 'self'", }, 'pdf-bom-extractor': { slug: 'pdf-bom-extractor', diff --git a/vercel.json b/vercel.json index 2bfcbde..d7f9b0f 100644 --- a/vercel.json +++ b/vercel.json @@ -22,7 +22,7 @@ }, { "key": "Permissions-Policy", - "value": "camera=(self), microphone=(), geolocation=()" + "value": "camera=(), microphone=(), geolocation=()" }, { "key": "Content-Security-Policy", @@ -31,11 +31,11 @@ ] }, { - "source": "/utilities/whisper-transcriber/:path*", + "source": "/:locale(ru|ua|de|es|et)?/utilities/whisper-transcriber/:path*", "headers": [ { "key": "Permissions-Policy", - "value": "camera=(self), microphone=(self), geolocation=()" + "value": "camera=(), microphone=(self), geolocation=()" } ] }, @@ -44,7 +44,25 @@ "headers": [ { "key": "Permissions-Policy", - "value": "camera=(self), microphone=(self), geolocation=()" + "value": "camera=(), microphone=(self), geolocation=()" + } + ] + }, + { + "source": "/:locale(ru|ua|de|es|et)?/utilities/qr-master/:path*", + "headers": [ + { + "key": "Permissions-Policy", + "value": "camera=(self), microphone=(), geolocation=()" + } + ] + }, + { + "source": "/utility-apps/qr-master/:path*", + "headers": [ + { + "key": "Permissions-Policy", + "value": "camera=(self), microphone=(), geolocation=()" } ] }, @@ -125,4 +143,4 @@ "permanent": true } ] -} \ No newline at end of file +}