Compatibility
Published
Every request handler worth the name has a schema in front of it. Whether it is written with a library or by hand, the shape is always the same: an object whose keys are the field names you accept, checked against an object whose keys came from whoever called you. Two objects, one set of names, and only one of them is authored in the file that gets protected.
What was measured
One sample file, driven through five protection profiles for the base column and through both member-renaming profiles for the rest. It holds a schema of the familiar shape -- a key per field, each with a type and a required flag -- and validates a JSON body parsed from text, exactly as a handler does. It also reads a rule directly the way a form renderer does, runs the unknown-key rejection that every library ships as strict mode, and validates a deliberately invalid second payload so that both verdicts are visible in the same run.
The payload keys are not authored by the sample; they arrive as JSON text and become properties through JSON.parse, which is not a rename site. The schema is authored in the file and is a rename site. That asymmetry is the whole subject: the schema is compiled into your bundle, the data is not, and only one of them can be renamed.
The base column is clean on all five profiles. The verdict, the error list, the direct rule reads, the strict-mode result, the accepted values and the second payload's rejection all reproduced exactly. Validation is not affected by protection on its own.
A valid request is rejected, and the error names a field nobody sent
With a pattern matching one field name, the verdict flipped: valid=true became valid=false, and the error list went from errors=none to errors=_0x1:missing. The schema key was renamed, the payload key was not, and the validator looked in the request for a property that only exists in the protected source.
This is the loudest member-renaming failure measured across the last several passes, and the noise is in the right place for once: the request fails, immediately, at the boundary. The problem is what the failure says. Your API returns a validation error for a field called _0x1, and the caller -- a customer, a partner, your own mobile client -- goes looking for a field by that name in your documentation, where it does not appear, because it did not exist until the build ran.
The arm matching three field names at once behaved identically and shows the scale: every request that omits nothing at all is rejected for the one renamed field that is required, while the two optional ones simply stop being validated. There is no partial-failure mode here. If the pattern touches a required field, every request through that route fails.
Strict mode reports the caller's real field as unknown
The second half of the same run is the mirror image and is arguably worse for a support queue. The unknown-key check went from unknown=none to unknown=email, and in the three-field arm to unknown=id,age,role.
The mechanism is a single step: the list of known keys is read off the schema, so it now contains generated names, and the caller's genuine, documented fields are not in it. Strict mode was designed to catch typos and stale integrations, so its message is usually some variant of unexpected property. Your API is now telling a caller that email is not a field it accepts, while simultaneously rejecting the request for a missing field with an unreadable name. Both messages are produced by the same validator in the same pass, and both are, from the caller's point of view, false.
It is worth being precise about the blast radius. This is not a subtle data corruption; it is a route that cannot be called successfully by anyone. If a rename pattern reaches your schema keys, the effect is a hard outage of that endpoint, visible in the first request after deployment, which is at least the failure mode you want if you are going to have one.
Type checking quietly stops happening
The invalid payload in the same run shows the part that does not fail loudly. Unprotected, the second body -- missing a required field and carrying a string where a number belongs -- was rejected with bad-errors=email:missing,age:expected-number. Under renaming it was rejected with bad-errors=_0x1:missing,email:missing.
The request is still rejected, so a smoke test that only asserts invalid input is refused passes. But the type error is gone from the report. The validator never reached the type comparison for the renamed field, because it looked up a name that is absent and stopped at the required-field branch. Every renamed optional field is now unvalidated: absent as far as the schema is concerned, and therefore never type-checked, never range-checked and never rejected for being the wrong shape.
Two arms in the same run did not change at all, and they explain why this is easy to miss when reading the code. Patterns matching the rule keys themselves -- the type and required properties inside each rule -- produced identical output on both targets, because those names are written and read entirely within this file and were renamed on both sides together. A pattern matching the route key that holds the schema behaved the same way. The machinery of validation is safe under renaming. It is only the field names, which you share with your callers, that are not.
What the first hour after that deploy looks like
This is the rare member-renaming failure with a clean incident shape, so it is worth describing. The route does not degrade; it stops. Every request that reaches the affected schema is rejected from the first one after the deployment, so the error rate for that endpoint goes to a hundred per cent while every other route in the service stays healthy. If you graph validation failures separately from server errors, this appears entirely in the first graph and not at all in the second, because nothing threw.
The confusing part is the content of the errors rather than their volume. Support receives reports that an integration which has worked for years is suddenly rejecting well-formed requests, quoting an error about a field that does not appear in your documentation, and a second error saying that a field which does appear there is not accepted. Both come from the same validator in the same pass, and read together they look like a corrupted deployment rather than a rename.
The useful diagnostic is the shape of the error rather than any log line: a required-field error naming an identifier you did not write, alongside an unknown-field error naming one you did, is a signature that only member renaming produces. A rollback resolves it immediately and completely, which is worth knowing during the incident and worth not stopping at afterwards, because the same build will be reissued once the schema keys are reserved.
What a validation library changes, and what it does not
The measurement deliberately uses a hand-written validator rather than a library, so that nothing sits between the engine and the result. The question that leaves open is whether a library would behave differently, and the answer follows from where the field names end up in the emitted file.
A schema built as an object literal -- the shape almost every modern library uses, a key per field with a validator as its value -- compiles to property names in your source. That is the shape measured here, and a library version of it is the same code with better error messages. A schema loaded from a JSON file at runtime is a different case entirely: those keys arrive through JSON.parse, which is not a rename site, so the schema side survives renaming untouched. Validators configured through string arguments rather than object keys are in the same position, for the same reason a quoted string is never an identifier.
That split is genuinely useful when you are deciding what to check, but it should not be mistaken for safety. Even where the schema itself cannot be renamed, the handler that runs after validation reads the validated object by property -- body.email, params.id -- and those reads are ordinary member access. A JSON schema keeps the gate honest while the code behind the gate reads the wrong names, which produces the quieter half of the failure without the loud half that would have told you.
The general shape holds across all of it. Renaming is safe exactly where both ends of a name are inside the bundle, and unsafe exactly where one end is not. A schema is unusual only in that it makes the boundary explicit: the schema is the list of names you have promised to other people.
What this means in practice
Protection alone does not break schema validation, on any of the five profiles measured, and that is the answer for most readers. The rest applies only with member renaming switched on.
The rule follows from the measurement rather than from taste. Schema keys are field names, field names are a published interface, and a published interface belongs in the reserved list. This is true whether the schema is a hand-written object like the one measured here or a library's builder chain, because both compile to property names in your source. Anchoring the member pattern to identifiers distinctive to your own application handles it automatically, since such a pattern will not match a bare id or email.
If you want a check that takes a minute: send one known-good request to the protected build and read the response body. A validation error naming a field you did not send, or a strict-mode complaint about a field you did, means the schema keys were renamed. Then send one known-bad request and confirm the error list still names the type problem rather than only a missing field, because that is the half that fails quietly and the half a passing test suite will not notice.
Frequently asked questions
Does obfuscation break schema validation libraries?
Not in the default configuration. A sample built in the shape every library uses -- a key per field with a type and a required flag, checked against a parsed JSON body -- produced identical output on all five protection profiles measured. Validation becomes a surface only when member renaming is on and the pattern matches your field names.
Why does my API reject valid requests after obfuscation?
Because the schema key was renamed and the caller's payload key was not. The validator asks the request for a property that exists only in the protected source, does not find it, and reports the field as missing. The measured verdict went from valid to invalid, with an error naming a generated identifier rather than the real field.
Why does strict mode say a documented field is unknown?
Because the list of known keys is read off the schema, which was renamed, so the caller's genuine fields are no longer in it. The measured output went from no unknown keys to reporting the caller's real field as unknown. The same request can be rejected for a missing field with a generated name and for an unexpected field with the correct name, in one pass.
Does this affect zod, ajv and joi the same way?
The mechanism is the same for any schema that is authored in your source, because in every case the field names end up as property names in the emitted bundle. The measurement here uses a hand-rolled validator of the same shape so that no dependency sits between the engine and the result, but nothing about the finding is specific to that implementation.
Do type checks still run after member renaming?
Not for a renamed field. The lookup fails first, so the validator stops at the required-field branch and never reaches the type comparison. In the measured run a body carrying a string where a number belonged was still rejected, but the type error had disappeared from the report and only a missing-field error remained, so a renamed optional field is effectively unvalidated.
Are the type and required keys inside each rule also at risk?
No, when both ends live in your bundle. Patterns matching the rule keys themselves produced no difference on either target, because they are written and read in the same file and renamed together. The validation machinery is safe; the field names, which you share with callers, are the part that is not.
How do I verify this on my own service?
Send one known-good request to the protected build and read the response: an error naming a field you did not send, or a strict-mode complaint about one you did, means the schema keys were renamed. Then send one known-bad request and confirm the type error is still reported, since that half fails silently. Keep field names in the reserved list.
Related reading