An SDK error message should never contain credentials, full request or response bodies, personal data, or internal server details such as stack traces and file paths. Give callers a stable error code, the HTTP status, a short human message and a request ID that support can look up.
Why errors leak
Exceptions travel. They get logged by the calling application, sent to an error tracker, pasted into tickets and shown to end users. If your SDK builds its message by dumping the request, anything sensitive in that request goes along for the ride.
MITRE tracks this as CWE-209, generation of an error message containing sensitive information, and the sibling weakness CWE-532, insertion of sensitive information into a log file. The OWASP Error Handling Cheat Sheet shows the defensive pattern: a global handler that returns a generic response for unexpected errors while the details stay in server side logs.
What belongs in an error
| Include | Leave out |
|---|---|
| Stable machine readable code | Authorization header or API key |
| HTTP status | Full request URL if it has a query secret |
| Short human message | Request and response bodies |
| Request ID or trace ID | Stack traces from your servers |
| Retry hint, when relevant | Internal hostnames, paths, SQL |
| Link to docs for the code | Another customer's identifiers |
Steps to build a safe error type
- Define one error class with fields for code, status, message and request ID. Keep the fields few and stable.
- Build the message from templates, never by interpolating the raw request. Parameters that are safe to echo, such as a resource type, can be added one by one.
- Strip credentials from any URL before it enters an error. Replace query values with a placeholder.
- Keep the raw response body out of the default string form. If callers need it for debugging, expose it as an explicit property they must read on purpose, and document that it may contain sensitive data.
- On the server, return the same generic shape for authentication failures whether the user or the password was wrong, so errors do not reveal which accounts exist.
- Add a unit test that sets a recognisable fake secret in every input, triggers each error path, and fails if the fake secret appears in the message, the string form or the serialised form.
The validation error trap
Validation errors are the subtle case. Telling a caller that the field named email is not valid is useful. Echoing back the full submitted value is a leak if that value is a password, a token or someone's personal data. Return the field name and the rule that failed, and leave the value out.
Consistency helps defenders too
A stable code per failure lets customers write alerting that does not depend on parsing English. It also lets you change wording without a breaking change, because the code is the contract and the message is a hint. Announce new codes in the changelog so customers can update handlers.
Quick check
Is it ever fine to include the response body in an exception?
As an opt in property, yes, with documentation that it may hold sensitive data. It should not be part of the default message that gets logged automatically.
What is a request ID for?
It lets a customer give support one value that locates the exact request in your logs, so the error message itself can stay short and safe.
Safeguard's policies and gates can check pull requests for risky patterns, and its dependency scanning shows what your SDK pulls in. Try the free scan.
Sources
- CWE-209: Generation of Error Message Containing Sensitive Information
- CWE-532: Insertion of Sensitive Information into Log File
- OWASP Error Handling Cheat Sheet
Last reviewed: 19 October 2026.