skip to content

In a field-level read denial, what does omitting the field, returning it masked, or refusing the whole response each tell the caller?

level: seniorimportance: should knowfreq 45%

answer

  1. the shape itself leaks
  2. silence versus a placeholder
  3. a placeholder confirms the field is populated
  4. hiding existence is a MAY, not a MUST

basics

~20 s

Each shape leaks a different amount. Omission is the quietest, a masked placeholder confirms the field exists and holds content, and refusing the whole response confirms the same while also denying the caller data they are entitled to read.

solid answer

~50 s

There are four shapes and you pick per field, not per product. **Omit** the field, and the caller cannot distinguish "you may not read it" from "nothing is recorded" — quiet, but only where absence is already a normal outcome for that field. Return it **null**, and you have said the same thing with a value that is also ambiguous with empty. Return a **masked placeholder**, and you have confirmed the field exists and is populated. **Refuse the whole response** with `403`, and you have confirmed it too, plus lost the caller the rest of the record. For a pupil's safeguarding note, "there is a note here you may not read" is the disclosure. The framework-neutral anchor sits one level up: RFC 9110 Section 15.5.4 lets a server hide a forbidden resource behind `404` rather than `403` — a MAY, not a MUST — and the same argument applies to a field.

code

json · 8 lines
json
{
  "pupilId": "P-40912",
  "formGroup": "9B",
  "predictedGrade": "B",
  "guardianPhone": "+44 7700 900318",
  "safeguardingFlag": true,
  "safeguardingNote": "Reviewed 12 Sep; see pastoral file."
}

go deeper

for a junior

Know that a denied field does not have to come back as an error. The server can leave it out of the response body altogether, and the caller then has nothing to read into.

for a middle

Explain the four shapes and what each costs the client: omission needs an optional field in the schema, a placeholder needs the client to recognise the marker, a full refusal needs a fallback view.

for a senior

Demonstrate that you weigh disclosure against client breakage. Name the shape you chose for a specific field and why, and mention that per-caller bodies change what a shared cache may reuse.

for a principal

Own the rule for the whole product: decide once whether the existence of a sensitive field is itself confidential, record that decision, and make every new field inherit it rather than re-argue it.

## Four shapes for one denied field A caller is entitled to the record but not to one field on it. A class teacher opens a pupil's record and must not see `safeguardingNote`. The server has four ways to answer, and the choice is a **disclosure decision**, not a serialisation detail: 1. **Omit** the field from the response body entirely. 2. **Return it null**, present as a key with no value. 3. **Return a masked placeholder** — a fixed marker in place of the real content. 4. **Refuse the whole response** with `403`, giving the caller nothing. ## What each one discloses | Shape | What the caller can conclude | Cost to the client | Fits when | |---|---|---|---| | Omit | Nothing, if absence is already normal for this field | Field must be optional in the schema | The field's existence on this record is itself sensitive | | Null | The field exists on the type; ambiguous with "not recorded" | Client must treat null as "unknown", not "empty" | The type is public and only values are protected | | Masked | The field exists **and is populated** | Client must recognise the marker | The caller needs to know something is there | | Refuse all | The same as masked, plus no record at all | Client needs a fallback view | The record is unusable without the field | The row that catches people is **omit**. It is quiet precisely because it is ambiguous with "no note exists" — and that ambiguity evaporates if the field is otherwise always present. If every authorized reader sees `safeguardingNote` on every pupil, then its absence for one reader is as loud as an explicit error. Omission buys silence only where the field is genuinely sometimes absent for everyone. ## When the existence of the field is the secret For most fields the protected thing is the value. A guardian's phone number is confidential; the fact that pupils have guardians is not. For a small number of fields the protected thing is **whether there is anything there at all**. A safeguarding flag is the canonical case: knowing that a pupil has one is the sensitive fact, and a per-field error saying "you are not permitted to read `safeguardingNote`" confirms both that the field applies to this pupil and — if the error is only emitted when the field is populated — that it holds content. An explicit per-field error is the most honest shape and the loudest one, and for this class of field that combination is a defect rather than good manners. The symmetric trap is a response whose **shape varies with the data**. If the server omits the field when the caller may not read it, but the client can tell from response size, field ordering, or a count elsewhere in the payload that something was removed, the omission has leaked anyway. Whatever shape you choose has to be the shape for every pupil, not only the ones with something to hide. ## The status-code argument one level up RFC 9110 Section 15.5.4 says a server that wishes to hide the existence of a forbidden target resource **MAY** answer `404` instead of `403`. Two things matter about that sentence for field-level work: - It is a **MAY**. The specification is granting permission for a local decision, not prescribing behaviour, so a team arguing "we must return 404" is misreading it. - It is about a **resource**, and the reasoning transfers to a field without the specification having to say so: if confirming existence is the disclosure, the answer must not confirm existence, whatever granularity the thing sits at. The price is the same at both levels. A caller who genuinely has no access cannot tell a permission problem from a typo, and neither can the support desk fielding the call. ## Consequences for callers and caches - **Typed clients break on omission.** A client generated from a schema that marks the field required will fail to parse a body that lacks it. Either every conditionally-visible field is optional in the published schema, or you choose the placeholder shape and pay in disclosure. - **Bodies now vary by principal.** Two staff fetching the same pupil get different field sets from the same URL, so a shared cache must be told what the selection depends on — `Vary: Authorization` where identity rides in that header, `Vary: Cookie` where it rides in a session cookie — or the response must not be shared at all. - **Write the choice down per field.** Because the decision is per field rather than per endpoint, the only way it stays consistent as the record grows is for each new field to inherit a recorded decision instead of being argued afresh.

  • Your response bodies now differ per caller for the same URL. What must you tell shared caches?
    Say what the selection depends on. If the caller's identity arrives in the `Authorization` header, mark the response `Vary: Authorization`; if it arrives in a session cookie, `Vary: Cookie`. Where the field sets are numerous enough that the cache would never get a hit anyway, mark the response uncacheable by shared caches instead. Getting this wrong hands one staff member's projection to another.
  • Two staff fetch the same pupil and get bodies with different field sets. Does that break a typed client?
    It does if the published schema marks the field required, because a generated client will fail to parse the shorter body. You have two exits: declare every conditionally-visible field optional, which pushes an "absent means unknown" rule onto every consumer, or switch that field to a masked placeholder so the shape is constant and pay the disclosure instead.

saying these in an interview costs you the question

  • A masked placeholder discloses nothing, since the real value is gone.
  • Every denied field should come back as an explicit per-field error.
  • Returning the field null is always safer than leaving it out.
  • Hiding a forbidden thing behind a 404 is required by the specification.
  • These responses cache like any other; the URL is the whole key.
  • If one field is denied, refusing the entire record is the safe default.