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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/lnbits_cli.py (reported line 32)May include surrounding context.

python
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")

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/lnbits_cli.py (reported line 69)May include surrounding context.

python
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")

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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.