Payment gateway uses cert-based auth, not API keys in URLs
Why it matters
CWE-522 (insufficiently protected credentials) and CWE-319 overlap when API keys appear in URLs: query string parameters are logged by every proxy, CDN, load balancer, and browser history along the full request path. A Stripe secret key in a URL like /api/charge?key=sk_live_... is exposed in server access logs, Cloudflare logs, and any third-party request inspector — long after the original request. PCI-DSS 4.0 Req-4.2 requires that authentication credentials transmitted during payment flows be protected; URL-embedded keys violate this by design. Even a brief window where a key is logged gives an attacker with log access the ability to drain the account or create fraudulent charges.
Severity rationale
Medium because URL-embedded API keys are exposed in logs and browser history, but exploitation requires log access or a compromised intermediary rather than direct network interception.
Remediation
Always pass payment API keys in the Authorization header or via the SDK, never as URL parameters. Stripe's SDK handles this correctly; for direct HTTP calls:
// CORRECT
const response = await fetch('https://api.stripe.com/v1/payment_intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({ amount: '1000', currency: 'usd' }),
});
// NEVER — key appears in server logs:
// fetch(`https://api.stripe.com/v1/charges?key=${process.env.STRIPE_SECRET_KEY}`)
Rotate any key that appeared in a URL immediately — treat it as compromised. Store all secrets in environment variables (.env.local for local dev, Vercel Environment Variables or AWS Secrets Manager for production) and add .env* to .gitignore if not already present.
Detection
- ID:
no-apikeys-in-urls - Severity:
medium - What to look for: Count all payment API calls and enumerate the authentication method used for each (Authorization header, URL parameter, request body, hardcoded). Quote the actual authentication pattern found. Count all hardcoded API keys in source files. An API key in a URL query string does not count as pass — do not pass if any key appears in URLs or is hardcoded.
- Pass criteria: At least 90% of API calls use Authorization headers or request bodies for authentication. Count all API calls — 0 must have keys in URL parameters, 0 must have hardcoded keys. Report the count even on pass (e.g., "3 of 3 API calls use Authorization headers, 0 hardcoded keys, all from process.env").
- Fail criteria: Any API keys visible in URLs, OR any keys hardcoded in source files (even 1 instance).
- Skip (N/A) when: Payment processing is entirely client-side (no server-side processor API calls) — cite the actual integration pattern found.
- Detail on fail:
"1 of 3 API calls passes key in URL: /api/charge?key=sk_live_... in src/app/api/payments/route.ts"or"1 hardcoded Stripe key found in src/services/stripe.ts:15" - Remediation:
- Use Authorization headers for API authentication:
// CORRECT - Stripe SDK handles this const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); // For custom HTTP clients: const response = await fetch('https://api.stripe.com/v1/charges', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount: 1000, currency: 'usd' }), }); - Store all secrets in environment variables:
# .env (local only, not committed) STRIPE_SECRET_KEY=sk_live_... STRIPE_PUBLISHABLE_KEY=pk_live_...
- Use Authorization headers for API authentication:
External references
- cwe · CWE-522 — Insufficiently Protected Credentials
- cwe · CWE-319 — Cleartext Transmission of Sensitive Information
- owasp:2021 · A02 — Cryptographic Failures
- pci-dss:4.0 · Req-4.2 — PAN protected with strong cryptography during transmission
Taxons
History
- 2026-04-18·v1.0.0·Initial import from finserv-encryption·automated