Skip to main content

Common Misconfiguration

Exposed payment gateway keys can lead to financial fraud, unauthorized charges, and compliance violations.

Vulnerable Example

// VULNERABLE - Hardcoded payment keys
const stripe = require('stripe');
const paypal = require('@paypal/checkout-server-sdk');

// Never hardcode these keys!
const STRIPE_SECRET_KEY = 'sk_live_4eC39HqLyjWDarjtT1zdp7dcTYooMQauvdEDq54NiTphI7jx';
const STRIPE_PUBLISHABLE_KEY = 'pk_live_51H3bgKG8vPDgDbmwNbL4nqVGRdDq5rWvZ';

// PayPal credentials
const PAYPAL_CLIENT_ID = 'AXy9r6UmgMXQKvnodataJ3u_lqIMSbhA2kV8tWwFakeID';
const PAYPAL_CLIENT_SECRET = 'EGnHDxD_qRPdaGP2Ioq9BYw6rF5hFakeSecretExample';

// Square API
const SQUARE_ACCESS_TOKEN = 'EAAAEOuLQObrVwJvCvoTsZvvM1YvFakeTokenExample';
const SQUARE_APPLICATION_ID = 'sq0idp-FakeApplicationId123';

// Initialize with hardcoded keys
const stripeClient = stripe(STRIPE_SECRET_KEY);

class PaymentProcessor {
    constructor() {
        this.stripe = stripe(STRIPE_SECRET_KEY);
        this.setupPayPal();
    }
    
    setupPayPal() {
        const environment = new paypal.core.LiveEnvironment(
            PAYPAL_CLIENT_ID,
            PAYPAL_CLIENT_SECRET
        );
        this.paypalClient = new paypal.core.PayPalHttpClient(environment);
    }
}

Secure Example

// SECURE - Using environment variables and secure initialization
const stripe = require('stripe');
const paypal = require('@paypal/checkout-server-sdk');

class SecurePaymentProcessor {
    constructor() {
        this.initializeProviders();
    }
    
    async initializeProviders() {
        // Load from environment variables or secrets manager
        const stripeKey = process.env.STRIPE_SECRET_KEY;
        const paypalClientId = process.env.PAYPAL_CLIENT_ID;
        const paypalSecret = process.env.PAYPAL_CLIENT_SECRET;
        
        // Validate keys exist
        if (!stripeKey || !paypalClientId || !paypalSecret) {
            throw new Error('Payment credentials not configured');
        }
        
        // Initialize Stripe with restricted key
        this.stripe = stripe(stripeKey, {
            apiVersion: '2023-10-16',
            maxNetworkRetries: 2,
            timeout: 20000
        });
        
        // Initialize PayPal with appropriate environment
        const environment = process.env.NODE_ENV === 'production'
            ? new paypal.core.LiveEnvironment(paypalClientId, paypalSecret)
            : new paypal.core.SandboxEnvironment(paypalClientId, paypalSecret);
            
        this.paypalClient = new paypal.core.PayPalHttpClient(environment);
    }
    
    // Use webhook endpoints for secure payment confirmation
    async handleStripeWebhook(request) {
        const sig = request.headers['stripe-signature'];
        const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
        
        try {
            const event = this.stripe.webhooks.constructEvent(
                request.body,
                sig,
                webhookSecret
            );
            
            // Process webhook event
            await this.processPaymentEvent(event);
        } catch (err) {
            console.error('Webhook signature verification failed');
            throw err;
        }
    }
}

// Use a secrets manager for production
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');

async function getPaymentCredentials() {
    const client = new SecretManagerServiceClient();
    
    const [stripeSecret] = await client.accessSecretVersion({
        name: 'projects/my-project/secrets/stripe-key/versions/latest',
    });
    
    return {
        stripe: stripeSecret.payload.data.toString(),
        // ... other credentials
    };
}

Detection Patterns

  • Stripe Secret Key: `sk_(live|test)_[0-9a-zA-Z]{24,}`
  • PayPal Client Secret (Live): `E[C|V][A-Za-z0-9-_]{40,}`
  • Square Access Token: `EAAA[a-zA-Z0-9-_]{60,}`
  • Braintree Access Token: `access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32,}`

Prevention Best Practices

  1. Use Restricted API Keys: Do not use your root API key. Create “restricted” keys with the minimum permissions needed (e.g., a key that can only create charges but not issue refunds).
  2. Verify Webhook Signatures: Never trust a webhook just because it hits your endpoint. An attacker can send fake “payment successful” requests. Always verify the request’s signature using your dedicated webhook secret (as shown in the secure example).
  3. Use Separate Keys Per Environment: Your sk_live_... (live key) is a top-tier secret. Your sk_test_... (test key) is not. Never use your live keys in development, testing, or staging environments.
  4. Ensure PCI DSS Compliance: This is a set of security standards for handling card data. The easiest way to comply is to never let sensitive data (like a full credit card number or CVC) touch your server.
  5. Implement Rate Limiting and Fraud Detection: Protect your payment endpoints from “carding attacks,” where bots test lists of stolen credit cards. Use rate limiting and built-in fraud tools (like Stripe Radar).
  6. Log All Payment Transactions: Maintain a secure, immutable audit log of all API calls and transactions. This is critical for tracing fraudulent activity or a leaked key.
  7. Use Client-Side Tokenization: Use the gateway’s JavaScript library (e.g., Stripe.js, PayPal Smart Buttons) to send card data directly from the user’s browser to the payment gateway. The gateway will give you back a safe, one-time-use token (e.g., tok_...). Your server only ever touches the token, not the card number.
  8. Rotate API Keys Regularly: Even if you follow all other practices, you should regularly rotate (delete and create new) your API keys (e.g., every 90 days). This limits the window of opportunity if a key is ever silently leaked.