Back to skill

Security audit

PayPal

Security checks for vulnerabilities and agentic risk

Overview

This PayPal integration skill is documentation-only and mostly purpose-aligned, but it needs review because some payment examples could produce unsafe financial workflows if copied directly.

Review and strengthen generated code before using it in production. Require authenticated server-side authorization, transaction ownership checks, exact amount and currency validation, merchant validation, idempotency keys or atomic event claims, refund limits, and audit logging for all capture, subscription, webhook, dispute, and refund flows.

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

Error
Location
patterns.md:29
Finding
Incomplete Server-Side Order Validation Before Payment Capture<![CDATA[ ## Vulnerability Details **File Location**: `patterns.md`, lines 29–47 **Vulnerability Type**: Insufficient validation of a client-selected payment order **Risk Level**: High ### Complete Code Snippet ```javascript const captureOrder = async (orderId) => { const token = await getToken(); // First verify the order const order = await fetch(`https://api.paypal.com/v2/checkout/orders/${orderId}`, { headers: { 'Authorization': `Bearer ${token}` } }).then(r => r.json()); if (order.status !== 'APPROVED') { throw new Error(`Invalid order status: ${order.status}`); } // Then capture const capture = await fetch(`https://api.paypal.com/v2/checkout/orders/${orderId}/capture`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); return capture.json(); }; ``` ### Technical Analysis The capture function accepts an `orderId` that the documented frontend submits to the server. Before capture, the server retrieves the selected PayPal order but validates only that its status is `APPROVED`. The function does not verify that: - The order belongs to the authenticated application user or current checkout. - The PayPal order is linked to the expected internal order. - The amount equals the server-side expected amount. - The currency equals the expected currency. - The payee merchant ID is the intended merchant. - The order intent and purchase units match the expected transaction. This omission is especially significant because `SKILL.md` lines 74–84 explicitly state that amount, currency, and merchant must be checked before fulfillment. The operational capture pattern does not implement those checks. ### Attack Path 1. An attacker starts or identifies a lower-value PayPal order available through the same integration. 2. The attacker approves that lower-value order through PayPal. 3. During a higher-value application checkout, the attacker sends the approved lower-value ...[truncated 1085 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat `orderId` as an untrusted identifier, even if PayPal generated it. 2. Load the current internal order from server-side storage using the authenticated user and checkout context. 3. Require an immutable association between the PayPal order and the internal order, such as a server-generated `custom_id` or `invoice_id`. 4. Before capture, validate all relevant PayPal fields: - `order.status === 'APPROVED'` - Expected capture intent - Expected amount using exact decimal-string or minor-unit comparison - Expected currency - Expected payee merchant ID - Expected internal order identifier - Expected purchaser or account association where applicable 5. Reject an order ID that is already associated with another internal order or user. 6. Perform fulfillment only from a verified capture record whose status, amount, currency, merchant, and internal-order association have all been checked server-side. 7. Add tests for lower-value order substitution, currency substitution, cross-user order IDs, reused order IDs, and malformed PayPal responses. 8. Check PayPal HTTP response status and schema before using response fields. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
webhooks.md:31
Finding
Webhook Idempotency Race Allows Duplicate Side Effects<![CDATA[ ## Vulnerability Details **File Location**: `webhooks.md`, lines 31–48 **Vulnerability Type**: Non-atomic check-then-act webhook deduplication **Risk Level**: High ### Complete Code Snippet ```javascript const exists = await db.webhooks.findOne({ eventId: body.id }); if (exists) { return res.status(200).send('Already processed'); } // 3. Process by event type switch (body.event_type) { case 'PAYMENT.CAPTURE.COMPLETED': await handleCaptureCompleted(body.resource); break; case 'CUSTOMER.DISPUTE.CREATED': await handleDisputeCreated(body.resource); break; // ... other events } // 4. Record processing await db.webhooks.insert({ eventId: body.id, processedAt: new Date() }); ``` ### Technical Analysis The handler checks whether an event has been processed, performs its business side effects, and only then inserts the event record. These operations are not atomic. If two deliveries of the same valid webhook run concurrently, both can complete `findOne` before either reaches `insert`. Both requests will then process the same event. A uniqueness constraint at insertion time would not by itself prevent duplicate side effects because the side effects occur before the insert. PayPal legitimately retries webhook deliveries, so exploitation does not necessarily require signature forgery. The condition can occur through normal concurrent retries, delivery delays, or deliberate replay of a previously observed valid request while it is still being processed. ### Attack Path 1. PayPal sends a valid webhook, or multiple valid deliveries of the same event arrive close together. 2. Two handler executions verify the webhook signature successfully. 3. Both executions query `db.webhooks.findOne({ eventId: body.id })`. 4. Neither execution sees an existing event record because no record has yet been inserted. 5. Both execute `handleCaptureCompleted`, `handleDisputeCreated`, or another event handler. 6. Both attempt to insert the event record o ...[truncated 938 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a database-level unique constraint on the PayPal event ID. 2. Atomically claim the event before performing any side effect. Use a transaction, conditional insert, or database upsert that succeeds for only one worker. 3. Store a durable processing state such as: - `processing` - `completed` - `failed` 4. Allow only the worker that successfully creates or transitions the event record to perform processing. 5. Mark the event `completed` in the same transaction as local business-state changes where possible. 6. Design every event handler to be independently idempotent. For example, fulfillment should use a unique transaction or capture identifier and refuse to fulfill the same order twice. 7. Implement safe recovery for events left in `processing` after a crash, using leases, retry counters, and timestamps. 8. Preserve failed events for controlled retries rather than deleting their deduplication records. 9. Add concurrency tests that submit identical verified events simultaneously and assert that the business action executes exactly once. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (16)

External Transmission

Medium
Category
Data Exfiltration
Content
```javascript
// Token expires ~8 hours — handle refresh
const getToken = async () => {
  const res = await fetch('https://api.paypal.com/v1/oauth2/token', {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${Buffer.from(`${clientId}:${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
PayPal webhooks MUST be verified via API call — not simple HMAC:
```javascript
// POST /v1/notifications/verify-webhook-signature
const verification = await fetch('https://api.paypal.com/v1/notifications/verify-webhook-signature', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    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
```javascript
const createOrder = async (amount, currency = 'USD') => {
  const token = await getToken();
  const res = await fetch('https://api.paypal.com/v2/checkout/orders', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${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.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The examples show payment capture, subscription creation, and refund operations without emphasizing that these are financially sensitive, potentially irreversible actions that require explicit user authorization and server-side access control. In an agent skill context, omission of such warnings can encourage unsafe reuse where destructive financial actions are exposed without confirmation or role checks.

External Transmission

Medium
Category
Data Exfiltration
Content
```javascript
// Create product first
const product = await fetch('https://api.paypal.com/v1/catalogs/products', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
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
}).then(r => r.json());

// Then create plan
const plan = await fetch('https://api.paypal.com/v1/billing/plans', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
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 token = await getToken();
  const body = amount ? { amount: { value: amount, currency_code: 'USD' } } : {};
  
  return fetch(`https://api.paypal.com/v2/payments/captures/${captureId}/refund`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
Confidence
68% confidence
Finding
While sending a refund request to PayPal is expected in a payment system, the example documents a refund primitive with no visible authorization, approval workflow, or business-rule validation. In an agent skill context, this can normalize exposing refund capability as a simple callable action, which could lead to unauthorized financial reversals if copied into production without strong controls.

Description-Behavior Mismatch

Low
Confidence
89% confidence
Finding
The manifest describes checkout flows, subscriptions, webhook verification, OAuth handling, and security validation. This file also documents a refund capability, which is a distinct payment operation beyond the stated scope and is not implied by the listed features.

Static analysis

No suspicious patterns detected.