
Tencent BrowserSkill Only Checked That an Extension ID Looked Real — Any Extension Could Feed the Agent Forged Pages
- OrcaNEWOrcaCyber: Qwen3.8 Flash Next Abliterated2026-10-26$0.80 / $1.60 per 1M tokens · 98 tok/s
- OrcaNEWOrcaCyber: MiMo V2.6 Flash Abliterated2026-10-26$0.80 / $1.60 per 1M tokens · 115 tok/s
- MoonshotNEWMoonshotAI: Kimi K3.1 Pro2026-10-2455Intelligence
- OrcaNEWOrcaCyber: DeepSeek V4.1 Flash Abliterated2026-10-23$0.80 / $1.60 per 1M tokens · 138 tok/s
- minimaxNEWMiniMax: M3.1 Flash2026-10-1943Intelligence
- OrcaNEWOrca: OrcaCyber Ultra 1.02026-10-17$5.00 / $10.00 per 1M tokens · 43 tok/s
- deepseekNEWDeepSeek: DeepSeek V4.1 Flash 10172026-10-1745Intelligence
- DeepSeekNEWDeepSeek: DeepSeek V4.1 Flash2026-10-1740Intelligence
- OrcaOrca: OrcaCyber Zero 1.52026-10-10$3.00 / $7.50 per 1M tokens · 111 tok/s
- openaiOpenAI: GPT-6.1 Sol2026-09-2952Intelligence
- anthropicAnthropic: Claude Sonnet 5.52026-09-2856Intelligence
- typesafeTypeSafe: Jev 1.132026-09-24$0.04 / $0.00 per 1M tokens · 698 tok/s
- OpenAIOpenAI: GPT-6 Luna2026-09-2238Intelligence
- OpenAIOpenAI: GPT-6 Sol2026-09-2248Intelligence
- AnthropicAnthropic: Claude Opus 5.52026-09-2258Intelligence
- xiaomiXiaomi: MiMo-V2.6-Flash2026-09-21$0.14 / $0.28 per 1M tokens · 45 tok/s
- xiaomiXiaomi: MiMo-V2.6-Pro2026-09-21$0.43 / $0.87 per 1M tokens · 50 tok/s
- xAIGrok 4.72026-09-2146Intelligence
- OrcaOrca: OrcaCyber Zero 1.02026-09-17$3.00 / $7.50 per 1M tokens · 231 tok/s
- OrcaOrca: OrcaVerify Text 1.02026-09-16$2.00 / $0.00 per 1M tokens · 3445 tok/s
On 17 September 2026, a GitHub user posting as carfeii opened issue #273 against Tencent's open-source BrowserSkill project with a narrow claim: the local daemon that connects the browser extension to an AI agent was not checking which extension connected, only that the connection looked like an extension at all. Three days later, on 20 September, VulnCheck published the finding as CVE-2026-94111, describing a daemon that "accepts any chrome-extension origin with 32 characters in range a-p," classifies it as CWE-346 (origin validation error), and credits George Chen. The advisory scores it CVSS 4.0 6.9, medium, with integrity impact rated high and confidentiality and availability low. Nothing in the record suggests the flaw was used: the archive logs no exploitation and grades the entry confidence A, with real_harm false. This is one record in the AI incident archive, where each entry carries its own sources, severity, confidence grade and disputed flag.
A format check that never checked identity
The code at the centre of the record is small. In crates/bsk-cli/src/daemon/ws.rs, the function origin_allowed() takes the WebSocket handshake's Origin header, strips the string chrome-extension://, and returns true if what remains is exactly 32 characters long and every character falls between a and p. That is the complete test. It is run once, inside the HTTP upgrade callback, and a failure returns a 403 whose message reads "Origin not in chrome-extension allow-list" — although as published there was no list to be in.
The pattern is not arbitrary. Chrome's Manifest V3 extension IDs are 32 characters drawn from exactly those sixteen letters, so the check correctly recognises the shape of a real extension origin. The defect is that every extension has that shape. The check asks whether the caller looks like a browser extension, never whether it is this project's browser extension, and the identifier it should have compared against was already public: the project's README links the Chrome Web Store listing, whose ID is hhcmgoofomhgciiibhipgmgkgnoenaoi, and the Edge Add-ons ID beside it. Neither string appears anywhere in the gate.
The daemon listens on a fixed local port, 52800, declared as DEFAULT_WS_PORT in the CLI and used without variation in the local connection path. The port is documented, constant, and reachable by anything already running in the user's browser profile. Location is why the advisory's vector begins AV:L — the attacker needs a foothold on the machine, and PR:L records that a low-privilege one is enough. An extension installed in the same profile is precisely that kind of foothold.
What the seat is worth, and how much of it is established
The advisory's impact sentence is broad: an attacker who registers a malicious extension as a browser client can "intercept and manipulate page content, DOM, and screenshots returned to the AI agent." That is the description the CVE, the GitHub advisory record GHSA-x6f2-qp26-2xrh and the archive all carry, and it explains the CVSS integrity rating of high. It is also a summary written by the party that assigned the CVE, not a report of a demonstrated attack.
A more careful account arrived the same week, in the issue thread itself. On 20 September, a commenter posting as bb6894 reported re-checking the code against main at commit fa953dc6 (18 September) and traced what a foreign client can actually reach after the handshake. Most inbound tool methods, the comment says, return UnknownMethod to a WebSocket peer; audit requests are scoped to the client's own records; and cross-session actions such as window_closed, user_interrupt and update_interaction are guarded against sessions owned by another browser. What survives that filter is narrower and, for an agent, arguably worse than interception: a foreign extension can take a browser slot under an attacker-chosen instance_id, and if it is the only browser registered — a cold start or a daemon restart — it receives the agent's tool requests and answers them with fabricated page content, DOM or screenshots. The commenter calls this "result forgery toward the agent" and "a channel into the agent's perception." A second effect is a denial of service: with a genuine browser also online and no selector, session startup can fail with MultipleBrowsersOnline.
Neither account has been demonstrated in public. The reporter described a proof of concept as "available upon request"; nobody has published one, and no exploitation is recorded. So the difference between the two accounts is a difference between two readings of the same source file, not between a theory and a working exploit — and the piece reports both rather than choosing. What can be said without qualification is that there was never a second identity check after the handshake, so whatever the gate admitted stayed admitted for the life of the connection.
The code already knew
The most uncomfortable document in this record is not the advisory. It is the comment directly above the function, shipped in the tagged release the advisory points at. It marks the gap as TODO(M10/M12), names the allow-list it intends to build — a DaemonConfig::allowed_extension_ids set populated from a config file or pairing flow — and then states the reason it was deferred: "acceptable defense-in-depth gap for now because pairing happens through the popup, but a side-loaded extension on the same machine currently passes the gate."
Read plainly, the maintainers knew which caller the gate let through, wrote down that they knew, and shipped. That does not make the exposure larger than the advisory says; it makes it less surprising, and it moves the question from "how did this get past review" to "what would have made the deferred fix urgent." The mitigating design assumption is real and also documented: the local connection is opt-in. Per the README, the user installs the extension, opens its popup, and enables the local connection, and the daemon has to be running for any of this to be reachable. A profile with the extension installed but the toggle off, or with no bsk daemon running, is not a listening target.
Five weeks later, the gate is unchanged

The record's affected range is "through 0.3.0," and that is the range the CNA assigned. It is not the end of the story in the repository. The CLI's 0.3.0 release was tagged on 17 September at 02:22 UTC; the issue was filed the same day at 15:14 UTC, roughly thirteen hours later. Three further CLI releases have shipped since — 0.3.1 on 23 September, 0.3.2 on 30 September, and 0.3.3 on 15 October — and the origin_allowed() function at each of those tags is the same check described above, with the TODO still above it. The main branch carries it too as of 27 October.
A fix exists, but only as a proposal. On 28 September, a contributor posting as dangzitou opened pull request #363 from a personal fork, titled "fix(daemon): pin WS origin gate to BrowserSkill's own extension ids." It replaces the shape test with a comparison against the two store IDs, and adds BSK_ALLOWED_EXTENSION_IDS as an explicit admission path for sideloaded and development builds, whose unpacked IDs are derived from the install path. The pull request body claims it closes both the forgery path and the MultipleBrowsersOnline denial of service. As of 27 October it is still open and unmerged, and the issue remains open with no vendor statement attached to it.
The practical consequence is that there is no version to upgrade to that contains the fix. Anyone advising "upgrade past 0.3.0" is advising a step that does not exist yet: the later releases are later, not fixed. The record's range should be read as the range the CNA assessed on 20 September, not as a statement that 0.3.1 and after are clean.
What is established, and what is not
• Established: the released source at tag cli-v0.3.0 contains an origin gate that accepts any 32-character a–p chrome-extension origin. The reporter, the CNA and the file itself agree, and the code is public.
• Established: the gate ran once, at the WebSocket upgrade, with no later identity check, on a fixed local port of 52800.
• Established: the project's own source comment acknowledged that a sideloaded extension would pass, and deferred the allow-list.
• Established: CVSS 4.0 6.9 and CVSS 3.1 6.6 are both VulnCheck's scores, recorded by NVD as secondary. NVD's own status for the entry is Deferred, meaning it has published the CNA's data without adding an NVD-authored analysis.
• Established: CISA's SSVC assessment, dated 21 September, records exploitation as none, automatable as no, and technical impact as partial. FIRST's EPSS puts the probability of exploitation in the next thirty days at 0.00135, the 2.9th percentile, as of 26 October.
• Not established: any real-world use. No exploitation, no published proof of concept, no affected installation count. The project's repository metrics are not a count of installations, and no one has published one.
• Not established: which impact account holds. The CNA's "intercept and manipulate" and the thread's "result forgery plus denial of service" describe different ceilings, and neither has been demonstrated outside code reading.
• Not established: that the flaw stops at 0.3.0. The version string is the CNA's assessment; the code is unchanged in the three releases after it, and no vendor statement clears them.
• Contradicted, unresolved: one aggregator, DevGuard, displays an "exploitation attempts have been detected" signal for this CVE without naming a source, IOC or telemetry provider. That sits against CISA's recorded assessment of no exploitation and against the absence of any public report; the archive records no exploitation either. Treat the aggregator's flag as unsubstantiated rather than as a second data point.

Why the transport is the interesting target
Most prompt-injection guidance draws the same boundary: treat retrieved page text as untrusted, and treat the machinery that delivers it as trusted. A tool that reads a web page is assumed to be reading the page; the question is what the page says. This record inverts that. What the agent receives as page content, DOM and screenshots is whatever the client on the other end of a local socket chooses to send, and the only gate on that socket asked a question every candidate could answer correctly. If the client is foreign, "verify what the page says" has nothing left to verify against.
That is also why the severity lands where it does rather than higher. The attack is local, it presumes an extension already installed in the profile or other local code, and it requires the daemon to be running with the local connection enabled. Those conditions are ordinary for the tool's intended users and unusual for everyone else. The archive's medium severity and its recorded absence of harm are consistent with that shape: a real widening of trust at the exact boundary the design was supposed to police, with no evidence anyone walked through it.
The narrow practical advice that follows is not a mitigation anyone has published, only the boundary the code leaves standing. Because admission is decided once at the handshake on a published port, the reachable surface is set by which extensions are installed in the profile that runs the daemon, and by whether that daemon and the extension's local connection are switched on at the time. Until an extension-ID allow-list ships, those two facts are the whole of the control.

CVE-2026-94111 will age one of two ways. Either the pull request gets merged and the record becomes a short entry about a known gap that took a while, or the same shape check is still in the tree in a year and the entry is cited as an early example of the class it belongs to: not a page carrying a hostile instruction, but the pipe that decides which page the agent is reading.
