When a FHIR request is denied, the response itself can leak information. HL7's security page discusses an empty Bundle, 404, 403 and 401, each with a trade off. Pick one policy per case and apply it consistently, including for search, _include and batch requests.
What each response tells the caller
The security page describes these options and notes they have different information leakage properties. In plain terms:
- 401 says the caller is not authenticated. It is fine when no valid token was presented, and it tells the client to authenticate.
- 403 says the caller is authenticated but not allowed. It also confirms that the resource, or at least the route, exists.
- 404 says not found. Returned for a resource the caller may not see, it hides existence, but it can confuse clients and make troubleshooting harder.
- Empty Bundle for a search says there are no results. It hides existence but can mislead a clinician into thinking there is no data.
The right choice depends on the sensitivity of the fact that a record exists. For some data, such as behavioral health or reproductive care, even existence may be sensitive.
Where access control gets hard
The security page lists access control considerations beyond a plain read:
- Chained searches, where a search condition reaches into a resource the caller cannot read.
- _include and _revinclude, which pull in related resources.
- Batch and transaction processing, where one request carries many operations.
- Break the glass, an emergency override that needs its own policy and audit.
A server that checks authorization on the primary resource only can leak data through any of these. An attacker who cannot read an Observation might still learn something by searching Patient with a chained condition on it.
A policy you can write down
- Decide per resource type whether existence is sensitive.
- For sensitive types, return 404 or an empty result to unauthorized callers, and log the real reason internally.
- For everything else, use 403 so developers can debug.
- Re-check authorization on every resource that appears in a response, including included ones, and inside every batch entry.
- Test with a low privilege token and try chained and included paths.
Timing and size also leak
Status codes are not the only signal. A denied request that returns fast while an allowed but empty search returns slowly lets a caller guess. Likewise, a Bundle total that counts hidden resources discloses how many exist. Make sure totals and paging links reflect only what the caller may see.
Log carefully
The FHIR security page says HTTP logs, even URL only logs, are as sensitive as the resources. A denied search logged with its full query string can store a patient identifier in a place with weaker controls.
Also write an AuditEvent for denials, since a run of denied requests from one client is a useful signal. See AuditEvent vs Provenance.
Quick check
Is 403 always safer than 404?
No. 403 confirms the resource exists. If existence is sensitive, 404 or an empty result leaks less, at the cost of clearer debugging.
Sources
Last reviewed: 19 October 2026.