Back to skill

Security audit

QQ Music Browser Control

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a disclosed QQ Music browser controller, but it exposes broad browser-tab metadata and can overwrite arbitrary user-writable files when saving screenshots.

Install only if you are comfortable giving the skill CDP access to a browser. Use a dedicated browser profile containing only QQ Music, avoid the tabs diagnostic on a profile with private pages open, and do not pass arbitrary screenshot paths unless you are certain they are safe to overwrite.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
qq-music-ctl.js:251
Finding
Disclosure of Unrelated Browser Tab Metadata<![CDATA[ ## Vulnerability Details **File Location**: `qq-music-ctl.js`, lines 251–262 **Vulnerability Type**: Browser-wide metadata disclosure beyond the declared domain boundary **Risk Level**: Medium ### Vulnerable Code ```js async function actionTabs() { const entry = await discoverEndpoint(); output({ browser: entry.version.Browser || entry.version['Browser'] || '', baseUrl: entry.baseUrl, tabs: pageTargets(entry).map(t => ({ id: t.id, title: t.title, url: t.url, isPlayer: isPlayerTarget(t), isQQMusic: isQQMusicTarget(t), })), }); } ``` ### Technical Analysis The `tabs` action obtains all page targets exposed by the connected browser's Chrome DevTools Protocol endpoint and prints each target's ID, title, and complete URL. It does not filter the returned targets through `isQQMusicTarget` before exposing their metadata. The domain restrictions elsewhere in the implementation prevent DOM evaluation on non-QQ-Music pages, but they do not protect metadata processed by this action. Consequently, this behavior conflicts with the documented security boundary that the Skill only operates on `y.qq.com` tabs. Browser titles and URLs can contain sensitive information, including: - Search terms - Account or user identifiers - Private document identifiers - Internal hostnames and application routes - Session-like or authorization parameters embedded in URLs Enumerating CDP targets may be necessary to locate a QQ Music tab, but returning metadata for every unrelated page is not necessary for the Skill's declared music-control functionality. ### Attack Path 1. A user starts a CDP-enabled browser that contains both QQ Music and unrelated tabs. 2. The browser profile may contain private services, documents, searches, or internal applications. 3. An Agent or local caller invokes: ```bash node qq-music-ctl.js tabs ``` 4. The script requests `/json/list` from the local CDP endpoint. 5. `pageTargets(entry ...[truncated 1018 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Filter page targets before constructing the response: ```js async function actionTabs() { const entry = await discoverEndpoint(); const pages = pageTargets(entry); const qqMusicTabs = pages.filter(isQQMusicTarget); output({ browser: entry.version.Browser || entry.version['Browser'] || '', baseUrl: entry.baseUrl, tabs: qqMusicTabs.map(t => ({ id: t.id, title: t.title, url: t.url, isPlayer: isPlayerTarget(t), isQQMusic: true, })), unrelatedTabCount: pages.length - qqMusicTabs.length, }); } ``` Additional hardening measures: 1. Do not return IDs, titles, or URLs for rejected targets. 2. If diagnostic information is required, return only an aggregate count of unrelated tabs. 3. Rename or document the action so that its actual disclosure scope cannot be misunderstood. 4. Continue requiring or strongly encouraging a dedicated browser profile containing only QQ Music. 5. Consider disabling the `tabs` action by default and requiring an explicit diagnostic option to expose even QQ Music target metadata. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
qq-music-ctl.js:848
Finding
Arbitrary Overwrite of User-Writable Files Through Screenshot Path<![CDATA[ ## Vulnerability Details **File Location**: `qq-music-ctl.js`, lines 848–861 **Vulnerability Type**: Unrestricted file path and destructive file overwrite **Risk Level**: Medium ### Vulnerable Code ```js async function actionScreenshot(pathArg) { const entry = await discoverEndpoint(); const target = firstTarget(pageTargets(entry), isPlayerTarget) || firstTarget(pageTargets(entry), isBrowseTarget); if (!target) return output({ error: 'No QQ Music tab found.' }); if (!isAllowedDomain(target.url)) return output({ error: `Refusing to screenshot non-QQ-Music tab: ${target.url}` }); const session = await pageSession(target); try { await sleep(1000); const result = await session.send('Page.captureScreenshot', { format: 'png' }); const outPath = pathArg || SCREENSHOT_PATH; const buf = Buffer.from(result.data, 'base64'); fs.writeFileSync(outPath, buf); output({ ok: true, path: outPath, bytes: buf.length }); } finally { session.close(); } } ``` ### Technical Analysis The optional screenshot path is taken directly from the command-line arguments and passed to `fs.writeFileSync` without validation or confinement. The implementation accepts: - Absolute paths - Relative paths containing directory traversal - Paths outside the project directory - Existing files of any type - Filenames without a `.png` extension By default, `fs.writeFileSync` truncates an existing file before writing. A caller can therefore replace any file writable by the Node.js process with PNG data. Allowing the user to select a screenshot destination may be legitimate, but unrestricted overwrite access exceeds the minimum privileges needed to save a QQ Music screenshot. The action should be confined to a dedicated output directory or require explicit authorization for paths outside that directory. ### Attack Path 1. An Agent or local caller identifies a sensitive file writable by the Skill's OS user. 2. The caller invokes the screenshot acti ...[truncated 1356 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Confine screenshots to a dedicated directory and reject path traversal: ```js const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); function resolveScreenshotPath(pathArg) { fs.mkdirSync(SCREENSHOT_DIR, { recursive: true, mode: 0o700 }); const fileName = path.basename(pathArg || SCREENSHOT_PATH); if (!fileName.toLowerCase().endsWith('.png')) { throw new Error('Screenshot output must use a .png extension'); } const resolved = path.resolve(SCREENSHOT_DIR, fileName); const root = path.resolve(SCREENSHOT_DIR) + path.sep; if (!resolved.startsWith(root)) { throw new Error('Screenshot path escapes the allowed directory'); } return resolved; } ``` Use exclusive file creation to prevent silent replacement: ```js const outPath = resolveScreenshotPath(pathArg); fs.writeFileSync(outPath, buf, { flag: 'wx', mode: 0o600 }); ``` Additional hardening measures: 1. Generate unique filenames when no path is supplied. 2. Reject absolute paths and path components containing traversal sequences. 3. Require explicit confirmation before replacing an existing screenshot. 4. If overwriting is supported, expose it through a separate clearly named option such as `--overwrite`. 5. Create output files with restrictive permissions. 6. Return a path relative to the Skill-owned screenshot directory rather than unnecessarily exposing an absolute filesystem path. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Ae1

High
Category
analysis-evasion
Content
1. **`.cdp-port` file** — a single-line file in the same directory as `qq-music-ctl.js`, containing just the port number (e.g. `19011`).
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill explicitly performs network-capable actions by connecting to a local Chrome DevTools Protocol endpoint over HTTP/WebSocket, but the manifest declares no tool scope such as permissions or allowed-tools. Even though the target is localhost and the documentation describes guardrails, this still grants a powerful browser-control channel that can execute JavaScript in a browser tab and should be transparently declared. In this context the risk is somewhat reduced by the documented y.qq.com allowlist and localhost-only design, but CDP remains high-privilege and the missing declaration weakens policy visibility and enforcement.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The skill relies on Chinese UI labels such as '列表循环', '单曲循环', '随机播放', and '顺序循环', and elsewhere matches Chinese text like '播放全部', which implies operation depends on a specific locale. There is no natural-language indication that the skill is limited to Chinese-language QQ Music pages or any user opt-in for locale selection.

Description-Behavior Mismatch

Low
Confidence
95% confidence
Finding
The manifest describes browser automation for playback, search, likes, playlists, and browser-target discovery, but it does not mention capturing and writing screenshots to disk. While adjacent to browser automation, persistent screenshot capture is a distinct behavior beyond the stated feature set.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The screenshot action captures page contents from the QQ Music tab and writes them to a local file via fs.writeFileSync, but there is no explicit warning in the command help or any confirmation before saving. Because this operation persists potentially sensitive account or media information to disk, it should be disclosed to the user.

Static analysis

No suspicious patterns detected.