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.
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.
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.
finserv-encryption.data-in-transit.no-apikeys-in-urlsmedium"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"// 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' }),
});
# .env (local only, not committed)
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...