# Certificate validation for payment processor API endpoints

- **Pattern:** `ab-001422` (`finserv-encryption.data-in-transit.certificate-validation`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001422
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001422)

## Why it matters

CWE-295 (improper certificate validation) and CWE-319 (cleartext transmission) both apply when `rejectUnauthorized: false` is set on payment processor connections. This flag tells Node.js to accept any certificate — including expired, self-signed, or attacker-controlled certificates — making TLS meaningless. An attacker positioned between your server and the payment processor can intercept payment requests, capture API keys, and manipulate transaction amounts without detection. PCI-DSS 4.0 Req-4.2 requires that cardholder data be protected during transmission, which presupposes that the TLS certificate chain is actually verified. NIST SC-8 reinforces this. This vulnerability is commonly introduced as a development workaround and accidentally shipped to production.

## Severity rationale

High because a single `rejectUnauthorized: false` in a payment API call completely nullifies TLS and enables undetectable MITM attacks against financial transactions.

## Remediation

Remove every instance of `rejectUnauthorized: false` from payment processor calls. Modern Stripe and Plaid SDKs validate certificates by default — use the SDK rather than raw `fetch` or `axios` for payment API calls:

```typescript
// CORRECT — Stripe SDK validates certs automatically
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// If you must use a raw HTTP client, never override cert validation:
const response = await fetch('https://api.stripe.com/v1/charges', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}` },
  // No agent override, no rejectUnauthorized
});

// NEVER do this — even in dev:
// const agent = new https.Agent({ rejectUnauthorized: false });
```

Search the codebase for `rejectUnauthorized` and `NODE_TLS_REJECT_UNAUTHORIZED` before each production deploy. If a dev environment requires connecting to a self-signed cert, use a CA bundle override instead of disabling validation entirely.

## Detection

- **ID:** `certificate-validation`
- **Severity:** `high`
- **What to look for:** Count all HTTP client calls to payment processors (fetch, axios, node-fetch). For each, verify certificate validation is enabled — quote the actual `rejectUnauthorized` setting found. Enumerate all instances of disabled SSL verification. Any `rejectUnauthorized: false` does not count as pass — do not pass if any API call disables certificate validation.
- **Pass criteria:** At least 100% of payment processor API calls use HTTPS with certificate validation enabled (default in modern libraries). Count all external API calls — 0 must have disabled certificate verification. Report the count even on pass (e.g., "4 of 4 payment API calls use HTTPS with certificate validation, 0 instances of rejectUnauthorized: false").
- **Fail criteria:** Any certificate verification is explicitly disabled (even 1 instance of `rejectUnauthorized: false`), or any payment API call uses HTTP.
- **Skip (N/A) when:** Payment processor is handled entirely client-side (e.g., Stripe Elements) with no server-side API calls — cite the actual integration pattern found.
- **Detail on fail:** `"1 of 3 payment API calls has rejectUnauthorized: false — certificate validation disabled in src/services/payment.ts:42"` or `"2 of 4 Stripe calls use HTTP instead of HTTPS"`
- **Cross-reference:** Check `finserv-encryption.data-in-transit.tls-12-minimum` for TLS version configuration.
- **Remediation:**
  - **Verify HTTPS usage:**
    ```typescript
    // Stripe SDK automatically validates certificates
    import Stripe from 'stripe';
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
    const payment = await stripe.charges.create({ /* ... */ });

    // For custom HTTP calls:
    const https = require('https');
    const options = {
      hostname: 'api.stripe.com',
      port: 443,
      path: '/v1/charges',
      method: 'POST',
      rejectUnauthorized: true,  // Ensure this is true (default)
      agent: new https.Agent({ rejectUnauthorized: true })
    };
    ```
  - **Never disable certificate verification:**
    ```typescript
    // WRONG - DO NOT USE
    const response = await fetch('https://api.stripe.com/...', {
      rejectUnauthorized: false  // Dangerous!
    });

    // CORRECT
    const response = await fetch('https://api.stripe.com/...');  // Default validation
    ```

## External references

- cwe CWE-295
- cwe CWE-319
- owasp A02
- nist SC-8
- pci-dss Req-4.2

Taxons: cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-001422
