Please report security issues privately via GitHub Security Advisories rather than a public issue.
Chronos is a developer tool. It runs on developer laptops and CI runners, and it has no server component and no user accounts. That shapes what "a vulnerability" means here.
A capsule (.chronos/failures/<seed>.json) exists to be shared (attached to a GitHub issue, produced by someone else's CI, handed to a teammate) and then opened with chronos trace, chronos stats, chronos export, chronos replay, chronos shrink, or the Inspector.
Chronos treats every capsule as untrusted input:
- Validated at the read boundary.
readCapsulecaps file size before reading (128 MB default;CHRONOS_MAX_CAPSULE_BYTESraises it), rejects__proto__,constructor, andprototypekeys duringJSON.parse, and validates every field and trace event against theTraceEventunion before anything reachesSimulatoror a renderer. - Content-free read errors. Node's
JSON.parseSyntaxErrorembeds offending file bytes, which on a misdirectedchronos replay /etc/passwdwould disclose that file. Capsule read errors never echo file contents or full paths. - Canonical path confinement. Capsules, scenario modules, and
chronos exportoutputs must resolve (including viarealpathacross symlinks) under the working directory,CHRONOS_DIR, orCHRONOS_CAPSULE_DIR. SetCHRONOS_ALLOW_OUTSIDE_CAPSULES=1to opt out. - Per-sink output escaping. Terminal control sequences and bidirectional overrides are stripped before printing in
trace,stats,replay,shrink,export, andexplain. CSV cells are prefixed against spreadsheet formula injection, and Markdown cells are escaped against table breakout. - Bounded Inspector rendering. The Inspector rejects prototype-pollution keys, drops malformed events or oversized partition groups, and caps event count (
50,000), node count (256), and virtual-time SVG coordinates (10,000,000ms) while surfacing any truncation in the UI.
The Inspector server binds 127.0.0.1, requires a loopback Host header (blocking DNS rebinding), confines static file serving to the built dist/, and sends a restrictive CSP plus X-Frame-Options: DENY. The ?capsule= parameter rejects backslashes, control characters, and cross-origin URLs via WHATWG URL resolution against a local origin.
chronos explain is off unless you configure a provider key, and is absent from @sx4im/chronos-core. API keys are sent in request headers only, with redirect: "error", over https unless the endpoint is loopback. Capsule summaries redact full and truncated JWTs as well as JSON-quoted and unquoted token/key/secret/password/auth fields before leaving the machine, and the model's reply is sanitized before printing.
Chronos removes every source of entropy from a simulated run. @sx4im/chronos-core has zero runtime dependencies, which keeps the supply chain for the part that runs inside your test process as small as it can be.
Note the asymmetry between the two environments:
SimEnv(tests) draws from the seeded xoshiro256** PRNG. It is deterministic by design and therefore completely predictable — never useenv.random()from a simulated run as a source of secrets.RealEnv(production) backsenv.random()with a CSPRNG. The same application code may reasonably useenv.random()for a request id or a nonce in production, so the production adapter must be safe for that; nothing in production is replayed, so this costs nothing.
- A malicious scenario module.
chronos sweep/replay/shrinkimport and run your scenario, which is your own code — the path confinement is a guard against a mistyped path, not a sandbox. - The contents of your own source tree, which
chronos checkreads. - Denial of service from a sweep you asked for. Seed counts are capped at 1,000,000 to keep a typo from exhausting memory, but a large sweep is meant to be expensive.