Skip to content

fix(browse): case-insensitive exfiltration-domain blocklist matching (#2190)#2228

Open
time-attack wants to merge 1 commit into
garrytan:mainfrom
time-attack:case-insensitive-domain-blocklist
Open

fix(browse): case-insensitive exfiltration-domain blocklist matching (#2190)#2228
time-attack wants to merge 1 commit into
garrytan:mainfrom
time-attack:case-insensitive-domain-blocklist

Conversation

@time-attack

Copy link
Copy Markdown

What was broken (plain English)

The browser assistant keeps a blocklist of shady sites known for stealing data. The check only matched lowercase web addresses, so a booby-trapped page could point to the same bad site written with capital letters and slip right past the guard. The assistant would then hand that dangerous link back instead of blocking it.

How it was fixed (plain English)

The guard now ignores capitalization when it compares a web address to the blocklist. A known data-stealing site is caught whether it is written in lowercase or uppercase. Ordinary sites that happen to use capital letters are still allowed through, so nothing legitimate gets blocked by mistake.

Fixes #2190

Technical detail

urlBlocklistFilter in browse/src/content-security.ts compared URLs against BLOCKLIST_DOMAINS with case-sensitive substring checks (url.includes(domain) / contentUrl.includes(domain)). Because hostnames and URL schemes are case-insensitive (RFC 3986), an uppercased exfiltration sink such as https://WEBHOOK.SITE/x bypassed the filter and reached the scoped browser agent.

The fix:

  • Normalizes the page URL and each extracted content URL to lowercase before comparing (blocklist entries are lowercase by construction).
  • Adds the i flag to the content URL-extraction regex so an uppercased scheme (HTTPS://) can't evade extraction either.

Live before/after evidence

Each probe drives the exact server-egress security decision (runContentFilters in block mode — the same call browse/src/server.ts makes for a scoped browser agent) against isolated baseline (pinned to origin/main) and candidate installs, with a page linking to an uppercased exfil sink. Baseline and candidate commands are byte-identical.

repo cohort baseline candidate
gstack-auto with-gstack 1 — allowed (VULNERABLE) 0 — blocked
zotero-arxiv-daily with-gstack 1 — allowed (VULNERABLE) 0 — blocked
lacp with-gstack 1 — allowed (VULNERABLE) 0 — blocked
stagehand control 0 — legit uppercase allowed 0 — legit uppercase allowed
infisical control 0 — legit uppercase allowed 0 — legit uppercase allowed

Baseline verdict: blocked=false … ALLOWED — uppercase exfiltration sink bypassed the blocklist.
Candidate verdict: blocked=true … warnings=["Content contains blocklisted URL: https://WEBHOOK.SITE/…"] … BLOCKED.
Controls confirm the fix does not over-block legitimate uppercase domains (e.g. GitHub.com).

Tests

Added 4 regression tests in browse/test/content-security.test.ts (case-insensitive page URL, case-insensitive content URL, uppercased scheme, and no over-block on legitimate uppercase domains). Full file: 62 pass / 0 fail.

urlBlocklistFilter compared URLs against the exfiltration blocklist with
case-sensitive substring checks, so an uppercased sink (https://WEBHOOK.SITE/x)
bypassed the guard and reached the scoped browser agent. Normalize the page URL
and extracted content URLs to lowercase before comparing, and make URL
extraction scheme-insensitive so HTTPS:// links are still caught.

Fixes garrytan#2190

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jul 10, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

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.

Case-sensitive domain matching allows blocklist bypass

2 participants