Back to skill

Security audit

browser-testing

Security checks for vulnerabilities and agentic risk

Overview

This browser-testing skill is coherent and purpose-aligned, but users should be careful because it can load supplied URLs and leaves flicker screenshots in temporary files.

Use this skill only against local or authorized web apps. Avoid running the flicker check on pages displaying sensitive data unless you are comfortable with /tmp/flicker-early.png and /tmp/flicker-final.png remaining on disk, and prefer pinned local dependencies or npx --no-install rather than runtime package resolution.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
detect-flicker.ts:39
Finding
CLI-Controlled JavaScript Injection in Browser Initialization Script## Vulnerability Details **File Location**: `detect-flicker.ts`, lines 39–44 and 99–102 **Vulnerability Type**: JavaScript injection through unsafe string interpolation **Risk Level**: Medium ### Vulnerable Code ```typescript async function detectFlicker(url: string, expectedTheme: string = 'dark'): Promise<FlickerResult> { const browser = await chromium.launch(); const context = await browser.newContext(); // Set theme preference BEFORE page loads await context.addInitScript(`localStorage.setItem('theme', '${expectedTheme}');`); ``` The interpolated value originates from a command-line argument: ```typescript // Main const url = process.argv[2] || 'http://localhost:3000'; const theme = process.argv[3] || 'dark'; detectFlicker(url, theme) ``` ### Technical Analysis The `expectedTheme` value is incorporated directly into a string containing executable JavaScript. It is not escaped or restricted to the intended values, such as `dark` and `light`. A malicious argument containing a quote and additional JavaScript can terminate the string passed to `localStorage.setItem` and append arbitrary statements. Playwright then installs the resulting source as an initialization script using `context.addInitScript`. That script executes whenever a page is created or navigated within the browser context. Because the injected script runs in the target page's browser context, it receives the origin-level capabilities available to ordinary page JavaScript. The vulnerability does not directly grant operating-system command execution, but it can expose page content and origin-accessible data. ### Attack Path 1. An attacker obtains control over, or influences, the third command-line argument passed to `detect-flicker.ts`. 2. The attacker supplies a value that closes the quoted theme value and appends JavaScript. 3. The value is inserted into the initialization-script source without validation or escaping. ...[truncated 1208 chars]
Remediation
## Remediation Suggestions Do not construct executable JavaScript by concatenating or interpolating untrusted values. Pass the theme as a structured Playwright argument: ```typescript await context.addInitScript( theme => localStorage.setItem('theme', theme), expectedTheme ); ``` In addition, enforce an explicit allowlist before creating the browser context: ```typescript type Theme = 'dark' | 'light'; function parseTheme(value: string): Theme { if (value !== 'dark' && value !== 'light') { throw new Error('Theme must be either "dark" or "light"'); } return value; } const theme = parseTheme(process.argv[3] || 'dark'); ``` Apply both controls: the allowlist enforces the intended interface, while structured argument passing prevents source-code injection if accepted values are expanded later.

T09 · Insecure Skill Coding Practices

Note
Location
detect-flicker.ts:51
Finding
Predictable Screenshot Files in Shared Temporary Directory## Vulnerability Details **File Location**: `detect-flicker.ts`, lines 51–60 **Vulnerability Type**: Unsafe temporary-file handling **Risk Level**: Low ### Vulnerable Code ```typescript // Take early screenshot and save const earlyPath = '/tmp/flicker-early.png'; const earlyScreenshot = await page.screenshot({ path: earlyPath }); // Wait for full load await page.waitForLoadState('networkidle'); await page.waitForTimeout(500); // Take final screenshot and save const finalPath = '/tmp/flicker-final.png'; const finalScreenshot = await page.screenshot({ path: finalPath }); ``` ### Technical Analysis The script writes screenshots to two fixed, globally predictable paths under `/tmp`. Shared temporary directories may be accessible to other local users or processes. Reusing static names creates the following risks: - Concurrent executions overwrite each other's screenshots. - Existing filesystem objects at the predictable paths can interfere with writes. - Screenshots remain on disk after successful or failed execution. - A different local process may monitor or retrieve the generated images. - Results can reference screenshots produced by another run. The in-memory buffers returned by `page.screenshot` are sufficient for brightness analysis, so writing these images to a shared directory is not required for the core measurement operation. ### Attack Path 1. A local attacker predicts the paths `/tmp/flicker-early.png` and `/tmp/flicker-final.png`. 2. The attacker monitors those paths, causes file collisions, or pre-creates filesystem objects where platform permissions permit. 3. A user runs the flicker detector against an internal, authenticated, or otherwise sensitive page. 4. Playwright writes viewport images to the predictable locations. 5. The attacker retrieves the retained screenshots, disrupts their creation, or causes artifacts from concurrent runs to be mixed. ### Impact Assessment The pr ...[truncated 615 chars]
Remediation
## Remediation Suggestions If persistent screenshots are unnecessary, omit the `path` option and analyze only the returned buffers: ```typescript const earlyScreenshot = await page.screenshot(); const finalScreenshot = await page.screenshot(); ``` If artifacts must be retained: 1. Create a unique private directory with `fs.promises.mkdtemp`. 2. Use the operating system's temporary-directory location from `os.tmpdir()`. 3. Apply restrictive directory and file permissions. 4. Generate unique filenames for every execution. 5. Remove files and the temporary directory in a `finally` block unless retention was explicitly requested. 6. Avoid following pre-existing symbolic links or writing over existing objects. Example pattern: ```typescript import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; const artifactDir = await mkdtemp(join(tmpdir(), 'flicker-')); try { const earlyPath = join(artifactDir, 'early.png'); const finalPath = join(artifactDir, 'final.png'); const earlyScreenshot = await page.screenshot({ path: earlyPath }); const finalScreenshot = await page.screenshot({ path: finalPath }); } finally { await rm(artifactDir, { recursive: true, force: true }); } ```

T08 · Insecure Dependencies

Note
Location
SKILL.md:17
Finding
Unpinned Runtime Tool Execution Through npx## Vulnerability Details **File Location**: `SKILL.md`, lines 17–22; related commands at lines 106, 136, and 154–157 **Vulnerability Type**: Unsafe and non-reproducible dependency resolution **Risk Level**: Low ### Vulnerable Code ```bash # Measure a page (outputs JSON with waterfall data) npx ts-node <path-to-this-skill>/measure.ts http://localhost:3000 # Measure an API endpoint npx ts-node <path-to-this-skill>/measure.ts http://localhost:3000/api/products ``` Additional documented invocations use the same pattern: ```bash npx ts-node <path-to-this-skill>/measure-cls.ts http://localhost:3000 ``` ```bash npx ts-node <path-to-this-skill>/detect-flicker.ts http://localhost:3000 ``` ```bash npx ts-node <path-to-this-skill>/measure-cls.ts http://localhost:3000 npx ts-node <path-to-this-skill>/measure-cls.ts http://localhost:3000 --scroll ``` ### Technical Analysis The project does not include a reviewed package manifest or lockfile pinning `ts-node`, `playwright`, and `pngjs`. The documented commands invoke `ts-node` through `npx` without a no-install restriction. If a suitable local `ts-node` executable is unavailable, behavior depends on the installed npm/npx version and configuration. `npx` may resolve and download a package from the configured registry before executing it. This creates a mutable runtime dependency on external package resolution rather than a reproducible, previously reviewed dependency set. The audit did not identify a malicious package in the project. The finding concerns the unsafe dependency-execution mechanism and the absence of version pinning. ### Attack Path 1. A user follows the documented `npx ts-node` command in an environment where `ts-node` is not already installed locally. 2. `npx` resolves the requested executable using the configured package registry. 3. A compromised registry, poisoned configuration, compromised package rel ...[truncated 1004 chars]
Remediation
## Remediation Suggestions 1. Add a project manifest declaring all required dependencies. 2. Pin reviewed versions of `ts-node`, `playwright`, `pngjs`, TypeScript, and relevant type packages. 3. Commit the generated lockfile. 4. Install dependencies through a controlled, reproducible process such as `npm ci`. 5. Prevent `npx` from downloading missing packages at execution time. 6. Use a trusted registry and dependency-integrity monitoring. A hardened invocation is: ```bash npx --no-install ts-node ./measure.ts http://localhost:3000 ``` Prefer package scripts backed by locked local dependencies: ```json { "scripts": { "measure": "ts-node ./measure.ts", "measure:cls": "ts-node ./measure-cls.ts", "detect:flicker": "ts-node ./detect-flicker.ts" } } ``` Then install with `npm ci` and invoke the appropriate script through the package manager.
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 (3)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill metadata advertises narrowly scoped verification behavior, but the content also introduces a generic measurement script and broader network timing workflow that are not cleanly aligned with the declared purpose. This mismatch can mislead operators into granting trust or running the skill under assumptions that do not match its actual behavior, which is dangerous because it normalizes execution of broader network-capable code than expected.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill instructs the agent to run network-capable scripts against arbitrary URLs, but the manifest declares no explicit tool scope or permissions. In an agent ecosystem, this creates an authorization gap where reviewers and policy layers cannot easily tell that the skill can initiate outbound requests and probe local or remote services, increasing the risk of unintended access or SSRF-style misuse.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script saves screenshots of arbitrary pages to fixed paths under /tmp, which can expose sensitive page contents such as tokens, personal data, or internal application state to other local users, processes, or later forensic recovery. Because the files are written without notice and persist on disk outside the process memory, this creates an unintended information disclosure risk in shared environments, CI runners, or multi-user systems.

Static analysis

No suspicious patterns detected.