Skip to content

Add-on iframe: delegate microphone + camera Permissions Policy#52068

Open
pvizeli wants to merge 3 commits into
home-assistant:devfrom
pvizeli:feat/addon-iframe-allow-mic-cam
Open

Add-on iframe: delegate microphone + camera Permissions Policy#52068
pvizeli wants to merge 3 commits into
home-assistant:devfrom
pvizeli:feat/addon-iframe-allow-mic-cam

Conversation

@pvizeli
Copy link
Copy Markdown
Member

@pvizeli pvizeli commented May 16, 2026

Proposed change

The add-on ingress iframe rendered by ha-panel-app.ts currently ships with no allow= attribute, so the W3C Permissions Policy default of deny applies for microphone and camera on the cross-origin iframe.

Any add-on that wants to call getUserMedia — voice notes, dictation, video calls, photo capture, conference apps — fails silently with NotAllowedError before the browser even surfaces the runtime permission prompt. The page sees a rejected promise but the user sees nothing change.

This is most visible on the Android Companion app, where the add-on iframe is the only entry point. The user presses the mic, nothing happens, no toast, no logs. The same problem exists in a desktop Chrome tab opened to the HA frontend but is less surprising there because the user can right-click → open in a new tab as a workaround.

The fix is a one-line attribute: delegate microphone, camera, and clipboard-write to the add-on iframe.

       <iframe
         class=${classMap({
           loaded: this._iframeLoaded,
           "kiosk-mode": this._kioskMode,
         })}
         title=${this._addon.name}
         src=${this._addon.ingress_url!}
+        allow="microphone; camera; clipboard-write"
         @load=${this._checkLoaded}
         ${ref(this._iframeRef)}
       >
       </iframe>

Why this is safe

  • Add-ons are first-party software the user explicitly installs. They've already accepted that the add-on runs as part of Home Assistant. The trust boundary between Core and an installed add-on is the install consent, not the iframe attribute.
  • Chrome's runtime permission prompt still gates the hardware. The allow= attribute only allows the iframe to request the prompt; it doesn't grant access. The user still gets the canonical "Use your microphone?" dialog the first time, and the Android Companion app's existing WebChromeClient.onPermissionRequest flow (which already checks the runtime RECORD_AUDIO Android permission) is unchanged.
  • 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 Permissions Policy mechanism. Add-ons commonly need to surface copyable values; the symmetry keeps the policy delegation list tight.

Type of change

  • Bugfix (non-breaking change which fixes an issue)

Example use cases

  • A voice-note feature in a chat add-on (the immediate motivating case — Socialhome's DM composer).
  • A barcode-scanner add-on that uses getUserMedia({video: {facingMode: 'environment'}}).
  • A voice-dictation add-on bridging to a STT pipeline.
  • A copy-to-clipboard "copy device ID" / "copy webhook URL" affordance in any add-on settings page.

Additional information

  • No new dependencies.
  • No related issue (filing this as a direct fix). Happy to add a tracking issue + cross-link if maintainers prefer.
  • Doesn't affect hui-iframe-card.ts (Lovelace iframe card) or ha-panel-iframe.ts (legacy "Webpage" panel) — those embed user-configured arbitrary URLs and have a different trust model. Keeping this change strictly to the add-on ingress iframe.

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist.
  • The code has been formatted using Prettier (prettier --write).

Copy link
Copy Markdown

@home-assistant home-assistant Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @pvizeli,

When attempting to inspect the commits of your pull request for CLA signature status among all authors we encountered commit(s) which were not linked to a GitHub account, thus not allowing us to determine their status(es).

The commits that are missing a linked GitHub account are the following:

Unfortunately, we are unable to accept this pull request until this situation is corrected.

Here are your options:

  1. If you had an email address set for the commit that simply wasn't linked to your GitHub account you can link that email now and it will retroactively apply to your commits. The simplest way to do this is to click the link to one of the above commits and look for a blue question mark in a blue circle in the top left. Hovering over that bubble will show you what email address you used. Clicking on that button will take you to your email address settings on GitHub. Just add the email address on that page and you're all set. GitHub has more information about this option in their help center.

  2. If you didn't use an email address at all, it was an invalid email, or it's one you can't link to your GitHub, you will need to change the authorship information of the commit and your global Git settings so this doesn't happen again going forward. GitHub provides some great instructions on how to change your authorship information in their help center.

    • If you only made a single commit you should be able to run
      git commit --amend --author="Author Name <email@address.com>"
      
      (substituting "Author Name" and "email@address.com" for your actual information) to set the authorship information.
    • If you made more than one commit and the commit with the missing authorship information is not the most recent one you have two options:
      1. You can re-create all commits missing authorship information. This is going to be the easiest solution for developers that aren't extremely confident in their Git and command line skills.
      2. You can use this script that GitHub provides to rewrite history. Please note: this should be used only if you are very confident in your abilities and understand its impacts.
    • Whichever method you choose, I will come by to re-check the pull request once you push the fixes to this branch.

We apologize for this inconvenience, especially since it usually bites new contributors to Home Assistant. We hope you understand the need for us to protect ourselves and the great community we all have built legally. The best thing to come out of this is that you only need to fix this once and it benefits the entire Home Assistant and GitHub community.

Thanks, I look forward to checking this PR again soon! ❤️

@home-assistant
Copy link
Copy Markdown

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant Bot marked this pull request as draft May 16, 2026 13:58
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.
@pvizeli pvizeli force-pushed the feat/addon-iframe-allow-mic-cam branch from fe65390 to 9b53a3f Compare May 16, 2026 14:03
@pvizeli pvizeli marked this pull request as ready for review May 16, 2026 14:05
@home-assistant home-assistant Bot dismissed their stale review May 16, 2026 14:05

Stale

@pvizeli
Copy link
Copy Markdown
Member Author

pvizeli commented May 16, 2026

@balloob is that somethings we can do? I need access to SocialHome to use the camera and microphone for QR code (pairing) and for speech DMs or transcript posts, and the voice calls and video calls

Copy link
Copy Markdown
Contributor

@silamon silamon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will also need to define the sandbox attribute when we're adding an allow. You can take a look at the iframe panel to have an example.

@home-assistant home-assistant Bot marked this pull request as draft May 16, 2026 18:25
balloob
balloob previously approved these changes May 16, 2026
@balloob
Copy link
Copy Markdown
Member

balloob commented May 17, 2026

@silamon The sandbox property is not needed for the allow camera to work. It would provide extra security hardening, but I am also unsure if we were to break something that an add-on relies on?

@silamon
Copy link
Copy Markdown
Contributor

silamon commented May 18, 2026

We go all-in on security. We don't want microphone or camera access to be granted to an addon without explit permission if it was granted before to the frontend.

@pvizeli
Copy link
Copy Markdown
Member Author

pvizeli commented May 18, 2026

I agree with @balloob, the sandbox will break the add-ons UI. The only person with more experience in how to quirk UI to work with add-ons is @frenck - if he feels safe, let's do it. But what I can remember, there were some add-ons on my end with excessive URL rewrites that now follow allow-same-origin.

In general, ingress is difficult because we sometimes do not control the app or the UI behind it. A user install an add-on and would like to have it working, they not care much about the other parameters as they decided it to install it.

So @frenck do you think that option (sandbox) is safe?

@balloob
Copy link
Copy Markdown
Member

balloob commented May 18, 2026

@silamon you got me into a wild rabbit hole. You are right, but our default implementation would make it wrong. We should fix both 👍

You are right that sandbox would treat it as different origin, and you are right that it is the right approach to ensure that apps don't inherit the camera permissions.

However, our default IFRAME_SANDBOX property that we use includes allow-same-origin. So we should split IFRAME_SANDBOX to have all, except same-origin. And use that one for this case.

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>
@balloob balloob marked this pull request as ready for review May 18, 2026 16:19
@home-assistant home-assistant Bot requested a review from silamon May 18, 2026 16:19
@silamon silamon requested a review from Copilot May 18, 2026 18:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to improve add-on ingress iframe capabilities by delegating Permissions Policy features so embedded add-on UIs can successfully request browser-gated APIs like microphone/camera and write to the clipboard.

Changes:

  • Adds a Permissions Policy allow attribute to the add-on ingress iframe (ha-panel-app) to delegate microphone, camera, and clipboard-write.
  • Refactors iframe sandbox constants to separate “base sandbox” from “sandbox + allow-same-origin”.
  • Updates existing iframe embeds to use the new IFRAME_SANDBOX_SAME_ORIGIN constant.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/util/iframe.ts Splits sandbox constants into a base set and a ..._SAME_ORIGIN variant.
src/panels/lovelace/cards/hui-iframe-card.ts Switches to the new IFRAME_SANDBOX_SAME_ORIGIN constant (behavior intended to remain equivalent).
src/panels/iframe/ha-panel-iframe.ts Switches to the new IFRAME_SANDBOX_SAME_ORIGIN constant (behavior intended to remain equivalent).
src/panels/app/ha-panel-app.ts Adds allow="microphone; camera; clipboard-write" and introduces a sandbox attribute on the ingress iframe.

Comment thread src/panels/app/ha-panel-app.ts
Comment thread src/panels/app/ha-panel-app.ts
Comment thread src/util/iframe.ts
@silamon silamon added the second-opinion-wanted Add this label when a reviewer needs a second opinion from another member. label May 18, 2026
@bramkragten bramkragten self-requested a review May 19, 2026 14:35
@silamon
Copy link
Copy Markdown
Contributor

silamon commented May 19, 2026

I think from my view that it is good to go. I've added a second-opinion though since I'm still not convinced about not needing the allow-source-origin.

@balloob
Copy link
Copy Markdown
Member

balloob commented May 19, 2026

If we allow source origin, and since the origin is the same, it will have full access to window.parent and can do whatever it wants, including just adding a script tag there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed second-opinion-wanted Add this label when a reviewer needs a second opinion from another member.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants