Skip to content

Security hardening: validateSync/validate inspect the live object → read-unstable properties (getters/Proxy) can bypass validation #2687

Description

@peaktwilight

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 (class-validator@0.15.1):

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 value

Reachability (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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions