Repository navigation
v3.3.1
Security fix — caller-supplied policy clock
evaluatePolicy resolves its clock as context.timestamp ?? new Date(). All three governor call sites spread caller-supplied params into the PolicyContext and then re-assert the trusted fields (model, tier, budget tiers, cost_center) — but timestamp was missing from that re-assertion at every site.
Impact. A caller could supply the clock that policy time-window rules are matched against: escaping a live curfew rule, or inflicting a dormant one. The same forged value also reached PolicyResult.evaluatedAt, which flows into denial evidence.
Who is most exposed. packages/server (usertrust-server) wraps the headless governor, so there the caller is a remote tenant over HTTP and the value arrives in a request body. The two in-process govern.ts routes require code already running inside your process.
Who should upgrade. Anyone using policy timeWindows on usertrust-server, or calling the headless governor with caller-supplied params. We cannot survey adopters of the published packages — if you use time-window rules anywhere, take this patch.
What did not change. PolicyContext.timestamp remains a legitimate trusted-host input for a host that builds its own context and calls evaluatePolicy directly; only the request-body route through the governors is closed. Time-window matching remains local time by contract — moving it to UTC would have silently altered deny behavior for every deployed policy carrying a window.
Evidence. clock-shadow.test.ts: four decision-level pairs (escape a live window / inflict a dormant one) across all three sites, each holding the forged value constant and moving only the real clock, plus one explicitly-declared vacuity guard. Verified failing-first twice — in TDD order during implementation, and re-confirmed from a clean revert in an isolated replica (8 failed / 1 passed, the pass being the guard). Post-fix 9/9; full suite green in CI.
PR #95 · commit ab7177d. No API changes; drop-in from 3.3.0.