Back to skill

Security audit

Payment Integration Guide

Security checks for vulnerabilities and agentic risk

Overview

This is mostly a normal payment-integration guide, but it includes unsafe webhook logging and verification examples that users should review before relying on it.

Install only if you are comfortable reviewing and correcting the payment code it suggests. Do not copy the raw webhook logging pattern into production, avoid logging headers or payloads without strict redaction, and verify webhook signatures using each provider's current official documentation before handling real payments.

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
SKILL.md:94
Finding
Sensitive Webhook Headers and Payloads Are Written to Application Logs## Vulnerability Details **File Location**: `SKILL.md:94-102` **Vulnerability Type**: Sensitive data exposure through insecure logging **Risk Level**: Medium ### Vulnerable Code ```typescript // Always log webhook events for debugging app.post('/webhook', async (req, res) => { console.log('[Webhook] Received:', { headers: req.headers, body: JSON.stringify(req.body).slice(0, 500), timestamp: new Date().toISOString(), }); // ... process }); ``` ### Technical Analysis The Skill explicitly recommends logging all webhook request headers and the first 500 characters of each request body. Payment-provider webhooks can include customer information, transaction identifiers, order metadata, email addresses, and other sensitive business data. Headers can contain webhook signatures, authorization information, cookies, tracing data, or infrastructure-specific secrets. Limiting the serialized body to 500 characters is not sanitization. Sensitive values frequently occur near the beginning of a payload and would still be recorded. The recommendation also contradicts the Skill's separate instruction not to log sensitive data. Although this repository contains documentation rather than an automatically executed server, developers following the supplied example could deploy the vulnerable logging behavior in a production payment endpoint. ### Attack Path 1. A developer copies or generates a webhook endpoint based on the Skill's debugging example. 2. The endpoint receives legitimate payment events containing customer and transaction data, or attacker-generated requests containing chosen sensitive content. 3. The endpoint records all request headers and up to 500 characters of the serialized body. 4. The records are retained in local logs or forwarded to a centralized logging service. 5. An operator, compromised logging account, support user, or attacker with read access to the logging system retrieves the e ...[truncated 763 chars]
Remediation
## Remediation Suggestions - Remove the recommendation to log raw webhook headers and bodies. - Log only an allowlist of non-sensitive fields, such as provider name, event ID, event type, processing result, timestamp, and an internal correlation ID. - Never log authorization, cookie, webhook-signature, access-token, card, customer, or payment-instrument fields. - If payload diagnostics are essential, implement structured recursive redaction before logging and disable detailed payload logging in production. - Apply least-privilege access controls, encryption, short retention periods, and audit monitoring to payment-related logs. - Replace the example with a safe pattern such as: ```typescript console.log('[Webhook] Received', { provider: 'example-provider', eventId: verifiedEvent.id, eventType: verifiedEvent.type, timestamp: new Date().toISOString(), }); ```

T09 · Insecure Skill Coding Practices

Warning
Location
providers.md:450
Finding
Razorpay Webhook HMAC Is Calculated over Reserialized JSON Instead of the Raw Request Body## Vulnerability Details **File Location**: `providers.md:450-459` **Vulnerability Type**: Incorrect webhook signature verification **Risk Level**: Medium ### Vulnerable Code ```typescript // Razorpay webhook uses a SEPARATE webhook secret (not your API key_secret) const webhookSignature = req.headers['x-razorpay-signature']; const expectedSig = crypto .createHmac('sha256', process.env.RAZORPAY_WEBHOOK_SECRET!) .update(JSON.stringify(req.body)) .digest('hex'); if (webhookSignature === expectedSig) { // Process webhook event } ``` ### Technical Analysis Webhook HMAC verification must be performed over the exact bytes received from the provider. This example instead computes the HMAC over `JSON.stringify(req.body)`, which assumes middleware has already parsed the request. Parsing and reserializing JSON can modify whitespace, property order, escape sequences, Unicode representation, and numeric formatting. The resulting byte sequence may therefore differ from the body Razorpay originally signed, causing valid webhook signatures to be rejected. The code also compares signatures with the ordinary `===` operator. Cryptographic values should be converted to equal-length buffers and compared with `crypto.timingSafeEqual` to avoid an unnecessary timing side channel. Timing differences alone do not make HMAC forgery practical, but constant-time comparison is the appropriate defensive implementation. ### Attack Path 1. A developer adopts the example in an application where JSON body-parsing middleware runs before the webhook handler. 2. Razorpay sends a legitimate, correctly signed webhook. 3. Middleware parses the raw request body and discards or fails to retain the original bytes. 4. `JSON.stringify(req.body)` produces a representation that differs from the bytes signed by Razorpay. 5. The calculated HMAC does not match the webhook signature. 6. The application rejects or fails to process the legitimate ...[truncated 711 chars]
Remediation
## Remediation Suggestions - Configure the webhook route to retain the exact raw request bytes before JSON parsing. - Compute the HMAC over the raw bytes rather than `JSON.stringify(req.body)`. - Validate that the signature header exists, has the expected hexadecimal format, and has the correct length. - Compare equal-length signature buffers with `crypto.timingSafeEqual`. - Parse and process the JSON only after successful signature verification. - Reject invalid signatures with an appropriate HTTP error and avoid including secret or signature values in logs. - Use a corrected pattern such as: ```typescript const rawBody: Buffer = req.body; const suppliedHex = req.headers['x-razorpay-signature']; if (typeof suppliedHex !== 'string' || !/^[a-f0-9]{64}$/i.test(suppliedHex)) { return res.status(400).send('Invalid signature'); } const expected = crypto .createHmac('sha256', process.env.RAZORPAY_WEBHOOK_SECRET!) .update(rawBody) .digest(); const supplied = Buffer.from(suppliedHex, 'hex'); if ( supplied.length !== expected.length || !crypto.timingSafeEqual(supplied, expected) ) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(rawBody.toString('utf8')); // Process the verified event. ```
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (12)

Credential Access

High
Category
Privilege Escalation
Content
| Apple Pay | Via processor (Stripe recommended) | N/A (processor handles) | Cents (via processor) | iOS/Safari users |
| Google Pay | Via processor (Stripe recommended) | N/A (processor handles) | Cents (via processor) | Android/Chrome users |
| Razorpay | REST, Basic Auth | key_id:key_secret | Paise (1/100 rupee) | India market |
| Square | REST, OAuth2 / Bearer | Access token | Cents | US SMBs, POS integration |

## Cross-Cutting Topics
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Core Flow — Orders API v2

```typescript
// Step 1: Get access token
const auth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
const tokenRes = await fetch('https://api-m.sandbox.paypal.com/v1/oauth2/token', {
  method: 'POST',
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Core Flow — Orders API v2

```typescript
// Step 1: Get access token
const auth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
const tokenRes = await fetch('https://api-m.sandbox.paypal.com/v1/oauth2/token', {
  method: 'POST',
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 8. Square

**API Model**: REST, OAuth2 or Access Token.
**Auth**: Bearer token (sandbox or production access token).
**Base URL**: `https://connect.squareupsandbox.com/v2` (sandbox) / `https://connect.squareup.com/v2` (live)
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

External Transmission

Medium
Category
Data Exfiltration
Content
**API Model**: RESTful, versioned (e.g. `2024-12-18.acacia`). Client libraries handle versioning.
**Auth**: Secret key (`sk_test_...` / `sk_live_...`) as Bearer token.
**Base URL**: `https://api.stripe.com/v1`

### Core Flow — PaymentIntents (recommended)
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
```typescript
// Step 1: Get access token
const auth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
const tokenRes = await fetch('https://api-m.sandbox.paypal.com/v1/oauth2/token', {
  method: 'POST',
  headers: {
    'Authorization': `Basic ${auth}`,
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
const { access_token } = await tokenRes.json();

// Step 2: Create order
const orderRes = await fetch('https://api-m.sandbox.paypal.com/v2/checkout/orders', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${access_token}`,
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
**API Model**: REST V3 (JSON), RSA-SHA256 signature auth + platform certificates.
**Auth**: Merchant ID + API v3 key + merchant certificate (serial number + private key).
**Base URL**: `https://api.mch.weixin.qq.com/v3`

### V3 Signature Construction
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
## 7. Razorpay

**API Model**: REST, Basic Auth (`key_id:key_secret`).
**Base URL**: `https://api.razorpay.com/v1`
**Currency**: INR (amounts in **paise**, not rupees — 1 rupee = 100 paise)

### Core Flow — Orders → Checkout → Verify
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
## 7. Razorpay

**API Model**: REST, Basic Auth (`key_id:key_secret`).
**Base URL**: `https://api.razorpay.com/v1`
**Currency**: INR (amounts in **paise**, not rupees — 1 rupee = 100 paise)

### Core Flow — Orders → Checkout → Verify
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
```typescript
// Step 1: Create Order (server-side)
const order = await fetch('https://api.razorpay.com/v1/orders', {
  method: 'POST',
  headers: {
    'Authorization': `Basic ${Buffer.from(`${KEY_ID}:${KEY_SECRET}`).toString('base64')}`,
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
### Core Flow — Payments API

```typescript
const response = await fetch('https://connect.squareupsandbox.com/v2/payments', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${ACCESS_TOKEN}`,
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.