Tencent / Tencent/BrowserSkill

Local WebSocket daemon accepts commands from any browser extension, not just BrowserSkill's own

Open
#273 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
5.7k
Forks
399
Avg merge
2d 9h
Merged PRs (30d)
74

Description

Affected versions: confirmed on tag ext-v0.3.0 (commit 75e2c64, 2026-09-16), the latest tag available at time of report, matching HEAD exactly.

Summary

BrowserSkill's local daemon (bsk daemon start) runs a WebSocket server (default port 52800) that a companion browser extension connects to, giving the daemon (and, through it, any AI agent driving it) full control of the user's real, already-logged-in browser: reading pages, taking screenshots, filling forms, and interacting with any site the user is signed into. The daemon's only access control for this local WebSocket server was origin_allowed() (crates/bsk-cli/src/daemon/ws.rs), which checks that the connecting page's Origin header is shaped like a Chrome extension origin (chrome-extension:// followed by exactly 32 lowercase a-p characters, the MV3 extension-id format), but never checks which extension it actually is. Every browser extension has an origin of exactly this shape, so any other extension installed in the same browser profile (a malicious one, a compromised or supply-chain-attacked legitimate extension, or one the user was tricked into installing) can open a WebSocket connection to this well-known local port and be treated identically to BrowserSkill's own extension, gaining full control of the user's browser and its logged-in sessions.

Details

crates/bsk-cli/src/daemon/ws.rs (before a fix), with the code's own doc comment preserved:

/// Result of the optional Origin allow-list check.
///
/// TODO(M10/M12): pair v0.1 GA with an actual extension-id allow-list
/// (`DaemonConfig::allowed_extension_ids: HashSet<String>`) populated
/// from a config file / pairing flow, rather than accepting any
/// extension-shaped origin. Review M4/M5 I8, 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.
pub(super) fn origin_allowed(origin: &str, allow_any: bool) -> bool {
    if allow_any {
        return true;
    }
    let prefix = "chrome-extension://";
    if let Some(rest) = origin.strip_prefix(prefix) {
        if rest.len() == 32 && rest.bytes().all(|b| (b'a'..=b'p').contains(&b)) {
            return true;
        }
    }
    false
}

handle_connection calls this once, during the WS opening handshake, and nothing afterward re-checks the caller's identity before dispatching full tool.* RPCs (browser automation commands) over the connection. The daemon's default local port (52800) is a published constant (DEFAULT_WS_PORT), and no additional secret is required to reach it, since the only local-mode gate is this shape check.

POC

(available upon request)

Impact

Any other browser extension installed in the same browser profile as BrowserSkill's own extension can drive the daemon and, through it, the user's real, already-logged-in browser: navigating pages, reading page content and screenshots, and submitting forms on any site the user is authenticated to (email, banking, internal tools, source control, etc.), with no interaction from the user and no indication that a different extension is in control. This converts "install any other browser extension, ever" into a path to full browser-session takeover on a machine running BrowserSkill's daemon.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in crates/bsk-cli/src/daemon/ws.rs by reading origin_allowed and handle_connection, then trace how the BrowserSkill extension is identified during the WebSocket handshake. Review the TODO about an extension-ID allow-list and the DEFAULT_WS_PORT usage. Done means other extension-shaped origins cannot access tool.* RPCs while the intended extension remains functional, with regression coverage for both cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.