
Refuse access without exposing private records
A refusal should not reveal whether another customer’s record exists. Wildo separates the decision visible to the caller from the detail needed to investigate the refusal.
The authorization trail preserves a signal for repeated attempts while keeping outward responses from becoming a private-record directory.
Example — An account member guesses a record ID
A record outside the person’s accessible scope does not become discoverable through a different “you cannot access this existing record” response. The internal refusal still has a reason for investigation.
For engineers
Role checks that do not depend on the row can refuse before resolving it. A missing or scope-invisible addressed record uses the same outward not-found treatment. Internal storage faults remain faults rather than being rewritten as evidence that a record is absent.
| Case | Meaning to preserve |
|---|---|
| Missing operation role | The caller cannot use this action |
| Missing or inaccessible addressed record | No accessible record can be returned |
| Repository or infrastructure failure | The operation failed; absence was not established |
Keep a useful refusal trail
authorization-denial-audit.backend.utils.ts classifies authorizer-origin not-found outcomes as authorization refusals while excluding unrelated application 404s. The audit policy emits the first refusal and escalating occurrence counts, rather than writing an attacker-controlled number of rows.
The resource-operation authorization path uses this classifier to distinguish a concealed subject from an unrelated application failure. Exact excerpt from authorization-denial-audit.backend.utils.ts:
export function classifyAuthorizationDenial(
error: unknown,
phaseDefaultReason: ResourceOperationDenialReason,
): ResourceOperationDenialReason | undefined {
if (!isWildoBackendError(error)) return undefined;
if (error.type === ErrorType.AUTHORIZATION) {
return error.customMessageReference === ErrorCustomMessageReference.AUTHORIZATIONS_MISSING_INITIATOR
? ResourceOperationDenialReason.MISSING_INITIATOR
: phaseDefaultReason;
}
if (
error.type === ErrorType.NOT_FOUND &&
error.customMessageReference === ErrorCustomMessageReference.AUTHORIZATIONS_VALIDATION_FAILED
) {
return ResourceOperationDenialReason.SUBJECT_NOT_RESOLVABLE;
}
return undefined;
}
The second branch recognizes only a not-found outcome marked by the authorizer. Other failures return undefined here; they keep their own error handling rather than entering the access-refusal series.
The counter is associated with the available user identity, resource, operation, variant and reason; calls without a user identity share the fallback bucket. Walking different target IDs therefore does not create an independent audit bucket for every guessed record.
Do not confuse observation with prevention
The normal authorization decision refuses the request. The audit volume policy does not rate-limit or lock the caller, and it does not normalize response timing. Security monitoring can act on the recorded pattern; application access rules remain the preventive control.