Skip to content

feat(browser): report realistic permission states from the embedded browser - #1344

Merged
Astro-Han merged 4 commits into
devfrom
claude/browser-permission
Jun 17, 2026
Merged

feat(browser): report realistic permission states from the embedded browser#1344
Astro-Han merged 4 commits into
devfrom
claude/browser-permission

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

Make the embedded browser report realistic permission states. Electron's default is to answer every permission check "granted", so navigator.permissions.query(...) returns granted for notifications, geolocation, camera, microphone, midi and clipboard — a state that is impossible in a real Chrome (you cannot have camera + microphone + geolocation + notifications all granted, unprompted, on a fresh profile) and an obvious automation tell. The app's existing handler only covered permission requests, not checks.

This introduces one shared policy (isDefaultGrantedPermission) used by both the request handler and a new check handler: grant exactly the permissions a fresh Chrome grants without prompting (clipboard-sanitized-write, background-sync, sensors, payment-handler) and deny the rest. Electron's boolean handler cannot express Chrome's prompt default, so denied is the faithful substitute — a normal privacy state, and far better than the impossible granted. Camera/microphone/geolocation stay denied, preserving the original "a content viewer must not silently grant them" intent.

The grant set was verified by measuring navigator.permissions.query on both sides — a fresh real Chrome 149 and the embedded browser through the same check handler. Notably midi is denied, not granted: Chrome gates Web MIDI behind a prompt since Chrome 124 (BlockMidiByDefault), so granting it would be both a privacy footgun (silent requestMIDIAccess) and a tell; and payment-handler is granted to match Chrome's default.

Second of the embedded-browser stealth-fidelity changes (after the UA work in #1343). Flat/independent of it.

Why this exact shape (measured, not guessed)

I reproduced PawWork's embedded-browser config in a standalone Electron 40.8.0 (Chromium 144) harness and measured navigator.permissions.query before and after:

permission         Electron default (current)   this PR        real Chrome (fresh, measured)
notifications      granted   ← impossible        denied         prompt
geolocation        granted   ← impossible        denied         prompt
camera (media)     granted   ← impossible        denied         prompt
microphone (media) granted   ← impossible        denied         prompt
clipboard-read     granted                       denied         prompt
persistent-storage granted                       denied         prompt
midi               granted   ← was wrong         denied         prompt   (BlockMidiByDefault, Chrome 124+)
clipboard-write    granted                       granted        granted   ✓
background-sync    granted                       granted        granted   ✓
accelerometer/gyro granted                       granted        granted   ✓
payment-handler    granted                       granted        granted   ✓  (now matched; was denied)
Notification.permission  granted ← impossible    denied (consistent with query)

The harness captured the exact Electron check-permission strings (camera and microphone both arrive as media; payment-handler and midi both reach the handler), so the policy keys on real names rather than guesses. Every "this PR" / "real Chrome" cell above was measured, not assumed.

Honest scope: this fixes the permission-state fingerprint only; it is not a behavioral-risk-control fix.

Related Issue

None — follow-up from a maintainer report; no tracking issue yet.

Human Review Status

Pending

Review Focus

  • The grant set in isDefaultGrantedPermission (logic.ts) — is "match Chrome's default-granted, deny the rest" the right policy? denied substitutes for Chrome's prompt because Electron's handler can't return prompt.
  • Both handlers now share the policy, so permissions.query state and an actual request outcome always agree. The original deny-all is relaxed only for the four benign default-granted permissions (no camera/mic/geo/notifications).

Risk Notes

  • Behavior change: the request handler now grants clipboard-sanitized-write / background-sync / sensors / payment-handler (previously denied all). These are the permissions a normal Chrome grants without a prompt and carry no camera/mic/geo/personal-data exposure.
  • Request-only permissions: the handler also grants fullscreen and pointerLock, which Chrome allows on a user gesture without a prompt. They are not exposed via permissions.query (so the fingerprint table above is unchanged), but the old deny-all broke fullscreen video and pointer-lock pages and a rejected requestFullscreen is itself a tell. fullscreen is measurement-confirmed to reach the request handler; pointerLock is Electron's documented request-permission name for the same API.
  • Scope is the embedded browser partition's session handlers only.
  • No visible UI/copy changed.

How To Verify

Verified by me (CI-checkable):

packages/desktop-electron:
typecheck (tsgo -b): clean
test src/main/browser/logic.test.ts: 20 pass (isDefaultGrantedPermission grants clipboard-sanitized-write/background-sync/sensors/payment-handler, denies media/geolocation/notifications/clipboard-read/persistent-storage/midi/midiSysex/unknown)

Empirical (Electron 40.8.0 harness reproducing the embedded config): the before/after table above — every permissions.query was granted before; after, the sensitive/prompt-type ones are denied and Notification.permission is consistent with its query.

Optional runtime re-check (your machine): open the fingerprint probe in dev:desktop's embedded browser; the permission rows should match the "this PR" column (probe flags the notifications inconsistency red — it stays green here).

Screenshots or Recordings

N/A — no visible UI change.

Checklist

  • Type label — exactly one of bug, enhancement, task, documentation.
  • Routing labels — at least one of app, ui, platform, harness, ci.
  • Priority label — exactly one of P0, P1, P2, P3.
  • Human Review Status above is set to Pending.
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed. Left unticked: no visible UI/copy changed.
  • (conditional) I considered macOS and Windows impact — permission semantics are Chromium-level and OS-independent; the policy is a pure string set, unit-tested.
  • (conditional) I called out the permission behavior change in Risk Notes (request handler now grants four benign default-granted permissions).
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

…rowser

Electron answers every permission CHECK "granted", so navigator.permissions
.query returned granted for notifications/geolocation/camera/microphone/midi/
clipboard — impossible in a real Chrome and an obvious automation tell. Add a
shared policy (isDefaultGrantedPermission) used by both the request handler and
a new check handler: grant exactly what a fresh Chrome grants without prompting
(midi, clipboard-sanitized-write, background-sync, sensors), deny the rest.
Camera/mic/geolocation stay denied. Measured before/after in an Electron 40.8.0
harness.
@Astro-Han Astro-Han added enhancement New feature or request P1 High priority labels Jun 17, 2026
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@Astro-Han, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 5 minutes and 58 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 67466a2c-0e3f-47a1-a2fb-3a2da6aa2753

📥 Commits

Reviewing files that changed from the base of the PR and between 2b5f1bc and a3ad71b.

📒 Files selected for processing (3)
  • packages/desktop-electron/src/main/browser/controller.ts
  • packages/desktop-electron/src/main/browser/logic.test.ts
  • packages/desktop-electron/src/main/browser/logic.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/browser-permission

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions label Jun 17, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested priority: P2 (includes user-path files (packages/desktop-electron/src/main/browser/controller.ts, packages/desktop-electron/src/main/browser/logic.test.ts, packages/desktop-electron/src/main/browser/logic.ts)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a consistent permission policy for the embedded Electron browser by aligning both permission requests and checks with standard Chrome defaults to prevent automation detection. Feedback suggests adding "fullscreen" and "pointerLock" to the default granted permissions to avoid breaking standard HTML5 features, along with updating the corresponding unit tests.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/desktop-electron/src/main/browser/logic.ts
Comment thread packages/desktop-electron/src/main/browser/logic.test.ts Outdated
Astro-Han added a commit that referenced this pull request Jun 17, 2026
…dependency

Two review fixes on the parity doc:

- The embedded browser rejects file://, so "load the file" was not runnable.
  Serve the probe over a local HTTP server and open the same http://127.0.0.1
  URL in both browsers (same origin, apples to apples).
- The parity table records state that assumes #1343 (UA) and #1344
  (permissions) are applied; neither is on dev yet. Say so explicitly and note
  this PR should land after both — on its own it is a forward-looking baseline,
  not current dev behavior.
… real Chrome)

Re-measured navigator.permissions.query on both sides (fresh real Chrome 149 +
the embedded browser via the same check-handler). Two corrections to the policy:

- Remove `midi`: Chrome gates Web MIDI behind a prompt since Chrome 124
  (BlockMidiByDefault), so the prior grant was both a privacy footgun (silent
  requestMIDIAccess via the request handler) and a fingerprint tell (we returned
  granted where Chrome returns prompt). It now maps to denied, the faithful
  substitute, like the other prompt-type permissions.
- Add `payment-handler`: Chrome returns granted for it by default; we were
  returning denied, a mismatch. The check handler does receive "payment-handler",
  so granting it now matches Chrome.

Final granted set = {clipboard-sanitized-write, background-sync, sensors,
payment-handler}, which reproduces Chrome's default granted set exactly; all
prompt-type permissions stay denied. Tests updated; 20 pass, typecheck clean.
Astro-Han added a commit that referenced this pull request Jun 17, 2026
…tifications

The probe only flagged a notifications/Notification.permission mismatch, so a
regression that left geolocation/camera/midi/etc. "granted" — exactly the tell
PR #1344 fixes — would have rendered green and been missed in manual diffing.

Encode the expected default state for each permission (measured against a fresh
real Chrome) and flag both directions of mismatch red: a sensitive permission
reporting "granted", or a default-granted permission (clipboard-write,
background-sync, accelerometer, payment-handler) not "granted". Validated in
real Chrome: every permission row is green (zero false positives).
…n gesture)

The deny-all-except-N handler also denied `fullscreen` and `pointerLock`, which
real Chrome allows on a user gesture without a prompt — so fullscreen video and
pointer-lock pages broke in the embedded browser, and a rejected requestFullscreen
is itself a non-Chrome tell. `fullscreen` is measurement-confirmed to reach the
request handler; `pointerLock` is Electron's documented request-permission name
for the same allow-on-gesture API. Neither is exposed via permissions.query, so
the query fingerprint is unchanged. Tests added; 21 pass, typecheck clean.
Per review: the policy rationale was duplicated across controller.ts and
logic.ts and repeated the PR description. Keep the controller comment to the
invariant (one shared policy on both handlers so query state and request
outcome agree) and the logic.ts comment to a concise policy statement; the
detailed measurement / per-permission rationale lives in the PR description.
Behavior unchanged; 21 pass, typecheck clean.
Astro-Han added a commit that referenced this pull request Jun 17, 2026
Codex: the doc claimed #1343/#1344 are "not on dev yet", which goes false the
moment this PR lands in the described order. State the dependency (the baseline
holds only with both changes applied) instead of the transient branch state; the
merge-order instruction stays in the PR body.
@Astro-Han
Astro-Han merged commit 24c050e into dev Jun 17, 2026
35 checks passed
@Astro-Han
Astro-Han deleted the claude/browser-permission branch June 17, 2026 08:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request P1 High priority platform Electron shell, OS integration, packaging, updater, signing, paths, and permissions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant