Back to skill

Security audit

LNURL Auth

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its LNURL-auth purpose, but its helper can send login artifacts to insecure, internal, or unexpected callback URLs.

Review before installing. Use only with LNURL challenges from services you trust, run --dry-run --json first, verify the callback host and scheme, avoid --single-key unless you knowingly want cross-domain correlation, and avoid running it in environments with sensitive internal HTTP services until HTTPS, same-origin, redirect, and private-network protections are added.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/lnurl_auth.js:343
Finding

Unrestricted outbound HTTP requests enable server-side request forgery

Content
View full analysis
{ let url; try { url = new URL(urlString); } catch { reject(new Error(`Invalid URL: ${urlString}`)); return; } if (!['http:', 'https:'].includes(url.protocol)) { reject(new Error(`Unsupported URL protocol: ${url.protocol}`)); return; } if (redirects > MAX_REDIRECTS) { reject(new Error(`Too many redirects (${redirects})`)); return; } const client = url.protocol === 'https:' ? https : http; const request = client.get(url, { timeout, headers: { 'User-Agent': USER_AGENT } }, (response) => { if ([301, 302, 307, 308].includes(response.statusCode) && response.headers.location) { response.resume(); resolve(httpGet(new URL(response.headers.location, url).toString(), timeout, redirects + 1)); return; } let body = ''; response.setEncoding('utf8'); response.on('data', (chunk) => { body += chunk; }); response.on('end', () => resolve({ status: response.statusCode, body })); }); request.on('timeout', () => request.destroy(new Error(`Request timed out after ${timeout}ms`))); request.on('error', reject); }); } ``` The untrusted URLs reach this function through the decoded LNURL, challenge-provided callback, command-line callback override, and authentication callback: ```javascript const serviceUrl = decodeLnurl(lnurl); const parsed = new URL(serviceUrl); if (!parsed.hostname) throw new Error('Decoded LNURL has no service hostname'); if (!k1) { log('No k1 in URL; fetching challenge...'); const challenge = await httpGet(serviceUrl, timeout); // ... } const callback = new URL(opts.callback || callbackFromChallenge || se ...[truncated 2678 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lnurl_auth.js:391
Finding

Unvalidated callback substitution discloses domain-specific authentication artifacts across origins

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lnurl_auth.js:352
Finding

Plaintext HTTP exposes authentication requests to interception and manipulation

Content
View full analysis
MAX_REDIRECTS) { reject(new Error(`Too many redirects (${redirects})`)); return; } const client = url.protocol === 'https:' ? https : http; const request = client.get(url, { timeout, headers: { 'User-Agent': USER_AGENT } }, (response) => { ``` The callback URL contains the challenge, signature, and stable linking public key: ```javascript if (!callback.searchParams.has('k1')) callback.searchParams.set('k1', k1); callback.searchParams.delete('sig'); callback.searchParams.delete('key'); callback.searchParams.set('sig', bytesToHex(signed.der)); callback.searchParams.set('key', bytesToHex(signed.compressed)); ``` ### Technical Analysis The helper explicitly accepts both HTTP and HTTPS. When HTTP is used, the LNURL challenge, callback parameters, signature, public key, and service response travel without transport encryption or server authentication. Because the artifacts are placed in a GET query string, they may additionally be exposed to intermediary logs, proxy logs, and local request histories. The use of GET is part of the described LNURL-auth protocol flow, but accepting plaintext transport is not necessary for remote authentication. Redirect handling also permits an initially secure HTTPS request to downgrade to HTTP. No policy prevents this downgrade. The master secret and private linking key are not transmitted. However, network observers can obtain the stable public identifier and signed challenge, while an active attacker can alter routing or challenge responses. ### Attack Path 1. A victim uses an LNURL that resolves to an HTTP service, or an HTTPS service redirects to an HTTP ...[truncated 1089 chars]
Remediation
View remediation
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (1)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.