Last reviewed: 2026-10-28
Direct answer
Prevent mass assignment in coding-agent API patches by defining the fields a caller may change for each operation, validating that input contract, and explicitly mapping approved values into the persistence call. Keep server-controlled properties outside the input object. Authenticate the caller and authorize the target object and action separately. A valid request to edit your own profile must not also let you change your role.
The OWASP Mass Assignment Cheat Sheet recommends dedicated input objects containing editable fields and explicit field allowlists. MITRE CWE-915 defines the weakness as insufficient control over which object attributes upstream input can initialize or update. Both support reviewing the input-to-write path, rather than checking only whether a request passes schema validation.
For example, an agent implements PATCH /me/profile by passing the parsed body to a generic user-update helper. Updating displayName works. The same request can also set role if that helper accepts every property in the user model. The fix is a profile-specific write contract, with regression tests that inspect protected stored values even when the response omits them.
Who this is for
Use this guide when reviewing agent-written account forms, REST updates, GraphQL mutations, import handlers, or service methods that turn external fields into persisted changes. Look for new model constructors, generic update calls, body spreads, automatic binders, nested relation updates, and shared create-or-update schemas.
An authenticated user may legitimately access a record while lacking permission to change particular fields. Pair this review with the site’s object-level authorization guide to establish who may act on the record. Here, the additional question is which changes that operation permits.
Key takeaways
- Define writable fields per operation and caller capability, rather than copying the domain model’s properties.
- Validate values and field authority. A well-typed
roleremains unauthorized in a self-service profile update. - Construct the persistence command from approved values; keep the original request body out of downstream update helpers.
- Specify omitted, null, empty, unknown, and nested-field behavior before asking an agent to implement partial updates.
- Test a permitted field mixed with a protected field, and require rejection before any write or business event.
- Inspect stored state and side effects. A response that hides a protected field cannot prove that the field stayed unchanged.
- Adding a database column must not silently add a public write capability.
Sources checked
Three primary public references were fetched successfully on 2026-10-28:
- OWASP: Mass Assignment Cheat Sheet supports dedicated data transfer objects, editable-field allowlists, the risks of denylist-only protection, and separate object and operation authorization. Its HTTP representation reported a modification time on the access date; that is not evidence of a new vulnerability announcement.
- OWASP API Security: API3:2023 Broken Object Property Level Authorization covers unauthorized changes to sensitive properties, side-effect analysis when a field is absent from the response, and explicit selection of exposed and writable properties. This is the project’s published 2023 edition.
- MITRE: CWE-915, Improperly Controlled Modification of Dynamically-Determined Object Attributes supports the weakness definition, allowlists over denylists, and refactoring away from dynamically selected writes. The fetched page identifies CWE 4.20 and displays a page-update date of April 30, 2026.
These maintained technical references support an evergreen patch-review task. The profile policy, pseudocode, rejection convention, and test cases below are engineering recommendations. They do not describe an executed application test or a newly disclosed incident.
Contract details to verify
Write an operation-specific field policy
Use a synthetic account service as the running example. A signed-in member may edit their own display name and locale. A separate staff operation manages roles. The server establishes tenant membership, verification status, credit balance, and timestamps through other trusted workflows.
| Field or input | Self-service profile policy |
|---|---|
displayName | Optional string, 1–80 characters under the application’s documented length rule |
locale | Optional string, exactly one of the supported locale identifiers |
role, tenantId, emailVerified, creditBalance | Never accepted by this operation |
createdAt, updatedAt | Server-controlled; never copied from the request |
| Unknown keys or nested objects | Reject the whole request |
| Omitted field | Preserve its existing value |
| Explicit null or empty body | Reject under this example’s contract |
Choose and document the length unit, supported locales, and public error format in the real application. The example’s rejection rules are deliberate choices, not universal HTTP requirements. A compatibility contract may ignore unknown fields, but those fields must remain excluded from every downstream write and side effect.
Keep a registration input, a self-service edit input, and a staff role-change input separate. They can share scalar validators without sharing writable-field authority. If the staff endpoint needs to change role, it needs its own authorization and audit behavior; adding a conditional exception to a broad profile binder makes the boundary harder to review.
Validate the request shape, then build the write command
A domain schema describes values the system stores. It may correctly accept role: "admin" because staff can create administrators. That says nothing about whether a member may submit that property to a profile endpoint. A schema generated from the whole model can therefore validate an unauthorized update perfectly.
Give the operation a narrow input type with runtime validation and an explicit unknown-property rule. Then construct the write command from its approved fields. A compile-time interface alone does not validate a network request, and validation loses its value if the handler later forwards the original body.
This framework-neutral pseudocode shows the intended sequence:
principal = authentication.require(request)
input = profileInput.validateEntireRequest(request.body)
profile = profiles.loadForPrincipal(principal)
authorization.require(principal, "profile:update", profile)
changes = emptyFieldSet()
if input.has("displayName"):
changes.set("displayName", input.displayName)
if input.has("locale"):
changes.set("locale", input.locale)
saved = profiles.applyProfileFields(profile, changes)
return profileResponse.selectApprovedFields(saved)
validateEntireRequest must enforce the table before returning any candidate. loadForPrincipal must establish the intended object scope. The authorization check must permit this action on that object. applyProfileFields must preserve the narrow write set and the application’s transaction rules, including any required consistency between authorization and mutation. These names describe obligations; they are not implementations.
Keep the input, domain model, write command, and response projection distinct. A field may be readable without being writable, and a writable field may need additional output restrictions. OWASP API3 addresses both directions. Selecting response fields cannot repair an earlier unauthorized database change.
Review nested values and partial-update semantics
An allowlisted root key can contain another authority boundary. If a later feature accepts preferences, define its allowed children and types. Do not pass the entire nested object to a generic merge. If a mutation accepts a related object or a list of record identifiers, authorize those relationships and define the permitted nested operations separately.
For partial updates, preserve the difference between an absent field and an explicit null. Do not let a default-filled validation result overwrite an omitted value. If clearing a value is supported, define that action deliberately. If the API accepts patch paths or update operators, authorize the resulting target fields and operations before applying them; validating only the outer envelope leaves that decision unresolved.
In JavaScript, a mass-assignment defect can exist with ordinary own properties and no prototype modification. Review dynamic merges separately with the site’s prototype-pollution guide
. A record with a safe prototype can still contain an unauthorized role that a generic updater persists.
Happy-path operator workflow
- Freeze the proposed revision and identify every changed path from request parsing to model creation, updates, relation changes, and business-event submission.
- Write the operation’s allowed fields, value rules, caller capabilities, object policy, omission behavior, and rejection behavior with the API owner.
- Inspect binder configuration, input schemas, service signatures, and repository helpers. Confirm that a caller cannot bypass the narrow handler by invoking the same broad service elsewhere.
- Replace domain-object binding with a dedicated input record and explicit mapping. Validate the complete candidate before changing existing state.
- Exercise the normal request path with synthetic accounts. Verify the exact permitted changes and the preservation of omitted and protected values.
- Run the negative cases below, then attach the field-policy revision, test identities, observed state changes, and proposed code revision to the review.
When an agent pushes a follow-up, recheck the final mapping and rerun the affected cases. A later convenience refactor can reintroduce a raw-body spread after an earlier repair.
Prove field authority with a regression matrix
Create an ordinary member account with known synthetic values: a member role, a tenant assignment, an unverified email, and a nonzero credit balance. Record the relevant state before each test. Exercise the actual request handler and persistence adapter; a unit test of an input validator cannot establish that the handler uses its result.
| Request or change | Required evidence under the example policy |
|---|---|
Valid displayName and locale | Only those fields change, along with documented server-managed metadata |
Valid displayName with locale omitted | Existing locale remains unchanged |
Valid display name plus role: "admin" | Whole request rejected; display name and role both unchanged |
Each of tenantId, emailVerified, and creditBalance | Rejected independently; no protected value changes |
| Unknown key, including a client-supplied timestamp | Controlled rejection; no business update or queued job |
| Null, wrong scalar type, unsupported locale, or invalid length | Controlled validation failure with existing state preserved |
| Empty object or nested object | Rejection consistent with the declared input contract |
| Another member’s identifier supplied through an alternate route | Object authorization denies the operation before a write |
| Member attempts the separate staff role-change operation | Function and object policy deny it; role remains unchanged |
| A new sensitive property is added to the domain model | Profile input and write fields remain unchanged; submitting that property is rejected |
For every denial, inspect the persisted record, business-event queue, and relevant downstream effects. A security audit entry for a rejected request may be intentional; distinguish it from an event that applies the requested account change. If a protected property is absent from the API response, inspect it through the authorized test harness rather than adding it to the public serializer.
OWASP API3 explicitly notes that side-effect analysis can be necessary when a manipulated property is not returned. An error status after a successful write is therefore a failed boundary. Test the mixed valid-and-forbidden request so that partial application cannot hide behind a final validation error.
Keep this matrix tied to a disposable fixture and the exact proposed revision. These are acceptance criteria for the reader’s implementation; no execution result is claimed here.
Error-path operator workflow
When a protected field changes, block the patch and retain a minimal synthetic reproduction. Trace the value through parser, validator, mapper, service, and persistence helper to find where the write set expands. Review sibling create, bulk, import, and staff paths that reuse the same helper.
Repair that boundary, reset the fixture, and repeat both permitted and denied cases. Do not close the finding by removing the field from the UI, hiding it in responses, or adding only its current spelling to a denylist. A new sensitive model property would remain exposed by a generic binder.
Return a stable validation or authorization failure using the application’s documented response contract. Do not retry rejected input through a permissive update path. If a write may already have occurred, inspect and reconcile the synthetic state before rerunning; the final error does not prove that nothing happened.
Preserve route and operation identifiers, synthetic principal labels, field-policy version, expected decision, stable failure category, state assertions, and event counts. Exclude credentials, real account contents, and complete request bodies from general review logs.
Failure modes
The agent reuses the database model as the request schema. The model includes legitimate internal fields. Use an operation-specific input contract and explicit mapping into the write command.
The patch spreads the original body after validating a subset. Trace which value reaches persistence. Only the validated, approved fields may become the update command.
An allowlist protects the HTTP route but the shared service still accepts arbitrary fields. Review every caller of the service and choose a narrow service interface where the operation permits it.
A nested key is approved without a child policy. Define and enforce each accepted child, relation operation, or patch path before mutation.
The test checks only response fields. Inspect stored protected values and business effects, including cases where the public response deliberately omits them.
A denylist grows whenever a bug is found. MITRE recommends allowlists over denylists. Make the writable set deliberate so new domain fields begin outside it.
FAQ
Does successful object authorization prevent mass assignment?
It establishes permission to perform an action on an object under the implemented policy. The operation still needs to restrict which fields that action may change. A member can own a profile without having permission to grant it an administrator role.
Is a data transfer object sufficient?
It helps when it contains only the operation’s editable fields, receives runtime validation, and is the sole source for the write command. A narrow DTO cannot protect a handler that later passes the original body to an updater. Authorization and value rules still apply.
Must unknown fields always produce an error?
No. Rejection is the example’s explicit policy and makes drift visible. An API that promises to ignore unknown fields must discard them before all writes and effects, and test that behavior. Avoid letting framework defaults choose the contract accidentally.
Can the same field be editable in one endpoint and protected in another?
Yes. A staff operation may legitimately change a role while self-service editing cannot. Give those operations distinct input and authorization contracts, and test both allowed staff behavior and denied member behavior.
Reader next step
Choose one update operation in the next coding-agent patch. Write its editable-field table, follow the exact value passed to persistence, and add a test that mixes a permitted edit with a protected property. Approve the revision only when the intended edit works, the mixed request follows the declared rejection policy, and protected stored values and business effects remain unchanged on denial.