Skip to content

feat(osr): report the real screen — removes a headless fingerprint#23

Merged
wenkaifan0720 merged 1 commit into
mainfrom
feat/real-screen-info
Jul 19, 2026
Merged

feat(osr): report the real screen — removes a headless fingerprint#23
wenkaifan0720 merged 1 commit into
mainfrom
feat/real-screen-info

Conversation

@wenkaifan0720

Copy link
Copy Markdown
Collaborator

Why

An OSR browser whose GetScreenInfo reports screen == its own viewport is a textbook headless/embedded fingerprint. Bot-detection engines (reCAPTCHA v3, DataDome, Cloudflare) key on exactly this. Measured on a live cefWebview tile before this change:

signal before
outerHeight vs innerHeight 530 == 530 (tell)
screen.width × height == the 800×560 tile (tell)
screen.colorDepth 0 (tell)
screenX / screenY 0 / 0 (tell)

What

Report the truth, not the tell — this is not spoofing:

  • GetScreenInfo.rect / available_rect = the real NSScreen frame / visibleFrame (the actual monitor), with depth 24 / 8-bit components.
  • GetRootScreenRect (added) = a plausible window frame at a non-zero offset, taller than the view by typical browser chrome, so outerHeight > innerHeight.
  • GetViewRect (the render size) stays tile-sized — exactly as a real browser window is smaller than its screen. device_scale_factor (Retina) preserved.
  • Falls back to a plausible 14"-class frame with no display (headless CI).

Popups (<select>) and IME composite into our own surface from view-relative coords, and GetScreenPoint is left at default, so the screen/root-window change doesn't move them; a larger screen rect only widens the popup clamp area.

Validation (live)

Measured after on the same tile:

signal after
outerHeight / innerHeight 617 / 530
screen.width × height 1496 × 935 (real display) ✅
colorDepth 24
screenX / screenY 100 / 80

Page still renders (example.org), DOM still interactive, <select> + input inject fine. No wire-protocol change.

Context

One of several fingerprint tells for Campus's embedded webview. Note the honest ceiling: this removes a headless geometry signal, but captcha scoring is dominated by IP/account reputation, and Google's embedded-framework sign-in block is a separate deterministic policy neither this nor any fingerprint change clears.

🤖 Generated with Claude Code

… fingerprint

An OSR browser whose GetScreenInfo reports screen == its own viewport is a
textbook headless/embedded fingerprint: window.screen.width == innerWidth,
screen.colorDepth == 0, and (with no GetRootScreenRect) window.outerHeight ==
innerHeight and screenX/screenY == 0. Bot-detection engines (reCAPTCHA, DataDome,
Cloudflare) key on exactly these. Measured on a live tile before this change:
outerHeight == innerHeight == 530, colorDepth 0, screenX/Y 0.

Report the truth instead of the tell — this is NOT spoofing:
- GetScreenInfo.rect / available_rect = the REAL NSScreen frame / visibleFrame
  (the actual monitor the app is on), with depth 24 / 8-bit components.
- GetRootScreenRect (added) = a plausible window frame at a non-zero offset,
  taller than the view by typical browser chrome, so outerHeight > innerHeight.
- GetViewRect (the render size) stays tile-sized — exactly like a real browser
  window is smaller than its screen. device_scale_factor (Retina) is preserved.
- Falls back to a plausible 14"-class frame when there's no display (headless CI).

Popups (<select>) and IME composite into our own surface from VIEW-relative
coords, and GetScreenPoint is left at its default, so the screen/root-window
change doesn't move them; a bigger screen rect only widens the popup clamp area.

Measured after (same tile): outerHeight 617 > innerHeight 530, screen 1496x935
(the real display, not 800x560), colorDepth 24, screenX/Y 100/80. Page still
renders, DOM still interactive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wenkaifan0720
wenkaifan0720 merged commit fd63ffa into main Jul 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant