Back to skill

Security audit

lock-me-in

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it exposes a logged-in browser through an unauthenticated public link and stores reusable login sessions in ways that need careful review.

Only install this if you are comfortable with a temporary public, unauthenticated URL controlling a browser that may contain live accounts. Do not use it for high-value accounts unless the tunnel is protected, the /eval endpoint is removed or strongly authenticated, typed secrets are not sent in URLs, and saved session files are restricted, encrypted, and easy to delete.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (7)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/browser-login.mjs:369
Finding
Unauthenticated Public Control of a Privileged Browser Session<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-login.mjs:369-371, 408-419` **Vulnerability Type**: Missing authentication and authorization on a publicly tunneled browser-control service **Risk Level**: Critical ### Vulnerable Code ```javascript server.listen(proxyPort, '0.0.0.0', () => { console.log(`🖥️ Server on port ${proxyPort}`); }); // --- Cloudflared Tunnel --- console.log('🚇 Starting tunnel...'); const tunnel = spawn( CLOUDFLARED, ['tunnel', '--url', `http://localhost:${proxyPort}`], { stdio: ['ignore', 'pipe', 'pipe'] } ); let tunnelUrl = null; const onData = (data) => { const m = data.toString().match(/(https:\/\/[a-z0-9-]+\.trycloudflare\.com)/); if (m && !tunnelUrl) { tunnelUrl = m[1]; console.log(`\n🔗 LOGIN URL: ${tunnelUrl}\n`); } }; ``` ### Technical Analysis The HTTP server listens on every local interface and is then exposed through a public Cloudflare tunnel. No endpoint requires authentication or authorization. The implementation also lacks request-origin validation, CSRF defenses, session-bound capability tokens, and rate limiting. The tunnel hostname is random, but possession or discovery of a URL is not a sufficient substitute for authentication. The URL can leak through chat history, process logs, terminal output, shell-management systems, browser history, screenshots, or monitoring infrastructure. This exceeds the minimum privileges needed to facilitate remote login because every person who can reach the URL receives the same browser-control privileges as the intended user. ### Attack Path 1. The Skill launches the local HTTP service and publishes it through Cloudflare. 2. The tunnel URL is exposed through logs or the messaging channel used to send it to the user. 3. An unauthorized party obtains the URL. 4. The party requests `/screenshot` to inspect the browser's current state. 5. The party invokes endpoints such as `/click`, `/type`, `/navigate`, `/save`, or `/done`. 6. The pa ...[truncated 469 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require a cryptographically random, single-use bearer token on every HTTP endpoint. - Bind authorization to the intended session and invalidate it immediately when the session ends. - Listen only on `127.0.0.1` rather than `0.0.0.0` when Cloudflared connects locally. - Require POST requests for all state-changing operations. - Validate the `Origin` header and implement CSRF protection. - Add request throttling and lockout controls. - Avoid printing the complete access credential in broadly accessible logs. - Prefer an authenticated Cloudflare Access configuration over an anonymous quick tunnel. - Display a list of active controllers and terminate the session if an unexpected client connects. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/browser-login.mjs:332
Finding
Undocumented Arbitrary JavaScript Execution in the Active Browser Page<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-login.mjs:332-341` **Vulnerability Type**: Arbitrary script execution through an unauthenticated control endpoint **Risk Level**: Critical ### Vulnerable Code ```javascript case '/eval': // Execute JS on the page (for clicking tricky elements, etc.) const script = url.searchParams.get('js'); try { const result = await page.evaluate(script); json(res, { status: `✓ eval: ${JSON.stringify(result)}`.slice(0, 200) }); } catch (e) { json(res, { status: `✗ eval error: ${e.message}`.slice(0, 200) }); } break; ``` ### Technical Analysis The `/eval` endpoint passes arbitrary caller-controlled text directly to Playwright's `page.evaluate`. The code executes in the JavaScript context of the currently displayed page and therefore has access to that origin's DOM, web storage, and same-origin application interfaces. This capability is not documented among the Web UI controls and is not necessary for the declared visual-login workflow. Because the public control service has no authentication, any person able to reach the tunnel can use this endpoint. Although browser-origin restrictions still apply, injected code can perform any action available to ordinary JavaScript on the current authenticated origin. The endpoint also returns a portion of the evaluation result, creating a direct extraction channel. ### Attack Path 1. The victim completes login or opens a sensitive page through the Skill. 2. An attacker obtains access to the public tunnel. 3. The attacker submits a request such as `/eval?js=...`. 4. The supplied JavaScript executes inside the authenticated page. 5. The script reads DOM content or localStorage, invokes same-origin APIs, or submits account-changing operations. 6. Results can be returned through the endpoint, observed in screenshots, or sent by the page to an attacker-controlled server where browser policy permits. ### Impact Assessment The attacker receives ...[truncated 315 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `/eval` endpoint entirely. - Replace general-purpose evaluation with narrowly scoped, allowlisted operations. - Validate every operation's arguments against strict schemas. - Require strong per-session authentication and authorization even for allowlisted controls. - Do not return arbitrary page data through control responses. - Maintain an explicit endpoint inventory and ensure every exposed operation is documented and security-reviewed. - Add automated tests confirming that unknown or privileged operations cannot be invoked remotely. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/browser-login.mjs:316
Finding
Unrestricted Navigation Enables Browser-Based SSRF<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-login.mjs:316-318` **Vulnerability Type**: Server-side request forgery through unrestricted browser navigation **Risk Level**: High ### Vulnerable Code ```javascript case '/navigate': await page.goto(url.searchParams.get('url'), { waitUntil: 'domcontentloaded', timeout: 15000 }); json(res, { status: `→ ${url.searchParams.get('url')}` }); break; ``` ### Technical Analysis The navigation endpoint accepts a caller-supplied URL without validating its scheme, hostname, resolved address, port, or redirect destination. The server-side browser can consequently be directed to resources reachable from the host but inaccessible to the remote caller. Potential targets include loopback services, private network hosts, link-local endpoints, and cloud instance metadata services. Responses can be inspected through the screenshot endpoint or processed through the arbitrary `/eval` endpoint. Filtering only the initially supplied hostname would be insufficient because DNS resolution and HTTP redirects can move navigation to prohibited addresses. ### Attack Path 1. An attacker obtains access to the public tunnel. 2. The attacker calls `/navigate` with a loopback, private, link-local, or metadata-service URL. 3. Chromium makes the request from the server's network context. 4. The attacker requests `/screenshot` or invokes `/eval` to inspect the response. 5. The attacker iterates through addresses and ports to discover services. 6. If an internal service accepts unauthenticated or browser-originated operations, the attacker reads data or performs internal actions. ### Impact Assessment Successful exploitation can expose internal administrative interfaces, service metadata, network topology, cloud credentials, or application data. The exact privilege depends on the host's network access, but the endpoint turns the browser into a remotely controlled network pivot. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Restrict navigation to an explicit allowlist of required HTTPS hostnames. - Reject all schemes other than `https`. - Resolve hostnames and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges. - Revalidate every redirect destination and resolved address. - Account for IPv4, IPv6, alternative address representations, and DNS rebinding. - Block access to cloud metadata endpoints at the host firewall or network layer. - Consider fixing the target origin at process startup and removing remote navigation entirely. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/browser-login.mjs:223
Finding
Passwords and MFA Codes Are Transmitted in URL Query Strings and Echoed<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-login.mjs:223, 307-309` **Vulnerability Type**: Sensitive-data exposure through GET query parameters and response content **Risk Level**: High ### Vulnerable Code ```javascript async function doType() { const el = document.getElementById('input'); const text = el.value; if (!text) return; const r = await fetch('/type?text=' + encodeURIComponent(text)); const j = await r.json(); el.value = ''; setStatus(j.status); setTimeout(refresh, 400); } ``` ```javascript case '/type': await page.keyboard.type(url.searchParams.get('text'), { delay: 30 }); json(res, { status: `Typed "${url.searchParams.get('text')}"` }); break; ``` ### Technical Analysis All remote keyboard input is placed in the query string of a GET request. During login this input is expected to include usernames, passwords, recovery information, and MFA codes. URLs are commonly captured by reverse proxies, tunnel providers, browser history, diagnostics, telemetry, and access logs. The server then includes the same sensitive value in its JSON response, causing the browser UI to display it in the status field. Transport encryption protects data in transit from passive network observers but does not prevent URL logging by endpoints or intermediaries. ### Attack Path 1. The user enters a password or one-time code in the remote Web UI. 2. Client-side JavaScript sends it as `/type?text=<secret>`. 3. The complete URL traverses the Cloudflare tunnel and reaches the local server. 4. Any intermediary or local component that records request URLs stores the secret. 5. The server echoes the value in its response and the UI displays it. 6. A party with access to logs, diagnostics, browser history, or the active tunnel recovers the secret. ### Impact Assessment Exposed credentials can enable direct account compromise. MFA-code exposure may permit real-time login takeover, while password disclosure can affect other accou ...[truncated 144 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Send keyboard input through an authenticated POST request body. - Never include typed content in response messages, logs, exceptions, or telemetry. - Return a generic status such as `Input sent`. - Apply `Cache-Control: no-store` and related no-cache headers to all control responses. - Configure Cloudflare and local HTTP logging to redact request bodies and authorization values. - Consider a protected streaming channel with explicit session authentication. - Clear sensitive client-side input immediately after the request is accepted. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/browser-login.mjs:275
Finding
Reusable Authentication State Is Persisted Without Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-login.mjs:275-277, 454-462` **Vulnerability Type**: Insecure local storage of session bearer credentials **Risk Level**: High ### Vulnerable Code ```javascript const userDataDir = path.join(sessionDir, 'chrome-profile'); fs.mkdirSync(userDataDir, { recursive: true }); // Clean stale locks from previous runs for (const f of ['SingletonLock', 'SingletonCookie', 'SingletonSocket']) { try { fs.unlinkSync(path.join(userDataDir, f)); } catch {} } ``` ```javascript async function saveSession(context, storageFile, metaFile, sessionName, currentUrl) { const state = await context.storageState(); fs.writeFileSync(storageFile, JSON.stringify(state, null, 2)); fs.writeFileSync(metaFile, JSON.stringify({ session: sessionName, lastUrl: currentUrl, savedAt: new Date().toISOString(), cookieCount: state.cookies?.length || 0 }, null, 2)); console.log(`💾 Saved: ${state.cookies?.length || 0} cookies`); } ``` ### Technical Analysis The Skill deliberately persists cookies and localStorage, which can contain reusable bearer credentials. Directories and files are created without explicit restrictive modes, leaving their effective permissions dependent on the process umask. Under a common `022` umask, newly created files may be readable by other local users. The complete persistent Chromium profile is also retained. It can contain additional browser state beyond the exported Playwright storage file. No encryption, explicit expiration, secure deletion, or retention control is implemented. ### Attack Path 1. The victim uses the Skill to authenticate to a website. 2. The Skill writes `storage.json` and a persistent Chromium profile to the session directory. 3. Another local account or compromised process searches the documented session location. 4. If filesystem permissions permit, it copies the session state. 5. The attacker imports the cookies and storage into a compatible browser c ...[truncated 375 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the session root and all per-session directories with mode `0700`. - Create credential-bearing files with mode `0600`. - Use atomic creation and replacement to avoid permission and partial-write races. - Verify ownership and permissions before loading an existing session. - Encrypt stored session material using an OS credential store or a key unavailable to other users. - Minimize stored state and avoid retaining a complete Chromium profile unless strictly necessary. - Implement explicit expiration, revocation, listing, and secure deletion commands. - Warn users that copied session cookies can bypass passwords and MFA. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/browser-login.mjs:96
Finding
Unvalidated Session Name Allows Path Traversal Outside the Session Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-login.mjs:96-104` **Vulnerability Type**: Filesystem path traversal **Risk Level**: Medium ### Vulnerable Code ```javascript const args = process.argv.slice(2); const targetUrl = args.find(a => a.startsWith('http')) || 'https://example.com'; const sessionName = args.find(a => !a.startsWith('http') && !a.startsWith('--')) || 'default'; const portArg = args.find(a => a.startsWith('--port=')); const timeoutArg = args.find(a => a.startsWith('--timeout=')); const proxyPort = portArg ? parseInt(portArg.split('=')[1]) : DEFAULT_PORT; const timeout = timeoutArg ? parseInt(timeoutArg.split('=')[1]) * 1000 : DEFAULT_TIMEOUT; const sessionDir = path.join(SESSIONS_DIR, sessionName); const storageFile = path.join(sessionDir, 'storage.json'); const metaFile = path.join(sessionDir, 'meta.json'); ``` ### Technical Analysis The session name is accepted directly from a command-line argument and joined to the configured session root without validation. Values containing `..` path components or path separators can cause the normalized destination to escape the intended directory. The Skill later creates the resulting directory and writes Chromium profile data, `storage.json`, and `meta.json` beneath it. Exploitation requires influence over process arguments, but this may occur when an agent constructs the command from user-supplied session names. The attacker cannot choose arbitrary file contents or arbitrary filenames through this issue alone, but can redirect predictable Skill-generated files to unintended locations where the process has write permission. ### Attack Path 1. An attacker supplies a session name containing traversal components, such as `../../chosen-directory`. 2. The invoking agent passes the value to the script without sanitization. 3. `path.join` normalizes the traversal outside `SESSIONS_DIR`. 4. The Skill creates a Chromium profile and writes session files in the unintended director ...[truncated 488 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Restrict session names to a conservative format such as `^[A-Za-z0-9_-]{1,64}$`. - Reject absolute paths, path separators, dot components, control characters, and empty names. - Resolve both the session root and candidate path with `path.resolve`. - Verify that the candidate starts with the resolved root followed by the platform path separator. - Avoid accepting session identifiers directly from untrusted natural-language input without validation. - Refuse to load or overwrite unexpected symbolic links and verify destination ownership. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:71
Finding
Installation Instructions Execute Unpinned and Unverified Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:71-75` **Vulnerability Type**: Unsafe third-party dependency installation **Risk Level**: Medium ### Vulnerable Code ```markdown ## Requirements - Playwright-compatible Chromium (installed via `npx playwright install chromium`) - `cloudflared` binary for tunneling (install: `curl -sL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared`) - Node.js 18+ ``` ### Technical Analysis The instructions use `npx` without an explicitly pinned package version and download Cloudflared from a mutable `latest` URL. The binary is made executable without checksum or signature verification. The referenced domains are recognizable upstream sources, and the reviewed project does not itself contain evidence that those sources are malicious. Nevertheless, the installation procedure does not establish reproducible artifact identity or integrity. A compromised registry, release artifact, account, or distribution path could therefore introduce code different from that reviewed. Writing the downloaded binary to `/usr/local/bin` may also require elevated privileges, increasing the impact of an altered artifact. ### Attack Path 1. A user follows the documented dependency-installation instructions. 2. `npx` resolves an unpinned package or the mutable Cloudflared URL resolves to the current release artifact. 3. An upstream package, release account, or artifact is compromised or substituted. 4. No checksum or signature check detects the altered content. 5. The downloaded component executes locally with the installing user's privileges, potentially including elevated privileges. ### Impact Assessment A compromised dependency could execute arbitrary local code, steal browser sessions, alter the tunnel, or take control of the host within the installer's permission scope. The issue is a supply-chain hardening weaknes ...[truncated 63 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin exact Playwright and Chromium versions in a lockfile. - Avoid implicit package retrieval through unversioned `npx` commands. - Pin an exact Cloudflared release URL. - Verify the vendor-published cryptographic checksum or signature before installation. - Fail closed if verification does not succeed. - Install through an approved package manager or reproducible build process where available. - Avoid downloading directly into privileged executable paths; verify in a restricted temporary location first. - Document supported versions and an explicit, reviewed update procedure. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill prominently describes creating a temporary public URL for interactive login but does not place a clear up-front warning in the description that this exposes live authenticated browser access over a publicly reachable tunnel with no authentication by default. In this context, missing consent and risk signaling is especially dangerous because compromise of the URL can enable account takeover or leakage of sensitive authenticated data during the session.

Missing User Warnings

High
Confidence
95% confidence
Finding
The skill creates a public cloudflared URL to a live browser-control interface and persists resulting session state to disk, but it does not provide an explicit security warning or visible access-control mechanism. Because the interface exposes screenshots, input, navigation, and save operations for authenticated sessions, accidental sharing or interception of the URL can lead to session hijacking and privacy compromise.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documents environment-variable use, network tunneling, and browser/session handling but declares no explicit tool scope or permissions boundary. In an agent ecosystem, that mismatch can lead to over-broad invocation and execution with capabilities the user or orchestrator did not clearly approve, increasing the chance of unintended network exposure and credential handling.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The description includes broad triggers such as 'log me in' and generic authenticated browsing needs, which could cause the agent to invoke a high-risk skill in situations where the user did not fully intend remote browser sharing or session persistence. Because this skill exposes authenticated browser control through a public tunnel and stores session state, accidental invocation materially increases security risk.

Session Persistence

Medium
Category
Rogue Agent
Content
3. Cloudflared creates a temporary public tunnel URL
4. User opens the link, clicks/types to log in
5. Session (cookies + localStorage) saved to disk
6. Future Playwright sessions load the saved state

## Quick Start
Confidence
94% confidence
Finding
Persisting cookies and localStorage to disk for future automated reuse creates durable bearer credentials that can be stolen or misused by any process or user with filesystem access. In this skill's context, the stored data represents authenticated sessions for third-party services, so exposure can directly enable unauthorized account access without re-entering credentials or MFA.

Session Persistence

Medium
Category
Rogue Agent
Content
Run in background with nohup, capture the tunnel URL from stdout:
```bash
nohup node <skill-dir>/scripts/browser-login.mjs <url> <name> > /tmp/lock-me-in.log 2>&1 &
# Wait for URL:
grep -m1 'LOGIN URL' /tmp/lock-me-in.log
```
Confidence
83% confidence
Finding
Running the login process under `nohup` and writing the tunnel URL to a predictable log file in `/tmp` increases the chance that other local users or processes can discover the public login link while the session is active. Given that the tunnel has no authentication by default, disclosure of that URL can grant live interactive access to the browser during login and session establishment.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The injected stealth script hard-codes navigator.languages to ['en-US', 'en', 'de'], which forces a specific locale presentation regardless of user preference. This is a natural-language policy issue because the skill does not offer locale choice or document a justified region-specific requirement.

Natural-Language Policy Violations

Medium
Confidence
99% confidence
Finding
The browser is launched with locale 'en-US' and '--lang=en-US,en', imposing a specific language/locale configuration. The file provides no user opt-in or documented justification for this constraint, so it conflicts with the stated language/locale policy.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The /eval endpoint allows anyone who can reach the exposed HTTP service to execute arbitrary JavaScript inside the authenticated browser context. In this skill, that context is specifically intended to hold live login sessions and persisted cookies, so arbitrary script execution can drive account actions, read page data, and abuse the session far beyond simple user-assisted login.

Context-Inappropriate Capability

Low
Confidence
79% confidence
Finding
The manifest centers on an interactive visual login flow through screenshots, clicks, typing, and session persistence. Adding a semantic automation endpoint that locates and clicks elements by text turns the skill into a more general remote web automation interface rather than just a login handoff mechanism.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/browser-login.mjs:419

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/browser-login.mjs:69