Back to skill

Security audit

In-App Purchases

Security checks for vulnerabilities and agentic risk

Overview

The skill is transparently about in-app purchases, but some examples could lead developers to implement insecure subscription and entitlement handling.

Install only if you are prepared to review and harden any copied payment code. In particular, verify webhooks before changing entitlements, validate Apple and Google purchase state completely, keep secret API keys server-side, minimize stored receipts and subscriber data, and add retention, access-control, and audit policies.

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

T09 · Insecure Skill Coding Practices

Error
Location
revenuecat.md:193
Finding
Unauthenticated RevenueCat Webhook Permits Forged Entitlement Events<![CDATA[ ## Vulnerability Details **File Location**: `revenuecat.md`, lines 193-220 **Vulnerability Type**: Unauthenticated webhook with authorization-sensitive side effects **Risk Level**: High ### Vulnerable Code ```javascript app.post('/revenuecat/webhook', (req, res) => { const event = req.body; switch (event.type) { case 'INITIAL_PURCHASE': grantAccess(event.app_user_id, event.product_id); break; case 'RENEWAL': extendAccess(event.app_user_id); break; case 'CANCELLATION': // User cancelled - will expire at period end markWillExpire(event.app_user_id); break; case 'EXPIRATION': revokeAccess(event.app_user_id); break; case 'BILLING_ISSUE': sendPaymentFailedEmail(event.app_user_id); break; case 'SUBSCRIBER_ALIAS': // User IDs merged break; } res.sendStatus(200); }); ``` ### Technical Analysis The example trusts `req.body` and performs entitlement and account lifecycle operations without authenticating the request as originating from RevenueCat. It also lacks schema validation, event replay protection, idempotency enforcement, and reconciliation with RevenueCat's authenticated API. Because `event.type`, `event.app_user_id`, and `event.product_id` are attacker-controlled for an unauthenticated request, a caller can select the operation and target account. These fields directly reach functions that grant, extend, alter, or revoke access. Webhook authentication is necessary even when the endpoint uses HTTPS. TLS protects traffic in transit but does not prove that an arbitrary caller is RevenueCat. ### Attack Path 1. An attacker discovers or predicts the public `/revenuecat/webhook` endpoint. 2. The attacker submits a forged JSON request such as an `INITIAL_PURCHASE` event containing a selected application user ID and product ID. 3. The handler accepts the request without verifying an authorization secret or trusted signature. 4. The handler ...[truncated 762 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Configure a high-entropy RevenueCat webhook authorization value and require it on every request. 2. Compare authentication values using a timing-safe comparison and reject unauthorized requests before parsing or processing event data. 3. Validate requests against a strict schema: - Permit only supported event types. - Validate user, product, application, environment, and event identifiers. - Enforce field lengths and expected data types. 4. Record RevenueCat's unique event identifier and reject duplicate or replayed events. 5. Make entitlement transitions idempotent and transactionally update the database. 6. Reconcile high-impact changes with RevenueCat's authenticated REST API before granting or revoking access. 7. Apply request-body size limits, rate limiting, audit logging, and safe error handling. 8. Do not log webhook authorization secrets, full receipts, or unnecessary subscriber identifiers. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
analytics.md:254
Finding
Unauthenticated Analytics Webhook Allows Subscription Data Poisoning<![CDATA[ ## Vulnerability Details **File Location**: `analytics.md`, lines 254-264 **Vulnerability Type**: Unauthenticated ingestion of untrusted analytics data **Risk Level**: Medium ### Vulnerable Code ```javascript app.post('/revenuecat/webhook', async (req, res) => { await db.events.insert({ event_type: req.body.type, user_id: req.body.app_user_id, product_id: req.body.product_id, price: req.body.price, currency: req.body.currency, timestamp: new Date(req.body.event_timestamp_ms) }); res.sendStatus(200); }); ``` ### Technical Analysis The data-warehouse endpoint inserts request fields directly into the event store without authenticating the sender. It does not validate event types, identifiers, numeric values, currency codes, timestamp ranges, or request sizes. No idempotency or replay control is shown. Parameterized behavior of `db.events.insert` cannot be determined from the documentation, so database injection is not confirmed. However, analytics poisoning is directly supported by the example: arbitrary callers can supply records that the application stores as RevenueCat events. The absence of error handling can also result in unreliable webhook delivery behavior or unhandled asynchronous errors when invalid values reach the database. ### Attack Path 1. An attacker locates the public `/revenuecat/webhook` endpoint. 2. The attacker sends fabricated subscription events with arbitrary user IDs, product IDs, prices, currencies, and timestamps. 3. The endpoint performs no sender authentication and inserts the supplied fields. 4. The attacker repeats the request or submits extreme values because no replay prevention, rate limiting, or bounds validation is shown. 5. Reporting systems consume the forged records and produce corrupted revenue, conversion, churn, or cohort metrics. ### Impact Assessment An attacker could poison business analytics, falsely inflate or reduce reported revenue, associate events with arbitrary us ...[truncated 261 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Authenticate webhook requests using a dedicated, high-entropy RevenueCat authorization value. 2. Reject unauthorized requests before performing database operations. 3. Define and enforce a strict request schema, including: - An allowlist of event types. - Bounded identifier lengths. - Finite, nonnegative price values where appropriate. - Valid ISO currency codes. - Plausible timestamp ranges. 4. Use a unique event ID with a database uniqueness constraint to prevent duplicate and replayed events. 5. Apply body-size limits, endpoint rate limiting, and ingestion quotas. 6. Ensure the database layer uses parameterized operations. 7. Return controlled error responses and implement safe asynchronous exception handling. 8. Separate raw webhook storage from trusted financial reporting, and reconcile revenue figures with authoritative store or RevenueCat records. 9. Minimize retained user identifiers and apply an explicit retention and access-control policy. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
server.md:19
Finding
Apple Transaction Identifier Is Consumed from an Unverified JWS<![CDATA[ ## Vulnerability Details **File Location**: `server.md`, lines 19-32 **Vulnerability Type**: Use of unverified signed-token claims in a privileged transaction lookup **Risk Level**: Medium ### Vulnerable Code ```javascript async function verifyiOSTransaction(signedTransactionInfo) { // Decode JWS to get transactionId const decoded = jwt.decode(signedTransactionInfo, { complete: true }); const transactionId = decoded.payload.transactionId; // Verify with App Store Server API const token = generateAppStoreJWT(); const response = await axios.get( `https://api.storekit.itunes.apple.com/inApps/v1/transactions/${transactionId}`, { headers: { Authorization: `Bearer ${token}` } } ); return response.data; } ``` ### Technical Analysis `jwt.decode()` only parses a token; it does not authenticate its signature or validate its claims. The function consequently treats `decoded.payload.transactionId` as usable before proving that Apple signed the JWS. The attacker-controlled transaction identifier is placed into a request sent with the application's App Store Server API bearer token. Although the destination is Apple's official API and the URL structure limits general server-side request forgery, the server is still performing a privileged transaction lookup selected by untrusted input. The function also does not show validation of the bundle ID, application Apple ID, environment, transaction revocation status, product allowlist, or ownership relationship between the transaction and the authenticated application user. ### Attack Path 1. An attacker creates a syntactically valid but unsigned or incorrectly signed JWT containing a chosen `transactionId`. 2. The attacker submits it as `signedTransactionInfo`. 3. `jwt.decode()` accepts and parses the payload without cryptographic verification. 4. The server generates a legitimate App Store API token and requests the attacker-selected transaction from Apple. 5. If the selected ide ...[truncated 698 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Verify the JWS cryptographically before reading or trusting any payload claim. 2. Use Apple's supported App Store Server library and validate the complete Apple certificate chain according to current App Store Server API requirements. 3. Validate all relevant claims and transaction properties, including: - Bundle ID. - Application Apple ID. - Production or sandbox environment. - Product ID against a server-side allowlist. - Revocation and expiration state. 4. Bind the verified transaction or original transaction ID to the authenticated application user and reject attempts to reuse it for another account. 5. Validate the transaction identifier format before constructing the API path. 6. Make transaction processing idempotent by enforcing uniqueness on transaction IDs. 7. Return only the minimum transaction data required by the caller and avoid exposing raw Apple responses. 8. Rate-limit verification requests and redact bearer tokens and transaction details from logs. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
server.md:150
Finding
Android Subscription Verification Unconditionally Reports Valid Status<![CDATA[ ## Vulnerability Details **File Location**: `server.md`, lines 150-166 **Vulnerability Type**: Fail-open subscription-state validation **Risk Level**: High ### Vulnerable Code ```javascript async function verifyAndroidSubscription(packageName, subscriptionId, purchaseToken) { const response = await androidPublisher.purchases.subscriptions.get({ packageName, subscriptionId, token: purchaseToken }); const sub = response.data; return { valid: true, expiryTime: parseInt(sub.expiryTimeMillis), autoRenewing: sub.autoRenewing, cancelReason: sub.cancelReason, // 0 = user, 1 = billing paymentState: sub.paymentState // 0 = pending, 1 = received, 2 = free trial, 3 = pending deferred }; } ``` ### Technical Analysis The function treats every successful Google API response as a valid subscription. It returns `valid: true` without checking whether the subscription has expired, whether payment is pending, whether access has been revoked, or whether the supplied package and product are authorized for the application. A successful API lookup proves that a record exists; it does not prove that the subscription currently authorizes access. The returned fields are informational only unless the function evaluates them and fails closed. The function also accepts `packageName` and `subscriptionId` as parameters. If these values originate from the client rather than trusted server configuration, the client can influence which application and product the backend queries. ### Attack Path 1. An attacker obtains or retains a genuine purchase token for an expired, canceled, pending, or otherwise ineligible subscription. 2. The attacker submits the token to the application's verification endpoint. 3. Google returns the subscription record because the token is structurally valid and exists. 4. The function does not evaluate the expiry time, payment state, or cancellation and revocation conditions. 5. The function returns `valid ...[truncated 527 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Derive subscription validity from authoritative lifecycle fields rather than returning a constant value. 2. Require the subscription expiry time to be later than the trusted server time. 3. Reject pending, deferred, expired, revoked, refunded, or otherwise non-entitling states. 4. Validate acknowledgement and payment status according to the current Google Play subscription API. 5. Keep the expected package name and allowed subscription IDs in trusted server-side configuration; do not accept them directly from the client. 6. Handle cancellation separately from immediate expiration, granting access only until the verified entitlement expiry when appropriate. 7. Process linked purchase tokens and replacement subscriptions to prevent stale tokens from retaining access. 8. Bind each purchase token or order to the authenticated application user and prevent cross-account reuse. 9. Use the current Google Play subscriptions API and implement explicit handling for every documented subscription state. 10. Add tests covering expired, pending, canceled, refunded, revoked, on-hold, grace-period, and replaced subscriptions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (13)

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The analytics examples explicitly log subscription-related events and business-sensitive values such as product identifiers, price, and LTV estimates, but provide no guidance on consent, data minimization, retention, or compliance obligations. In an in-app purchases skill, this omission is material because implementers are likely to copy these patterns directly, potentially resulting in privacy-law violations or overcollection of user-linked monetization data.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The webhook example stores externally supplied subscription event data, including app_user_id and purchase metadata, directly into a database without any warning about privacy handling, validation, or least-data collection. In this skill context, subscription webhooks commonly map to identifiable users, so implementers may persist regulated personal and revenue data unnecessarily or insecurely, increasing privacy and breach exposure.

Missing User Warnings

Medium
Confidence
81% confidence
Finding
This markdown file includes instructions for initiating purchases and restoring prior purchases, which can affect billing state and account entitlements. The document does not include any warning or disclosure to users/developers that these actions may trigger charges, restore paid content, or require clear user communication in the app.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The purchase example uses a product ID string check to decide whether an item is a subscription and then routes subscriptions to `buyNonConsumable`, which is incorrect and brittle. This can cause developers to implement broken purchase flows, mis-handle entitlement types, and potentially grant or deny paid access incorrectly despite later server verification.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The webhook example grants, extends, and revokes access based solely on the request body, without verifying webhook authenticity or integrity. If copied as-is, an attacker could forge webhook requests to manipulate subscription state, leading to unauthorized premium access or account disruption.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The REST API examples demonstrate use of a secret API key and subscriber identifiers but omit critical guidance that these credentials must be kept server-side, never embedded in client apps, and handled as sensitive customer data. In a payments/subscriptions context, readers may copy this pattern into insecure environments, risking API key exposure, unauthorized entitlement changes, or subscriber data disclosure.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# Get subscriber info
curl -X GET \
  "https://api.revenuecat.com/v1/subscribers/$USER_ID" \
  -H "Authorization: Bearer $SECRET_API_KEY"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# Get subscriber info
curl -X GET \
  "https://api.revenuecat.com/v1/subscribers/$USER_ID" \
  -H "Authorization: Bearer $SECRET_API_KEY"

# Grant entitlement (promo)
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# Get subscriber info
curl -X GET \
  "https://api.revenuecat.com/v1/subscribers/$USER_ID" \
  -H "Authorization: Bearer $SECRET_API_KEY"

# Grant entitlement (promo)
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# Get subscriber info
curl -X GET \
  "https://api.revenuecat.com/v1/subscribers/$USER_ID" \
  -H "Authorization: Bearer $SECRET_API_KEY"

# Grant entitlement (promo)
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
const token = generateAppStoreJWT();
  
  const response = await axios.get(
    `https://api.storekit.itunes.apple.com/inApps/v1/transactions/${transactionId}`,
    { headers: { Authorization: `Bearer ${token}` } }
  );
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
const token = generateAppStoreJWT();
  
  const response = await axios.get(
    `https://api.storekit.itunes.apple.com/inApps/v1/transactions/${transactionId}`,
    { headers: { Authorization: `Bearer ${token}` } }
  );
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The guidance explicitly recommends storing raw receipts for dispute resolution but provides no safeguards around minimization, encryption, retention, or access control. Purchase receipts and tokens can contain sensitive transactional identifiers and may be replayable or privacy-relevant if leaked, so telling implementers to store them without handling guidance creates a real security and privacy risk.

Static analysis

No suspicious patterns detected.