A single-file Node.js implementation of PRML v0.1 and v0.2, demonstrating that the canonicalization is implementable in a second language byte-for-byte against the conformance vectors.
Status: working draft, intended as portability evidence rather than a production tool. The Python reference implementation (falsify_prml.py, in the repo root) remains the recommended runtime.
Result: 13 / 13 v0.1 vectors and 8 / 8 v0.2 vectors pass byte-for-byte; all 14 reject-vectors are correctly rejected.
No build step. Requires Node.js ≥ 18.
# Conformance suites
node falsify.js test-vectors ../../spec/test-vectors/v0.1/test-vectors.json
node falsify.js test-vectors ../../spec/test-vectors/v0.2/test-vectors.json
# Reject suite (run from the repo root)
python3 spec/test-vectors/reject/check_reject.py -- node impl/js/falsify.js lock
# Hash a manifest (JSON or YAML)
node falsify.js hash my-manifest.json
# Lock a manifest (writes <name>.prml.sha256 sidecar)
node falsify.js lock my-manifest.json
# Verify a manifest against its sidecar; if --observed given, evaluate predicate
node falsify.js verify my-manifest.json --observed 0.876Exit codes match the spec: 0 PASS, 2 BAD (bad input/spec), 3 TAMPERED, 10 FAIL, 11 GUARD (missing sidecar).
About 400 lines of Node.js, zero runtime dependencies beyond the Node.js standard library (fs, path, crypto). Optional dependency on js-yaml for loading .yaml files; not required for .json input.
The canonicalizer is hand-rolled to match PyYAML's safe_dump output exactly. It does not use a generic YAML serializer because js-yaml (and other YAML libraries) make different plain-scalar quoting decisions than PyYAML, producing canonical bytes that diverge from the v0.1 vectors.
Input handling is hardened against untrusted manifests:
- Parsed objects (JSON and YAML alike) are rejected if any key is
__proto__,constructor, orprototype, at any nesting depth (prototype-pollution guard, with a bounded-depth DoS check). - YAML input is parsed with
js-yaml'sCORE_SCHEMA, so no custom or JS type tags (for example!!js/function) can be instantiated from manifest content. - Strings containing control or non-portable characters (C0, DEL/C1, U+2028/U+2029, BOM) are rejected, mirroring the Python reference.
- Not a production tool. Use the Python reference implementation for running real evaluations.
- Not a complete YAML implementation. Loading
.yamlfiles works only viajs-yaml's parser (CORE_SCHEMA); the canonicalizer is tuned to PRML manifest shapes, not arbitrary YAML. - Not a browser library. This targets Node.js >= 18 (it uses
fsand Node'scrypto); the registry serves its own browser verifier.
To prove the v0.1 specification is implementable from a second language without reading the Python reference. The exercise surfaced three non-obvious cross-language pitfalls — uint64 precision, integer-valued float typing, plain-scalar quoting heuristics — documented in spec/analysis/canonicalization-portability-v0.1.md. Those findings motivate the v0.2 formal grammar work.
The optional js-yaml dependency is present only for parsing .yaml input files. If you pass .json files (which the test vectors do), no external dependencies are required.
# Optional, only for .yaml input loading
npm install js-yamlMIT. Same as the rest of the repository.