Back to skill

Security audit

Max Auth

Security checks for vulnerabilities and agentic risk

Overview

This auth skill is intended to protect sensitive actions, but the shipped server does not enforce several protections it advertises.

Do not use this skill to protect real sensitive actions or collect real credentials until sessionKey isolation, returnUrl token handling, secret API authorization, CORS, XSS-safe rendering, and dependency locking are fixed and retested.

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 (6)

T09 · Insecure Skill Coding Practices

Error
Location
assets/auth-server.js:821
Finding
Authentication Token Disclosure Through an Arbitrary Redirect<![CDATA[ ## Vulnerability Details **File Location**: `assets/auth-server.js`, lines 821–850 **Vulnerability Type**: Unvalidated redirect with session-token disclosure **Risk Level**: Critical ### Vulnerable Code ```javascript // ---- If already authenticated: auto-redirect to returnUrl with token ---- (function() { const sessionToken = ${session ? JSON.stringify(session.token) : 'null'}; if (sessionToken) { const params = new URLSearchParams(window.location.search); const returnUrl = params.get('returnUrl'); if (returnUrl) { const dest = new URL(decodeURIComponent(returnUrl)); dest.searchParams.set('max_auth_token', sessionToken); window.location.href = dest.toString(); return; } } })(); // ---- Post-login: set cookie + redirect ---- function handleLoginSuccess(token) { const params = new URLSearchParams(window.location.search); const returnUrl = params.get('returnUrl'); if (returnUrl) { // Pass token via query param so the target proxy can set the cookie server-side const dest = new URL(decodeURIComponent(returnUrl)); dest.searchParams.set('max_auth_token', token); window.location.href = dest.toString(); } else { setTimeout(() => location.reload(), 500); } } ``` ### Technical Analysis The `returnUrl` query parameter is controlled by the requester. The application neither validates its origin nor restricts it to approved callback paths before appending a valid authentication token. This occurs both when the user already has a session and immediately after successful authentication. Consequently, a URL supplied by an attacker can cause the browser to transmit the token to an arbitrary external host. Placing the token in a query string also exposes it to destination-server access logs, browser history, monitoring infrastructure, reverse proxies, and potentially referrer data. The network request flagged at line 860 posts the password to the relative same-origin `/auth/login` endpoi ...[truncated 1285 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not transmit session or bearer tokens in URLs. - Remove support for arbitrary `returnUrl` destinations. - Configure an allowlist containing exact HTTPS origins and callback paths; reject every other destination. - Replace direct token forwarding with a cryptographically random, short-lived, single-use authorization code. - Bind the code to the intended callback URI, requesting session, and expiration time. - Exchange the code for a session through a server-to-server request. - Set the resulting session in a `Secure`, `HttpOnly`, and appropriate `SameSite` cookie. - Add tests covering external origins, protocol-relative URLs, encoded URLs, user-info URL tricks, and alternate ports. - Revoke existing tokens after deploying the fix because previously issued tokens may have leaked. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/auth-server.js:250
Finding
Declared Per-Channel Session Isolation Is Not Implemented<![CDATA[ ## Vulnerability Details **File Location**: `assets/auth-server.js`, lines 250–266 and 361–377 **Vulnerability Type**: Global authorization state used for session-scoped access decisions **Risk Level**: High ### Vulnerable Code ```javascript function createSession(ip) { ensureConfigDir(); const token = generateToken(); const session = { token, createdAt: Date.now(), expiresAt: Date.now() + SESSION_DURATION_MS, ip }; fs.writeFileSync( SESSION_FILE, JSON.stringify(session), { mode: 0o600 } ); log(`✓ Session created for IP: ${ip}`); return token; } function getSession() { try { if (fs.existsSync(SESSION_FILE)) { const s = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8')); if (Date.now() < s.expiresAt) return s; } } catch {} return null; } function isValidSession(token) { const s = getSession(); return s && s.token === token; } function resolveAuthStatus(sessionKey = 'global') { const session = getSession(); if (session) { return { hasSession: true, expiresAt: session.expiresAt, source: 'session', sessionKey: canonicalSessionKey(sessionKey) }; } const grant = getGrantStatus(sessionKey); if (grant) { return { hasSession: true, expiresAt: grant.expiresAt, source: 'grant', sessionKey: grant.childSessionKey, grant }; } return { hasSession: false, expiresAt: null, source: null, sessionKey: canonicalSessionKey(sessionKey) }; } ``` The login handler also ignores the documented `sessionKey`: ```javascript readBody(req).then(({ password }) => { // ... if (valid) { const token = createSession(ip); auditLog('login', ip, true); sendJSON(res, 200, { success: true, token, expiresAt: Date.now() + SESSION_DURATION_MS }); } }); ``` ### Technical Analysis The documentation declares independent authentication per channel/session key. In practice, the ser ...[truncated 1453 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store sessions in a map or database indexed by a validated canonical session key. - Include the canonical session key inside every session record. - Bind each generated token cryptographically and logically to exactly one session key. - Require `sessionKey` during login and reject missing, malformed, or unsupported values. - Require the same key during status checks and token verification. - Verify both the token and its bound session key before returning an authenticated result. - Ensure logout invalidates only the intended scoped session unless an explicit global logout is requested. - Make delegated grants reference immutable session identifiers and validate the authenticated parent token. - Add cross-channel tests proving that authentication for one Telegram, Discord, or WhatsApp session cannot authorize another. - Update the API documentation only after implementation and documentation agree. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/auth-server.js:574
Finding
Secret Collection and Retrieval Endpoints Lack Authentication<![CDATA[ ## Vulnerability Details **File Location**: `assets/auth-server.js`, lines 574–644 **Vulnerability Type**: Missing authorization on sensitive secret-management APIs **Risk Level**: High ### Vulnerable Code ```javascript if ((p === '/auth/secrets' || p === '/secrets' || p === '/auth/secrets/create' || p === '/secrets/create') && req.method === 'POST') { readBody(req).then(({ label, fields, sessionKey, session_key, expires_in_minutes }) => { if (!label || !Array.isArray(fields) || fields.length === 0) { sendJSON(res, 400, { error: 'label and fields are required' }); return; } const expireMin = Math.min( Math.max(Number(expires_in_minutes) || 30, 1), 1440 ); const token = generateToken(); const entry = { token, label: String(label), fields: fields.map(f => ({ name: String(f.name), label: String(f.label || f.name), type: ['password', 'textarea'].includes(f.type) ? f.type : 'text' })), sessionKey: sessionKey || session_key || null, expiresAt: Date.now() + expireMin * 60 * 1000, submitted: false, values: null, consumed: false, }; secretForms.set(token, entry); const url = 'https://' + RP_ID + '/auth/secrets/' + token; sendJSON(res, 201, { ok: true, token, url, expires_at: entry.expiresAt }); }); } const secretPollMatch = p.match(/^\/(?:auth\/)?secrets\/([a-f0-9]{64})\/poll$/); if (secretPollMatch && req.method === 'GET') { const token = secretPollMatch[1]; const entry = secretForms.get(token); if (!entry.submitted) { sendJSON(res, 202, { submitted: false }); return; } const values = entry.values; entry.consumed = true; secretForms.delete(token); sendJSON(res, 200, { submitted: true, values }); } ``` A second unauthenticated retrieval endpoint is present at lines 1211–1230: ```javascript if ((p ...[truncated 2488 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require an authenticated and properly scoped caller to create a secret form. - Bind each secret request to the authenticated creator’s session key. - Generate separate capabilities: - A submission-only browser token. - A creator-only retrieval token that never appears in the browser URL. - Require bearer authentication plus the creator-only capability for retrieval. - Do not return secret values through a GET endpoint. - Use POST for retrieval and set `Cache-Control: no-store`. - Ensure the reverse proxy does not log sensitive URL components. - Enforce short expiration periods and strict one-time consumption atomically. - Validate submitted field names and reject undeclared or oversized values. - Add audit records for creation, failed retrieval attempts, successful retrieval, expiration, and consumption without recording secret values. - Remove the duplicated secret endpoint implementations to avoid inconsistent authorization behavior. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
assets/auth-server.js:532
Finding
Wildcard CORS Applies to All Security-Sensitive Endpoints<![CDATA[ ## Vulnerability Details **File Location**: `assets/auth-server.js`, lines 532–538 **Vulnerability Type**: Overly permissive cross-origin resource sharing policy **Risk Level**: Medium ### Vulnerable Code ```javascript function handleRequest(req, res) { const url = new URL(req.url, `http://${req.headers.host}`); const ip = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.socket.remoteAddress; res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader( 'Access-Control-Allow-Headers', 'Content-Type, Authorization' ); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } const p = url.pathname; ``` ### Technical Analysis The server permits JavaScript from every origin to issue requests and read responses. This policy applies indiscriminately to authentication status, login, token verification, delegated grants, passkey operations, and secret retrieval. The service binds to localhost, but the documented deployment places an HTTPS reverse proxy in front of it. Once browser-reachable, wildcard CORS allows a malicious website visited by the user to interact with that deployment. This setting compounds the missing authentication on secret APIs by allowing arbitrary websites to create forms and read secret retrieval responses directly from browser JavaScript. ### Attack Path 1. A victim visits an attacker-controlled webpage. 2. The page sends cross-origin requests to the victim’s browser-reachable Max Auth deployment. 3. The server responds with `Access-Control-Allow-Origin: *`. 4. The attacker’s JavaScript reads status and API responses that would otherwise be protected by the same-origin policy. 5. The page can use the unauthenticated secret endpoints to create or retrieve forms, or use token verification as an oracle. ### Impact Assessment The issue expands access to the complete API from arbitra ...[truncated 337 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Disable CORS by default for the local authentication API. - If cross-origin browser access is required, use a fixed allowlist of exact HTTPS origins. - Return `Access-Control-Allow-Origin` only after validating the request origin. - Add `Vary: Origin` when dynamically selecting an allowed origin. - Restrict allowed methods and headers on an endpoint-by-endpoint basis. - Do not permit cross-origin secret retrieval or token verification. - Reject `Origin: null` unless there is a documented and tested requirement. - Add automated tests proving that unapproved origins cannot read any API response. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
assets/auth-server.js:393
Finding
Stored Script Injection in Generated Secret Forms<![CDATA[ ## Vulnerability Details **File Location**: `assets/auth-server.js`, lines 393–398 and 431–444 **Vulnerability Type**: Stored cross-site scripting through unsafe inline-script serialization **Risk Level**: High ### Vulnerable Code ```javascript function buildSecretFormPage(token, entry) { const expiresIn = Math.max( 0, Math.round((entry.expiresAt - Date.now()) / 60000) ); const fieldsJson = JSON.stringify(entry.fields); const labelJson = JSON.stringify(entry.label); const submitUrl = '/auth/secrets/' + token + '/submit'; return `<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Secure Form — Max Auth</title> </head> <body> <!-- page content --> <script> var navLang = (navigator.language || '').toLowerCase(); var lang = navLang.startsWith('pt') ? 'pt' : navLang.startsWith('es') ? 'es' : 'en'; var fields = ${fieldsJson}; var label = ${labelJson}; var expiresIn = ${expiresIn}; var submitUrl = '${submitUrl}'; ``` The values originate from unauthenticated request data: ```javascript const entry = { token, label: String(label), fields: fields.map(f => ({ name: String(f.name), label: String(f.label || f.name), type: ['password', 'textarea'].includes(f.type) ? f.type : 'text' })), // ... }; ``` ### Technical Analysis `entry.label` and field properties are serialized with `JSON.stringify()` and interpolated directly into an inline `<script>` element. JSON encoding does not make data safe for an HTML script context because a value containing `</script>` terminates the element before JavaScript parsing occurs. For example, a label containing `</script><script>/* attacker code */</script>` can break out of the intended assignment and execute attacker-controlled JavaScript under the Max Auth origin. Because secret-form creation requires no authentication, an external caller can persist such a payload in the in-memory form entry and distr ...[truncated 1155 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not interpolate untrusted JSON directly into an inline script. - Place serialized data in a non-executable `<script type="application/json">` element and safely parse its text content. - Escape at least `<`, `>`, `&`, U+2028, and U+2029 when serializing JSON for an HTML context. - Prefer server-rendered HTML with context-aware escaping and no inline JavaScript. - Validate field names against a strict character and length allowlist. - Validate labels and impose conservative length limits. - Require authentication to create forms. - Add a restrictive Content Security Policy using nonces or external scripts, with no `unsafe-inline`. - Add regression tests using `</script>`, encoded closing tags, quotes, backslashes, and Unicode separators. ]]>

T08 · Insecure Dependencies

Note
Location
assets/package.json:1
Finding
Dependency Installation Is Not Reproducibly Locked<![CDATA[ ## Vulnerability Details **File Location**: `assets/package.json`, lines 1–9 **Vulnerability Type**: Floating dependency range without a lockfile **Risk Level**: Low ### Vulnerable Code ```json { "name": "max-auth", "version": "1.0.0", "description": "Max Auth Server — biometric passkey + master password auth for OpenClaw sensitive actions", "main": "auth-server.js", "dependencies": { "@simplewebauthn/server": "^13.0.0" } } ``` The documented installation runs: ```bash npm install ``` ### Technical Analysis The caret version range permits installation of later compatible releases rather than the exact dependency version represented by this audit. No package lockfile is included in the audited project. As a result, two users installing the same Skill at different times may receive different transitive dependency graphs. A future compromised, vulnerable, or behaviorally incompatible release could therefore enter the authentication server without a corresponding Skill-code change. No evidence was found that the named dependency is malicious; this finding concerns reproducibility and supply-chain exposure. ### Attack Path 1. A later dependency or transitive-dependency release is published within the permitted version range. 2. That release contains a vulnerability or compromised installation/runtime code. 3. A user follows the documentation and runs `npm install`. 4. npm resolves and installs the newer package graph. 5. The added code executes with the privileges of the Max Auth service and can access its authentication files and runtime secrets. ### Impact Assessment A compromised dependency would execute inside a security-sensitive service that handles master-password verification, passkey material, bearer tokens, audit records, and plaintext submitted secrets. The practical likelihood is lower than the application vulnerabilities above, but the potential scope includes the entire Max Auth process and its user-owned configura ...[truncated 17 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate and include a reviewed `package-lock.json`. - Use `npm ci` in installation and deployment instructions. - Pin the direct dependency to a reviewed exact version where practical. - Review lockfile changes before accepting dependency updates. - Enable automated vulnerability and integrity scanning. - Use npm registry integrity hashes supplied by the lockfile. - Re-audit security-sensitive dependency upgrades before release. - Consider disabling lifecycle scripts during installation where compatible with required dependencies. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented behavior promises session-scoped authentication and a constrained auth gate, but the analyzed behavior indicates a global authenticated session, undocumented delegated grants, and unauthenticated secret submission/retrieval endpoints. For a security control, this mismatch is dangerous because users and downstream agents may trust stronger isolation and access restrictions than are actually enforced, enabling authorization bypass, privilege leakage across sessions, or unauthorized secret access.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares no explicit tool scope or permission boundaries even though it clearly relies on network and environment-related capabilities. In an auth-related skill, missing scope declarations increase the chance that an agent or integrator grants broader access than intended, weakening least-privilege guarantees and making misuse or unexpected behavior harder to detect.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
To derive the relying-party ID, the code imports child_process and runs `tailscale status --json`. Spawning subprocesses and interrogating host networking state is not an obvious requirement of a local biometric/password authentication gate as described in the manifest.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The server advertised as an authentication gate also contains delegated grant issuance and secret-collection workflows, materially expanding its authority and attack surface. In an auth component, extra capabilities are especially risky because callers may trust it as a narrow security boundary while it can mint downstream authorization artifacts and broker sensitive data.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The secret submission and retrieval endpoints operate without any authentication or authorization, allowing anyone who knows or obtains a token to submit or retrieve sensitive values. This turns the auth server into a general-purpose secret broker, which is far more dangerous in the context of a trusted local authentication gate because other components may assume secrets handled by it are protected by prior authentication.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
These endpoints collect arbitrary sensitive fields and later return them, but the user-facing form only presents generic 'secure' messaging and does not clearly disclose who will receive the secrets, how they are stored, or that retrieval is plaintext. In a security/auth skill, such opaque secret handling can mislead users into entering credentials or keys under stronger trust assumptions than the implementation actually provides.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Accepting the master password as a command-line argument exposes it to process listings, shell history, audit tooling, and potentially other local users. For a master credential protecting sensitive actions, this is an avoidable secret exposure path that undermines the strength of the authentication system.

Session Persistence

Medium
Category
Rogue Agent
Content
## Installation

```bash
mkdir -p ~/.max-auth && cd ~/.max-auth
cp <skill-path>/assets/auth-server.js .
cp <skill-path>/assets/package.json .
npm install
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
## Installation

```bash
mkdir -p ~/.max-auth && cd ~/.max-auth
cp <skill-path>/assets/auth-server.js .
cp <skill-path>/assets/package.json .
npm install
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
## systemd example

```bash
sudo tee /etc/systemd/system/max-auth.service > /dev/null <<'EOF'
[Unit]
Description=Max Auth Server
After=network.target
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
## systemd example

```bash
sudo tee /etc/systemd/system/max-auth.service > /dev/null <<'EOF'
[Unit]
Description=Max Auth Server
After=network.target
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Rule

Do not proceed with sensitive work if the relevant session key is not authenticated.
Do not ask the user to paste secrets into chat when `request_secret` is available.
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
The password login failure path returns `STRINGS.pt.wrong_pwd` regardless of the user's selected or detected language. This violates the language-choice policy because it imposes Portuguese on all users instead of honoring locale selection or using a neutral default consistently.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
On incorrect password during passkey registration, the server responds with `STRINGS.pt.wrong_pwd` independent of user locale. This is a natural-language policy issue because it overrides language preference and forces Portuguese output.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"description": "Max Auth Server — biometric passkey + master password auth for OpenClaw sensitive actions",
  "main": "auth-server.js",
  "dependencies": {
    "@simplewebauthn/server": "^13.0.0"
  }
}
Confidence
93% confidence
Finding
The dependency is specified with a caret range (^13.0.0), which allows newer minor and patch versions to be installed without review. In a security-sensitive authentication component, this weakens supply-chain control and can unexpectedly pull in vulnerable or behavior-changing releases.

Unverifiable Dependency: @simplewebauthn/server has 1 known advisory(ies) (GHSA-6hxq-p678-4hr2 (SimpleWebAuthn: Registration verification does not sufficiently ensure that atte)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
84% confidence
Finding
The manifest references @simplewebauthn/server without an exact version pin, while the package has a known advisory affecting some releases. Because this skill implements authentication with WebAuthn, uncertainty about whether an affected version may be installed is more dangerous than in a non-security feature: a vulnerable auth library could undermine registration or attestation validation.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.potential_exfiltration

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
assets/auth-server.js:44

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
assets/auth-server.js:25

Sensitive-looking file read is paired with a network send.

Warn
Code
suspicious.potential_exfiltration
Location
assets/auth-server.js:242