JSON Merge Patch Turns Omitted Fields Into an Authorization Question
JSON Merge Patch is convenient because a client can send a small object that looks like the resource it wants to change. RFC 7396 defines two rules that deserve more attention than they usually receive: a member that is absent from the patch remains unchanged, and a member whose value is null is removed from the target.
Those rules are useful for a configuration document. They are dangerous when the target object mixes editable presentation fields with identity, ownership, role, or entitlement fields. A request body that is merged wholesale can remove a required setting, replace a structured object with a scalar, or reintroduce a key that another validation layer assumed was immutable.
The merge target is part of the threat model
Suppose an application accepts a profile patch containing displayName, timezone, and preferences. The handler merges it into the stored profile and then saves the result. If the same object also contains tenantId, role, or emailVerified, the handler now accepts those fields whenever the parser permits them. Absence is not a denial of authority; it means the existing value remains. Presence of null can be an instruction to delete a control.
The problem is not unique to HTTP PATCH. Internal services often use merge patch to synchronize records, and a queue consumer may apply a patch from a less-trusted producer. The security boundary is the schema and the allowed operation, not the HTTP method name.
The post-merge object also needs validation. Checking only the patch can miss invalid combinations. For example, a patch can remove an authentication method while leaving a recovery method that was never allowed for that account. A role patch can remove a required approval policy while preserving the privileged role itself. Validate the complete resulting resource against the same invariants used for creation.
Build an explicit patch surface
Start with an allowlist of fields that the caller may change for that operation. Reject unknown members rather than silently ignoring them. Separate identity and security-sensitive state from ordinary update requests, and require dedicated endpoints or commands for role changes, ownership transfer, tenant movement, and account recovery changes.
Represent null deliberately. If the protocol uses null to delete a value, the schema should state which fields may be deleted and what authorization that deletion requires. Do not let a generic merge helper turn null into a database default or an omitted column.
Use optimistic concurrency. RFC 5789 defines PATCH as a partial update, but it does not make concurrent writers safe. An ETag or version check prevents two clients from merging against different base states and silently discarding each other's security changes. Record the actor, the base version, the accepted fields, and the resulting object in an audit record.
Tests that expose accidental mass assignment
Send an empty object and confirm it changes nothing. Send an unknown field and require a rejection, not a 200 response with hidden data. Send null for every protected field and check that the authorization layer rejects the operation before the storage layer sees it. Send nested objects that replace arrays or change their types. Send a patch with the same key twice through different JSON parsers and compare the result.
These tests are more useful than a decorative schema check because they show which component owns the final decision. The safest merge function accepts a typed command with named fields, applies authorization to each command, and serializes the result. A generic object merge is appropriate only after the application has established that every possible member is safe to edit.
Top comments (0)