Credentials and integrations
Treat third-party API data as untrusted
Validate upstream responses, constrain redirects and destinations, set timeouts and size limits, and avoid inheriting a provider’s compromise.
A provider you trust can still fail, change, or get compromised. Its response crosses a trust boundary on the way into your system, same as a request from a user.
TLS proves you’re talking to the right server. It says nothing about whether the response has the right shape, or whether the numbers inside make sense.
Validate the response like user input
Check status, content type, schema, size, and meaning. A tax provider may return an HTML outage page. A compromised one may redirect you to a private address.
Here’s how I call a tax rate API with those failures in mind:
const res = await fetch('https://tax-api.example.net/v2/rates?country=IT', {
redirect: 'error', // an unexpected redirect is a failure
signal: AbortSignal.timeout(3000),
})
if (!res.ok) throw new Error(`tax api returned ${res.status}`)
const contentType = res.headers.get('content-type') ?? ''
if (!contentType.includes('application/json')) {
throw new Error('tax api returned non-JSON') // the outage-page case
}
redirect: 'error' turns any redirect into a failure instead of following it somewhere you didn’t choose. The 3-second timeout means a slow provider can’t hold your request open forever.
Then parse the body into a bounded schema, exactly like a client request. A tax rate of -2 or 9999 should fail validation. It should not flow into invoice totals because “the provider is trusted”.
Cap the size before you read the whole thing into memory:
const length = res.headers.get('content-length')
if (length && Number(length) > 100_000) {
throw new Error('tax api response too large')
}
Chunked responses have no length header, so enforce the same cap while streaming.
Keep upstream text out of interpreters
Never pass upstream text into SQL, HTML, shell commands, or AI prompts without the same controls you use for user input. A provider’s “company name” field rendered unescaped into your dashboard is stored XSS with one extra hop.
Parameterize and escape no matter where the bytes came from. A paid API contract changes nothing about what’s in them.
Bound the failure, not just the request
Short timeouts protect one request. Bounded retries protect your whole service. Five automatic retries can turn a provider outage into your own resource exhaustion. A controlled fallback keeps the failure visible to operators instead.
The realistic failure is quiet. The provider gets slow. Your retries pile up. Your API exhausts its worker pool serving requests that were never going to succeed, and your own users start seeing timeouts for something unrelated.
Try this on one integration: stub normal, oversized, slow, redirected, malformed, and hostile provider responses. Record the timeout and size limits firing. Then prove no bad response reached storage, HTML, SQL, or a shell.
Lesson completed