Compatibility
Published
Validation libraries are configured, not called. You pass a URL and an options object, and the options object is where your policy lives: these protocols, these hosts, a scheme is mandatory, this length, these bounds. The library reads those names; your code writes them. When a build step rewrites property names, the check does not fail and does not error. It runs with the library's defaults, and a library's default is the permissive one whenever your option exists to restrict.
What was measured
One file against validator 13.15.35, the real installed library, driven through five protection profiles for the base column and both member-renaming profiles for the rest. It does the four things applications actually do with a validator: filter a list of redirect targets through an allowlist of protocols and hosts, check each switch in isolation so a failure names one option, screen signup email addresses against a display-name rule and a blocked-domain list, and enforce numeric and length bounds.
The base column is clean. All five profiles reproduced every accept and reject decision exactly, along with the decision record the application stores. Protecting an application that validates input does not change what it accepts.
Everything below is the member-renaming column. Only the option names move; the values are strings and arrays and are untouched, and the library is a dependency the pattern does not reach. The library therefore receives an object with the right values under names it does not recognise, which is indistinguishable to it from an empty options object.
An allowlist that accepts an off-host target
The sharpest result came from the host allowlist. Before, one of five candidate redirect targets was accepted: the one on the application's own host. After renaming that single key, the off-host target was accepted too, and the accepted count went from one to two.
That is an open redirect, produced by a build setting, in code whose allowlist is still visible three lines above the call. The library did not report an unknown option. It applied no host restriction at all, because from its point of view none was requested.
The decision record the application writes made it worse rather than better. It stores the target, the verdict and a summary of the policy, and after renaming it reads allowed true against a host the policy names as forbidden, while still printing the policy summary. A reviewer reading that record sees a decision that agrees with itself.
The protocol arm has the same shape with a different consequence: with the protocols list renamed, a plain HTTP target passed a rule written to accept only HTTPS, and the isolated check confirmed it in one line. The mandatory-scheme switch behaved identically, so an input with no scheme at all became acceptable.
Bounds stop being bounds
The numeric arm is the one to show a reviewer who thinks this is exotic. An integer check with a minimum of one and a maximum of one hundred was given the value 250 and returned false, correctly, before protection. After renaming the two bound keys it returned true.
The length check beside it did the same: a twelve-character string measured against a maximum of eight went from rejected to accepted. Neither call threw, neither logged, and both are still written in the source with their limits.
Two names are enough to remove every bound in an application, because min and max are the names everybody uses and they appear in dozens of option dictionaries across a codebase. This is also the clearest example of the collision hazard: those names are short, generic, and belong to somebody else's API.
The email arm generalises it. With the display-name switch and the blocked-domain list renamed, an address on a disposable mail domain was accepted where it had been rejected. A blocklist that silently stops applying looks exactly like a blocklist that was never populated.
The one arm that failed closed, and why
Not every result loosened the policy. One option in this file exists to relax a default rather than to tighten one: the switch that allows hostnames without a top-level domain, which teams turn on for intranet addresses. Renaming it made the library apply its default, which is the strict setting, and a hostname the application deliberately accepts was rejected.
So the direction is not a property of obfuscation. It is a property of your option. Options that restrict fail open when they stop being read; options that permit fail closed. Both are wrong, but they present completely differently: the first is a security finding nobody notices, the second is a support ticket the same afternoon.
Worth stating plainly because it also predicts what you will see first. A codebase with mostly permissive options gets loud, fast failures and fixes them before release. A codebase with mostly restrictive options ships, and the finding arrives from somebody else.
Where it does throw
The pattern that matched the library's own function names produced an immediate TypeError naming a generated identifier, on the first validation call in the file. That is the loud arm and it is worth having: it fails in development, before anything ships.
The control arm renamed only names this file owns, and behaved exactly as expected: the decision and report objects were emitted with generated keys while every verdict stayed correct. That is a reminder that the report is not evidence. An audit trail built from your own object survives renaming as data and stops surviving it as something a human or a log pipeline can read.
Between those, everything else in this area is silent, and that is the part worth designing for.
What to do about it
Nothing here argues against protecting an application that validates input. Every base-column arm was clean on every profile. The exposure is a member pattern broad enough to reach names a dependency reads, and the mitigations are specific.
Keep option-dictionary names out of the pattern: protocols, require_protocol, require_tld, host_whitelist, host_blacklist, allow_display_name, min, max, and the equivalents in whatever library you use. Better, anchor the pattern to a prefix or naming convention you own, so a dependency's vocabulary cannot collide with it.
Then write the one test that catches the whole class: feed each validator a value it must reject and assert the rejection. A test that only checks accepted inputs passes in every run above. The rejected cases are where the policy lives, and they are what stops being enforced.
Finally, treat any validation the application performs on itself as untrustworthy evidence after a build change. The decision record in this measurement agreed with the wrong decision. Log the inputs and the verdict from outside the module that made it, or assert against a fixed list of cases in CI, where nothing has been renamed.
Frequently asked questions
Does obfuscation break input validation?
Not in the default configuration. A file driving the real validator library through an allowlist, protocol rules, email screening and numeric bounds produced identical decisions on all five protection profiles measured. Validation becomes a surface only when member renaming reaches the option names the library reads.
Can obfuscation cause an open redirect?
It can if a member pattern matches the host allowlist key. In the measured run an off-host redirect target went from rejected to accepted, because the library received an options object whose keys it did not recognise and applied no host restriction. The allowlist is still visible in the source.
Why does the check pass instead of throwing?
Because an unrecognised option is not an error to most libraries. The values are still there under generated names, so the library sees what looks like an empty options object and falls back to its documented defaults. Only a pattern that matches the library's own function names produces a thrown error.
Do all these failures make validation more permissive?
No, and the exception is instructive. An option that exists to relax a default fails closed: renaming the switch that allows hostnames without a top-level domain made the library apply its strict default and reject an intranet address the application accepts. Restrictive options fail open, permissive options fail closed.
Which validator option names should stay out of a rename pattern?
protocols, require_protocol, require_tld, host_whitelist, host_blacklist, allow_display_name, min and max are the ones measured here, and every validation library has its own set. Anchoring the pattern to a naming convention you own is more durable than maintaining a list of somebody else's names.
How much does a numeric bound change after renaming?
It stops applying. An integer check bounded between one and one hundred accepted 250, and a length check capped at eight accepted a twelve-character string. Nothing threw and nothing was logged, so the only visible signal is downstream data that should not have been storable.
What test catches this on a protected build?
One that asserts on rejection. Feed every validator a value it is supposed to refuse and check that it refuses. Suites that only exercise valid input pass in every arm measured here, because the accepted cases were still accepted.
Related reading