Back to skill

Security audit

LNbits Wallet

Security checks for vulnerabilities and agentic risk

Overview

This LNbits wallet skill is mostly purpose-aligned, but it handles real payment authority and wallet admin keys in ways users should review before installing.

Install only if you are comfortable giving this skill access to an LNbits wallet key that can view wallet data and send payments. Use a dedicated wallet with limited funds, set LNBITS_BASE_URL only to a trusted HTTPS LNbits server, avoid creating wallets through this skill unless you can store the admin key outside chat/logs, and manually verify every invoice before payment.

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/lnbits_cli.py:10
Finding
Unrestricted LNbits Base URL Can Expose the Wallet API Key## Vulnerability Details **File Location**: `scripts/lnbits_cli.py`, lines 10-26 **Vulnerability Type**: Unvalidated security-sensitive endpoint configuration **Risk Level**: High ### Vulnerable Code ```python BASE_URL = os.getenv("LNBITS_BASE_URL", "https://legend.lnbits.com").rstrip("/") API_KEY = os.getenv("LNBITS_API_KEY") # --- Helpers --- def error(msg, code=1): print(json.dumps({"error": msg})) sys.exit(code) def request(method, endpoint, data=None): if not API_KEY: error("LNBITS_API_KEY environment variable is not set.") url = f"{BASE_URL}/api/v1{endpoint}" headers = { "X-Api-Key": API_KEY, "Content-Type": "application/json" } body = json.dumps(data).encode("utf-8") if data else None ``` ### Technical Analysis `LNBITS_BASE_URL` is accepted directly from the environment and used to construct authenticated requests without validating its scheme, hostname, port, or other URL components. Every authenticated request places the wallet API key in the `X-Api-Key` header. If the variable is set to an attacker-controlled HTTPS server, that server receives the API key. If it is set to an HTTP endpoint, the key and associated wallet traffic may be transmitted without transport encryption and intercepted by a network attacker. Environment-based configuration is legitimate, but a credential-bearing client should validate the destination before releasing an administrator-level wallet credential. ### Attack Path 1. An attacker influences the process environment, deployment configuration, `.env` file, shell profile, or agent configuration containing `LNBITS_BASE_URL`. 2. The attacker changes the value to an attacker-controlled URL or an unencrypted HTTP endpoint. 3. A user or agent invokes `balance`, `invoice`, `decode`, or `pay`. 4. The `request` function constructs a URL under the configured endpoint. 5. The CLI sends `LNBITS_API_KEY` in the `X-Api ...[truncated 588 chars]
Remediation
## Remediation Suggestions - Parse `LNBITS_BASE_URL` with `urllib.parse.urlsplit`. - Require the `https` scheme, except for an explicitly enabled local-development mode restricted to loopback addresses. - Reject embedded usernames, passwords, fragments, and unexpected URL components. - Maintain an explicit allowlist of trusted LNbits hostnames where deployment requirements permit it. - If arbitrary self-hosted instances must be supported, require an explicit trust-on-first-use or administrator approval step before sending credentials to a new host. - Prevent redirects from forwarding `X-Api-Key` to a different origin. Use a redirect handler that rejects cross-origin redirects for authenticated requests. - Keep TLS certificate validation enabled and do not introduce an unverified SSL context. - Store the API key in an approved secret manager with tightly restricted access, and rotate it immediately if endpoint redirection or disclosure is suspected.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lnbits_cli.py:66
Finding
Wallet Creation Prints the Administrator Key to Standard Output## Vulnerability Details **File Location**: `scripts/lnbits_cli.py`, lines 66-75 **Vulnerability Type**: Plaintext secret exposure through command output **Risk Level**: Medium ### Vulnerable Code ```python def create_wallet(name): url = f"{BASE_URL}/api/v1/account" req = urllib.request.Request( url, method="POST", headers={"Content-Type": "application/json"}, data=json.dumps({"name": name}).encode("utf-8") ) with urllib.request.urlopen(req, timeout=20) as resp: return json.loads(resp.read().decode("utf-8")) # --- CLI Handlers --- def cmd_balance(args): print(json.dumps(get_balance(), indent=2)) def cmd_create(args): print(json.dumps(create_wallet(args.name), indent=2)) ``` The corresponding Skill documentation explicitly expects the account response to contain an administrator key: ```markdown **Action**: 1. Run the command. 2. Capture the `adminkey` (Admin Key) and `base_url` ``` ### Technical Analysis `create_wallet` returns the complete account-creation response, and `cmd_create` serializes that response directly to standard output. According to `SKILL.md`, the response is expected to include the wallet `adminkey`. Standard output from agent tools is commonly retained in conversation transcripts, orchestration logs, observability systems, terminal history, or CI logs. Printing the complete response therefore creates a plaintext secret-disclosure path. It also conflicts with the Skill’s own instruction not to expose administrator keys. ### Attack Path 1. A user or agent invokes the `create` command. 2. LNbits returns the newly created wallet information, including the administrator key expected by the documented workflow. 3. `create_wallet` returns the complete decoded response without filtering sensitive fields. 4. `cmd_create` prints the entire object to standard output. 5. The surrounding agent platform, te ...[truncated 615 chars]
Remediation
## Remediation Suggestions - Do not print the raw account-creation response. - Extract and redact sensitive fields such as `adminkey`, wallet IDs, and user IDs before producing user-visible output. - Transfer the administrator key directly into an approved secret manager or protected configuration mechanism. - Return only a success indicator, the trusted base URL, and non-sensitive setup guidance. - If automated secret storage is unavailable, use a dedicated secure handoff channel that is not persisted in ordinary agent transcripts. - Ensure logs and exception handlers never serialize the account response. - Restrict file permissions if credentials must temporarily be written to a local configuration file. - Update `SKILL.md` so its setup workflow does not require displaying or repeating the administrator key in chat. - Rotate any key that has already appeared in retained command output or conversation logs.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/lnbits_cli.py:53
Finding
Payment Confirmation and Preflight Safety Checks Are Not Enforced by the CLI## Vulnerability Details **File Location**: `scripts/lnbits_cli.py`, lines 53-54 **Additional Locations**: `scripts/lnbits_cli.py`, lines 83-84 and 112-114; `SKILL.md`, lines 17-20 and 51-61 **Vulnerability Type**: Missing authorization and transaction-safety enforcement **Risk Level**: Medium ### Vulnerable Code ```python def pay_invoice(bolt11): return request("POST", "/payments", {"out": True, "bolt11": bolt11}) ``` The payment handler invokes this operation directly: ```python def cmd_pay(args): print(json.dumps(pay_invoice(args.bolt11), indent=2)) ``` Command dispatch does not establish or verify a confirmation state: ```python args = parser.parse_args() try: args.func(args) except Exception as e: error(str(e)) ``` The required controls exist only in the Skill documentation: ```markdown 2. **Explicit Confirmation**: You MUST ask for "Yes/No" confirmation before paying. 3. **Check Balance First**: Always call `balance` before `pay` to prevent errors. ``` ### Technical Analysis Payment is an irreversible, security-sensitive operation, but the executable does not enforce the documented workflow. The `pay` subcommand immediately submits any supplied Bolt11 invoice to the LNbits payment API. There is no code-level requirement to decode the invoice, verify its amount or destination metadata, check the available balance, bind a user confirmation to the decoded invoice, or ensure that the invoice being paid is identical to the invoice that was confirmed. Consequently, the safety policy depends entirely on the calling agent correctly following natural-language instructions. The absence of enforcement is especially significant because the same API credential can authorize outgoing wallet payments. Direct CLI invocation, automation errors, prompt-driven misuse, or a mismatch between the decoded and paid invoice can bypass the documented safeguards. ### Attack Path 1. A ...[truncated 1033 chars]
Remediation
## Remediation Suggestions - Move payment safety enforcement into the executable rather than relying solely on `SKILL.md`. - Decode the invoice before payment and validate its amount, description, expiry, and other relevant fields. - Retrieve the current wallet balance and reject payments that exceed the available balance. - Present the decoded amount and destination description to the user before authorization. - Generate a short-lived, single-use confirmation token bound cryptographically to the exact invoice, amount, wallet, and expiration time. - Require that token for the final payment operation and invalidate it after one attempt. - Re-decode or hash the invoice immediately before payment to ensure it matches the confirmed invoice. - Consider enforcing configurable per-payment and daily spending limits. - Record non-sensitive audit events for payment requests and approvals without logging API keys or complete sensitive responses. - For unattended automation, use a separately scoped credential with strict spending limits rather than the wallet administrator key.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Tainted flow: 'req' from os.getenv (line 63, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request(url, method=method, headers=headers, data=body)
    try:
        with urllib.request.urlopen(req, timeout=20) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        error_body = e.read().decode("utf-8", errors="replace")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.getenv (line 63, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request(url, method=method, headers=headers, data=body)
    try:
        with urllib.request.urlopen(req, timeout=20) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        error_body = e.read().decode("utf-8", errors="replace")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description says the skill manages wallet balance, payments, and invoices, but the body also instructs the agent to create wallets and decode invoices. This mismatch hides materially sensitive capabilities—especially account creation and payment pre-processing—which can mislead reviewers, users, or policy systems about the true financial and data-handling scope of the skill.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The skill first states that secrets must never be exposed, then instructs the agent to capture and present the wallet admin key to the user in setup output. Admin keys grant control over the LNbits wallet, so displaying them in chat or logs creates a direct secret-exposure path and can lead to wallet takeover or fund theft if transcripts are retained or observed.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill invokes Python scripts that rely on environment secrets and make networked wallet/payment operations, yet it declares no explicit tool scope or permission boundaries. In an agent setting, this weakens policy enforcement and makes it easier for the skill to access sensitive capabilities or perform financial actions without clear confinement.

Ssd 3

Medium
Confidence
98% confidence
Finding
Relaying a newly generated admin key to the user in plain text exposes a highly sensitive credential in the assistant conversation, which may be logged, cached, or viewable by unintended parties. Because the key controls the wallet, any leakage can enable unauthorized wallet access and downstream payment abuse.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The pay command immediately submits a Lightning payment when given a Bolt11 invoice, with no confirmation prompt, no amount/recipient preview, and no policy checks. In an agent context, this is especially dangerous because an upstream prompt injection, user misunderstanding, or malformed automation could trigger irreversible fund transfer with minimal friction.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill description says it manages an LNbits wallet with balance, pay, and invoice functions, but the CLI also supports creating new wallets/accounts. This capability expansion matters because users or orchestrators may grant or invoke the skill under an incomplete understanding of its authority, enabling unintended account provisioning or surface-area growth.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The create_wallet path performs account creation without using the configured API key or any other local authorization check. In a skill meant for wallet management, exposing unauthenticated account creation broadens functionality beyond the stated purpose and may allow abuse of the remote LNbits instance for unauthorized resource creation, depending on server policy.

Natural-Language Policy Violations

Low
Confidence
85% confidence
Finding
The instruction to 'summarize it clearly for the user' does not itself violate policy, but elsewhere the entire skill content is written as prescriptive English-only user-facing guidance with no indication that language choice should follow user preference. Because the skill defines user-visible confirmation and credential messages without offering locale choice, it can be read as forcing English output by default.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The `request` helper reads `LNBITS_API_KEY` and sends it in the `X-Api-Key` header on every API call. While this is functionally required, the file provides no comment, docstring, or user-facing notice that the skill accesses a sensitive environment variable and transmits it over the network.

Static analysis

No suspicious patterns detected.