Back to skill

Security audit

Stripe Analytics

Security checks for vulnerabilities and agentic risk

Overview

This Stripe analytics skill is broadly purpose-aligned, but it handles sensitive billing data with several under-scoped or overstated security and accuracy claims.

Review before installing. Use only a narrowly scoped Stripe restricted key, do not provide a general sk_ secret key, and expect the skill to read sensitive customer and revenue data from Stripe. Treat the dashboard numbers cautiously because some advertised metrics are hard-coded or simplified, and avoid broad voice or shortcut triggers unless your environment requires confirmation before execution.

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

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
skill/skill.js:15
Finding
Unrestricted Stripe Secret Keys Are Accepted Despite Read-Only Requirements<![CDATA[ ## Vulnerability Details **File Location**: `skill/skill.js:15-20` **Vulnerability Type**: Insufficient credential privilege validation **Risk Level**: Medium ### Complete Code Snippet ```javascript if (!process.env.STRIPE_READ_KEY || !(process.env.STRIPE_READ_KEY.startsWith('sk_') || process.env.STRIPE_READ_KEY.startsWith('rk_'))) { return { status: 'error', error_type: 'auth_error', message: 'STRIPE_READ_KEY invalid. Use Stripe Dashboard > Restricted Key > read:customers/subscriptions/invoices/payment_intents' } } ``` ### Technical Analysis The skill is documented as requiring a restricted, read-only Stripe key, but its validation accepts credentials beginning with either `rk_` or `sk_`. An `rk_` key is a restricted Stripe key, while an `sk_` key can be a general secret key with permissions substantially broader than the read-only operations required by this analytics function. The current code only performs read requests against Stripe. Therefore, the module does not directly use the accepted credential to modify Stripe resources. Nevertheless, allowing an unnecessarily privileged credential violates least privilege and expands the consequences of a process compromise, future code change, runtime instrumentation, or accidental credential disclosure. Prefix validation also does not verify the effective permissions granted to the key. A restricted key may still have excessive scopes, while an `sk_` key may grant account-wide capabilities. ### Attack Path 1. A user follows the configuration process but supplies a general Stripe secret key beginning with `sk_`. 2. The validation condition accepts the key even though the skill claims to require a restricted read-only key. 3. The privileged key is loaded into the skill process and transmitted in authorization headers to Stripe. 4. If the process, runtime, or future version of the skill is compromised, the attacker can capture the key. 5. The attacker can then use every permiss ...[truncated 689 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject general Stripe secret keys and require a restricted key: ```javascript const key = process.env.STRIPE_READ_KEY if (!key || !key.startsWith('rk_')) { return { status: 'error', error_type: 'auth_error', message: 'A restricted Stripe read-only key is required.' } } ``` 2. Document the minimum required read scopes precisely and keep all write scopes disabled. 3. Where supported, validate the key's effective permissions during setup and fail closed when required scopes are absent or unnecessary scopes are present. 4. Store the key in a dedicated secret manager rather than plaintext configuration. 5. Ensure the environment variable is not inherited by unrelated child processes. 6. Add automated tests confirming that `sk_` keys and malformed credentials are rejected. 7. Rotate any unrestricted secret key previously supplied to the skill and replace it with a narrowly scoped restricted key. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
skill/skill.js:96
Finding
Raw Stripe API Error Responses Are Exposed to Callers<![CDATA[ ## Vulnerability Details **File Location**: `skill/skill.js:96-103` and `skill/skill.js:138-141` **Vulnerability Type**: Improper error-message sanitization and information exposure **Risk Level**: Low ### Complete Code Snippet ```javascript } catch (err) { return { status: 'error', error_type: err.message.includes('401') ? 'auth_error' : err.message.includes('429') ? 'rate_limit' : 'stripe_api_error', message: `Stripe: ${err.message}` } } ``` ```javascript if (!res.ok) { const errorText = await res.text() throw new Error(`${res.status}: ${errorText}`) } ``` ### Technical Analysis When Stripe returns a non-success response, the implementation reads the complete response body into `errorText` and places it in an exception. The outer error handler then returns the complete exception message to the caller. This creates a direct information-flow path from an upstream Stripe response to skill output without redaction, allowlisting, or normalization. Stripe error bodies can contain request identifiers, object identifiers, parameter details, account-related context, and diagnostic information. Downstream agent frameworks may additionally persist these responses in conversation history, telemetry, or application logs. The authorization header itself is not included in the shown error response handling, and there is no evidence that Stripe echoes the API key. Consequently, direct key disclosure is not established. The confirmed issue is uncontrolled exposure of upstream diagnostic content. The error classifier also relies on substring matching against the message rather than a structured HTTP status, which can lead to inaccurate error categorization. ### Attack Path 1. A Stripe API request fails because of invalid permissions, malformed parameters, rate limiting, or another upstream condition. 2. `paginateStripe` reads the complete Stripe response body with `res.text()`. 3. The response status and ...[truncated 1089 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not return raw upstream response bodies to callers. 2. Preserve the HTTP status separately and map it to a fixed, user-safe message: ```javascript if (!res.ok) { const requestId = res.headers.get('request-id') const error = new Error('Stripe request failed') error.status = res.status error.requestId = requestId throw error } ``` 3. Classify errors using a structured status property rather than searching message text: ```javascript const type = err.status === 401 ? 'auth_error' : err.status === 429 ? 'rate_limit' : 'stripe_api_error' ``` 4. Return generic messages such as `Stripe authentication failed`, `Stripe rate limit exceeded`, or `Stripe API request failed`. 5. If diagnostic logging is necessary, log only allowlisted fields such as the HTTP status and Stripe request ID. 6. Protect diagnostic logs with access controls and retention limits, and never record authorization headers or complete response bodies. 7. Add tests that simulate Stripe error responses containing sensitive markers and verify that none are returned to the caller. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill requires environment access to `STRIPE_READ_KEY` and clearly intends to make network calls to Stripe, but it does not declare any explicit tool scope such as `permissions` or `allowed-tools`. That mismatch weakens least-privilege enforcement and can cause the runtime or reviewers to underappreciate the skill's actual capabilities, increasing the chance of unintended secret or network access.

Vague Triggers

Medium
Confidence
94% confidence
Finding
Documented usage triggers such as "Stripe metrics", "top customers", and "90d revenue" are somewhat ambiguous and may overlap with normal user requests without making clear that a privileged Stripe-backed skill will be invoked. In a finance context, ambiguous invocation increases the risk of unintentional access to sensitive customer and revenue data.

Vague Triggers

Medium
Confidence
97% confidence
Finding
The voice triggers like "Hey, Stripe numbers?" and especially broad phrases such as "Business dashboard" can match ordinary conversation and invoke the skill unintentionally. Because this skill accesses sensitive business and customer billing analytics, accidental activation could expose confidential revenue data in the wrong context or to unintended listeners.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code performs multiple Stripe API requests that retrieve customer, subscription, invoice, and payment intent data, which are potentially sensitive business and personal records. While the header notes the API is read-only, there is no confirmation prompt, user-facing log/print, or inline warning explaining that external network calls will transmit account data to Stripe during execution.

Scope Creep

Medium
Confidence
93% confidence
Finding
The skill calls Stripe's /v1/plans endpoint even though the declared required scopes do not include plans/products access. This creates a permission mismatch between what operators expect to grant and what the code actually attempts, which can lead to over-privileged API keys being provisioned or unexpected authorization failures that weaken least-privilege controls.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest describes a 'complete Stripe analytics dashboard' with concrete metrics such as NRR, forecasts, segments, and insights. In code, several outputs are hard-coded or mock implementations, including net_revenue_retention = 107, retained_pct = 92, static segment/geo data, and fixed alerts, so the actual behavior does not match the claimed analytics completeness or fidelity.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
The header asserts that the verified read-only endpoints and listed restricted scopes are sufficient, but that statement is inaccurate because the code also accesses /v1/plans. Misleading security claims reduce operator scrutiny and can cause unsafe trust decisions about required privileges and data access.

Static analysis

No suspicious patterns detected.