Back to skill

Security audit

Paddle

Security checks for vulnerabilities and agentic risk

Overview

This Paddle payment-integration skill is mostly purpose-aligned, but it includes high-impact billing operations and webhook security examples that are under-scoped for a payments workflow.

Review this skill before installing if you may use it against a live Paddle account. Keep API keys out of ~/paddle/memory.md, require explicit confirmation before creating customers or changing subscriptions, prefer sandbox endpoints until ready for production, and replace the webhook verification examples with hardened code that checks timestamp freshness, validates signature format, handles malformed input safely, and minimizes logging of webhook payloads.

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

T09 · Insecure Skill Coding Practices

Warning
Location
webhooks.md:36
Finding

Webhook Verification Does Not Enforce Timestamp Freshness

Content
View full analysis

Vulnerability Details

File Location: webhooks.md, lines 36–48
Vulnerability Type: Webhook replay vulnerability
Risk Level: Medium

Vulnerable Code

javascript
function verifyWebhook(rawBody, signature, secret) {
  const ts = signature.split(';').find(p => p.startsWith('ts=')).split('=')[1];
  const h1 = signature.split(';').find(p => p.startsWith('h1=')).split('=')[1];
  
  const signedPayload = `${ts}:${rawBody}`;
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(h1),
    Buffer.from(expectedSig)
  );
}

The corresponding Python example has the same omission:

python
def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    parts = dict(p.split('=') for p in signature.split(';'))
    ts = parts['ts']
    h1 = parts['h1']
    
    signed_payload = f"{ts}:{raw_body.decode()}"
    expected_sig = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(h1, expected_sig)

Technical Analysis

Both webhook verification examples include the sender-provided timestamp in the HMAC input but never verify that the timestamp is recent. HMAC verification proves that a request was signed with the webhook secret, but without a freshness check it does not prove that the request is current.

An attacker who captures a legitimate webhook body and its Paddle-Signature header can retransmit the unchanged request later. Because the timestamp remains part of the original signed payload, the HMAC remains valid indefinitely.

The separate event-ID idempotency example can mitigate exact duplicate processing when it is implemented with durable, atomic storage. It does not replace timestamp validation and may not protect deployments that omit the option ...[truncated 1550 chars]

Remediation
View remediation

Remediation Suggestions

  • Parse ts as a strict integer and reject invalid, missing, nonnumeric, or duplicated timestamp fields.
  • Compare the timestamp with the server's current time and reject requests outside a short configurable tolerance, such as five minutes.
  • Perform freshness validation before processing or parsing the event body.
  • Retain constant-time HMAC comparison after validating signature format and length.
  • Continue using event_id for durable idempotency as a separate defense.
  • Make event processing and insertion of the processed-event record atomic, preferably in one database transaction with a unique constraint on event_id.
  • Retain processed event IDs for a period aligned with Paddle's delivery and retry behavior.
  • Ensure the server clock is synchronized through a trusted time source.
  • Log rejection metadata without recording webhook secrets, complete signatures, or sensitive payload contents.

T09 · Insecure Skill Coding Practices

Note
Location
webhooks.md:36
Finding

Malformed Webhook Signatures Can Cause Unhandled Exceptions

Content
View full analysis

Vulnerability Details

File Location: webhooks.md, lines 36–55
Vulnerability Type: Improper input validation and exception handling
Risk Level: Low

Vulnerable Code

javascript
function verifyWebhook(rawBody, signature, secret) {
  const ts = signature.split(';').find(p => p.startsWith('ts=')).split('=')[1];
  const h1 = signature.split(';').find(p => p.startsWith('h1=')).split('=')[1];
  
  const signedPayload = `${ts}:${rawBody}`;
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(h1),
    Buffer.from(expectedSig)
  );
}

// In your webhook handler
app.post('/webhooks/paddle', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['paddle-signature'];
  
  if (!verifyWebhook(req.body, signature, process.env.PADDLE_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

The Python example is similarly unsafe:

python
def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    parts = dict(p.split('=') for p in signature.split(';'))
    ts = parts['ts']
    h1 = parts['h1']
    
    signed_payload = f"{ts}:{raw_body.decode()}"
    expected_sig = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(h1, expected_sig)

Technical Analysis

The examples treat the unauthenticated Paddle-Signature header as structurally valid before checking its signature.

In the JavaScript implementation:

  • A missing header causes signature.split(...) to throw.
  • A missing ts or h1 component causes .find(...) to return undefined, after which .split(...) throws.
  • Node.js crypto.timingSafeEqual throws when the supplied buffers have different lengths. An attac ...[truncated 1880 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject requests immediately when the signature header is absent or is not a string.
  • Parse signature components with a defensive parser that tolerates unknown fields but rejects malformed, missing, or duplicated required fields.
  • Require ts to be a strictly formatted integer.
  • Require h1 to contain the expected number of hexadecimal characters and decode it explicitly before comparison.
  • Verify that compared buffers have identical lengths before calling crypto.timingSafeEqual.
  • Avoid decoding the body merely to build the signed payload when raw-byte concatenation can preserve the exact request representation.
  • Wrap signature parsing and verification in exception handling that fails closed and returns HTTP 400 or 401.
  • Apply request-size limits, endpoint rate limiting, and safe logging to reduce denial-of-service exposure.
  • Add tests for missing headers, missing fields, duplicate fields, malformed separators, invalid hexadecimal data, oversized values, invalid timestamps, and unequal signature lengths.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (17)

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 77)May include surrounding context.

md
## Common Traps

- **Hardcoding price IDs** → Use environment variables, prices change between sandbox/production
- **Processing webhooks without verification** → Security vulnerability, anyone can fake events
- **Ignoring `past_due` state** → User loses access during retry window, bad UX
- **Not handling proration** → Confusing charges when users upgrade/downgrade mid-cycle
- **Testing with production keys** → Real charges, angry customers, refund headaches

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 8)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 8)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 22)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 33)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 41)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 47)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 57)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 67)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 79)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 85)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 93)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · api.md (reported line 99)May include surrounding context.

All requests require an API key in the Authorization header:

bash
curl https://api.paddle.com/customers \
  -H "Authorization: Bearer $PADDLE_API_KEY"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file documents cancellation, pausing, and immediate resumption of subscriptions, which can affect billing state and user service, but it provides no warning about the operational impact or need for caution. Under the markdown-specific missing-warning rule, user-facing documentation should disclose behaviors that can affect user data, billing, or system integrity.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill directs the agent to persist user preferences and setup details to ~/paddle/memory.md without telling the user that this information will be retained across sessions. Even if the stored data is not highly sensitive by itself, it can include business context, infrastructure details, and behavioral preferences that users may not expect to be written to disk, creating a privacy and consent issue.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This section explicitly defines an internal memory file containing environment, product type, pricing model, tech stack, and requirements, but does so without any user-facing notice or consent mechanism. In the context of payments integration, these details can reveal operational and commercial information, making undisclosed retention more concerning than generic preference storage.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

This markdown file describes processing webhook payloads that include identifiers such as customer_id and internal user_id, and earlier sections instruct implementers to receive and process subscription and transaction events. The document does not include any warning about handling potentially sensitive customer/account data, retention, or secure logging, which is relevant for a skill description that affects privacy-sensitive data flows.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.