mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-24 00:59:20 +01:00
Add-on iframe: delegate microphone + camera Permissions Policy (#52068)
* Add-on iframe: delegate microphone + camera Permissions Policy The add-on ingress iframe in ``ha-panel-app.ts`` ships without an ``allow=`` attribute, so the Permissions Policy default of *deny* applies for ``microphone`` and ``camera`` on the cross-origin iframe. An add-on that wants to call ``getUserMedia`` — voice notes, dictation, video calls, photo capture — fails silently with ``NotAllowedError`` before the browser even surfaces the permission prompt. The failure is most visible on the Android Companion app, where there's no "open in a new tab" escape: the user presses the mic button and nothing happens, no toast, no logs. Delegate ``microphone``, ``camera``, and ``clipboard-write`` to the add-on iframe. Add-ons are first-party software the user explicitly installs, and Chrome's runtime permission prompt still gates the hardware access — the ``allow=`` attribute just lets the iframe *request* the prompt instead of being blocked at the policy layer. ``clipboard-write`` is bundled in because the next-most-frequent silent-fail in add-on land is ``navigator.clipboard.writeText`` for "copy link" / "copy code" affordances, blocked by the same mechanism. * Sandbox add-on ingress iframe without allow-same-origin Split IFRAME_SANDBOX into two constants: IFRAME_SANDBOX (without allow-same-origin) for add-on ingress iframes that need origin isolation, and IFRAME_SANDBOX_SAME_ORIGIN for external iframes that need same-origin access. This ensures add-on iframes can't inherit camera/microphone permissions already granted to the Home Assistant origin, and prevents same-origin iframes from removing their own sandbox. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Paulus Schoutsen <balloob@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { createRef, ref } from "lit/directives/ref";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { IFRAME_SANDBOX } from "../../util/iframe";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { computeRouteTail } from "../../common/url/route";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
@@ -136,6 +137,8 @@ class HaPanelApp extends LitElement {
|
||||
})}
|
||||
title=${this._addon.name}
|
||||
src=${this._addon.ingress_url!}
|
||||
.sandbox=${IFRAME_SANDBOX}
|
||||
allow="microphone; camera; clipboard-write"
|
||||
@load=${this._checkLoaded}
|
||||
${ref(this._iframeRef)}
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import "../../layouts/hass-error-screen";
|
||||
import "../../layouts/hass-subpage";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { IFRAME_SANDBOX } from "../../util/iframe";
|
||||
import { IFRAME_SANDBOX_SAME_ORIGIN } from "../../util/iframe";
|
||||
|
||||
@customElement("ha-panel-iframe")
|
||||
class HaPanelIframe extends LitElement {
|
||||
@@ -41,7 +41,7 @@ class HaPanelIframe extends LitElement {
|
||||
this.panel.title === null ? undefined : this.panel.title
|
||||
)}
|
||||
src=${this.panel.config.url}
|
||||
.sandbox=${IFRAME_SANDBOX}
|
||||
.sandbox=${IFRAME_SANDBOX_SAME_ORIGIN}
|
||||
allow="fullscreen"
|
||||
></iframe>
|
||||
</hass-subpage>
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
LovelaceGridOptions,
|
||||
} from "../types";
|
||||
import type { IframeCardConfig } from "./types";
|
||||
import { IFRAME_SANDBOX } from "../../../util/iframe";
|
||||
import { IFRAME_SANDBOX_SAME_ORIGIN } from "../../../util/iframe";
|
||||
|
||||
@customElement("hui-iframe-card")
|
||||
export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
@@ -95,7 +95,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
const sandbox_params = this._config.disable_sandbox
|
||||
? undefined
|
||||
: `${sandbox_user_params} ${IFRAME_SANDBOX}`;
|
||||
: `${sandbox_user_params} ${IFRAME_SANDBOX_SAME_ORIGIN}`;
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
|
||||
+3
-1
@@ -1,2 +1,4 @@
|
||||
export const IFRAME_SANDBOX =
|
||||
"allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts allow-modals allow-downloads";
|
||||
"allow-forms allow-popups allow-pointer-lock allow-scripts allow-modals allow-downloads";
|
||||
|
||||
export const IFRAME_SANDBOX_SAME_ORIGIN = `${IFRAME_SANDBOX} allow-same-origin`;
|
||||
|
||||
Reference in New Issue
Block a user