skip to content

When a handler binds the request body onto a domain object by field name, what is over-posting and why is it dangerous?

level: middleimportance: must knowfreq 68%

answer

  1. the binder reaches the whole target type
  2. fields the interface never showed
  3. authorized operation, unauthorized field set
  4. unknown-field rejection misses it entirely
  5. allow-list, never deny-list

basics

~20 s

Over-posting is a client sending body fields the interface never offers, which the binder still assigns because they are real properties of the bound object. Nothing fails, so a caller can set internal state such as a role or a price.

solid answer

~40 s

Body binding usually works by name: for each field in the payload, the framework looks for a matching property on the target type and sets it. If the target is the domain object, the binder's reach is the whole object, not the subset the form or client shows. A caller who adds `role`, `status`, `ownerId` or `balance` to the payload gets those set too. This is often called mass assignment. It is dangerous because it fails silently and passes authorization: the request is authenticated, the operation is permitted, and only the *field set* is illegitimate — something most authorization checks never look at. The primary fix is structural: bind into a transfer model that declares only client-settable fields, then copy explicitly.

go deeper

for a junior

Remember the shape of the attack: a client adds a field the screen never shows, and the binder sets it because it is a real property of the object being bound. Never bind a request body onto an entity.

for a middle

Explain why the usual defences miss it: the operation is authorized, the value is valid, and the field is declared, so unknown-field rejection does not trigger. Name the structural fix and why it fails closed.

for a senior

Show how you find it in an existing codebase, how you write a regression test as an attack rather than a shape assertion, and how you handle callers who legitimately have different writable field sets.

for a principal

Talk about making the class of bug unrepresentable — an architecture rule that no domain type appears in a handler signature, enforced automatically, beats reviewing each endpoint forever.

**Over-posting** — also called **mass assignment** — is what happens when a client puts fields into a request body that the endpoint never intended to accept, and the framework's binder assigns them anyway. It is a property of *how binding works*, not of any one framework. ## The mechanism Most body binding is **name-directed and reflective**. Given a target type, the framework walks the fields present in the payload and, for each one, finds a matching property on the target and sets it. Some frameworks do this through constructors, some through setters, some through direct field access; some use the declared names, some apply a naming strategy. The common denominator is the one that matters here: > The binder's reach is **every settable property of the target type**, not the subset a user interface happens to display. So the target type's field list silently becomes the API's accepted field list. If the target is a transfer model with three fields, a client can set three things. If the target is the domain object, a client can set everything the domain object can hold. ## Why it is not caught by the usual defences 1. **Authorization runs on the operation, not the field set.** The check is "may this user update this record?" — and the answer is yes. Nothing in that question asks *which fields* the update touches. 2. **Nothing fails.** There is no error, no type mismatch, no unknown property. The over-posted names are genuine, well-typed properties of the bound object. 3. **Constraint checking does not help.** Declared constraints say what a value must look like when present, not whether the field was allowed to be present at all. A perfectly valid role string passes every format rule. 4. **Rejecting unknown fields does not fix it.** That setting rejects names the target type does *not* declare. Over-posted fields are declared — that is the entire problem — so a strict-unknown-fields policy sails straight past the attack while catching only typos. ## What the exposure looks like - **Privilege escalation:** a `role`, `permissions` or `isAdmin` field set on a profile update. - **State machine bypass:** a `status` or `state` field jumped straight to an approved or paid value, skipping the transition that would have checked the rules. - **Ownership theft or confusion:** a `tenantId`, `ownerId` or `accountId` rewritten so a record moves to another owner. - **Value manipulation:** a `price`, `discount`, `balance` or `creditLimit` supplied by the caller instead of computed by the server. - **Audit corruption:** a `createdAt`, `createdBy` or `version` field overwritten, which quietly destroys the trail you would use to investigate the rest. ## The fixes, strongest first 1. **Bind into a transfer model that declares only client-settable fields.** This is structural: the exposure disappears because the binder cannot reach anything else. New domain fields are unbindable by default — the system fails closed. 2. **Copy explicitly into the domain object.** After binding, assign field by field. Server-controlled values (identity, ownership, timestamps, computed money, state transitions) are set from server state or derived, never from the payload. 3. **Configure an allow-list of bindable names** where the framework supports constraining a binder. It is weaker than a dedicated type because it lives apart from the type it protects, but it is far better than nothing. 4. **Never a deny-list.** A list of fields to ignore is a statement about a set that grows every time someone adds a field, and forgetting to extend it produces no error. ## Two subtleties worth knowing **Nested objects and collections.** A transfer model protects only the level it declares. If it holds a nested type or a list of them, binding recurses into those, and each nested type needs the same discipline. The most common regression is a carefully-built top-level input model with a nested domain type hanging off it. **Different callers, different writable fields.** An administrator may legitimately set a `status` that an ordinary user may not. Resist one model with conditional logic; prefer either separate transfer models per caller class, or an explicit server-side check that this caller may write this field. Conditional binding rules are easy to write and hard to review, and they drift. ## How to prove it is fixed Write the test as an attack, not as a shape assertion: send a payload containing a privileged field, then read the record back and assert the field is unchanged. That test keeps its meaning when someone later swaps the binding style, adds a field, or refactors the mapping — which an assertion about the model's field list does not.

  • Does configuring the binder to reject unknown fields prevent over-posting?
    No. That setting rejects names the target type does not declare, and over-posted fields are declared properties of the bound object — that is precisely why they bind. Strict unknown-field handling is useful for catching client typos and contract drift early, but the field set a caller may write is still decided by the target type, so it must be decided by a transfer model.
  • How does over-posting interact with partial-update endpoints, where absent fields must stay unchanged?
    It gets worse, because partial update is defined as touching only what was sent, so the payload itself decides the write set. Bind into a model that can distinguish absent from explicitly null, then apply only the fields that were present — and still restrict the model to client-settable fields, so a present-but-privileged field has nowhere to land.
  • Where does the risk hide once a transfer model is in place?
    In nesting and in collections. A transfer model protects its own level only; binding recurses into nested types, so a domain type reachable through a nested field or a list re-opens the hole. It also hides in models shared across endpoints, where a field added for an administrative surface becomes settable on a public one.
  • What is the difference between over-posting and simply sending an invalid value?
    An invalid value is caught by constraints: it fails a declared rule about shape, length or range. An over-posted field is perfectly valid in isolation — a well-formed role name, a legal status value — and illegitimate only because that caller was never entitled to set that field. Constraint checking answers the wrong question.

saying these in an interview costs you the question

  • Rejecting unknown body fields prevents mass assignment
  • The interface never shows that field, so nobody can send it
  • Validation constraints already block it because the value is checked
  • A deny-list of ignored fields is as safe as an allow-list
  • Authorization passed, so the whole request must be legitimate
  • The top-level input model is safe, so the nested objects are too