Security hardening: validateSync/validate inspect the live object → read-unstable properties (getters/Proxy) can bypass validation
Hi — a heads-up on a soundness issue with a security consequence. Private vulnerability reporting isn't enabled on the repo and there's no SECURITY.md contact, so I'm raising it here constructively (with the fix). Happy to move it private if you enable reporting.
Issue. validateSync(obj) / validate(obj) check the caller's live object
and the caller keeps using that same object. If a validated property is a
non-idempotent read (an accessor getter, a Proxy get-trap, or a value derived
from mutable state), the value validated can differ from the value later used —
a validation TOCTOU that bypasses allowlist/authorization checks.
PoC ([email protected]):
class Dto { @IsIn(["readonly","guest"]) role!: string; }
let n = 0;
const dto = Object.assign(new Dto(), { get role(){ return ++n === 1 ? "readonly" : "admin"; } });
validateSync(dto).length === 0; // passes the allowlist
dto.role === "admin"; // app then uses the malicious valueReachability (honest). Not reachable from a plain JSON body (JSON.parse
yields inert data). It bites when validating class instances/models with getter
accessors, Proxy-wrapped objects, or getters over mutable state — a real but
conditional bypass. I'd rate it Medium.
Fix. Offer a materialized, read-stable snapshot of the validated data (read
each property once into a fresh object), as joi (value) and zod (data)
do; or at minimum document that validation reads the caller's live object and
gives no read-stability guarantee.
Reported by 0sec (https://0sec.ai). No ask — just wanted you to have it + the fix.
Source: typestack/class-validator