Skip to content

volynetstyle/runtime-robustness-contracts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Runtime Robustness Contracts

This repository is an independent benchmark lab for scoped robustness in JavaScript runtime internals.

Main claim:

This is not a security sandbox. This is a scoped robustness model for Node internals.

This PoC does not replace primordials as a primitive. It challenges pervasive hand-written primordials as a policy.

The alternative model is:

  • explicit robustness contracts;
  • explicit internal operations;
  • specialized containers for hot paths;
  • static policy checks;
  • benchmark gates.

This benchmark compares cost models, not complete production designs. A faster result in one workload does not imply universal superiority.

The project compares three runtime variants:

  • 01-baseline: ordinary dynamic JavaScript using mutable built-ins.
  • 02-primordials: captured primordial-style operations.
  • 03-explicit-contract (scoped internal ops): explicit internal operations plus specialized internal containers for hot paths.

Evidence Provided

The project provides evidence for four separate claims, rather than one vague "better than primordials" claim:

Claim Evidence
Correctness same observable result before pollution
Robustness selected internals keep working after post-bootstrap monkey-patching
Performance characteristics benchmark with warmup, samples, median, p95, p99, mean, stddev, baseline slowdown
Security boundary non-claim explicit threat model says this is not a sandbox

Security claim: none.

Robustness claim: selected internal paths keep working after post-bootstrap prototype pollution.

Claims and Non-claims

This PoC claims:

  • selected internal paths can remain robust after post-bootstrap prototype pollution;
  • explicit internal operations can avoid whole-program type inference;
  • specialized internal containers can reduce overhead in hot paths;
  • robustness policy can be tested and statically checked.

This PoC does not claim:

  • to replace all primordials in Node.js;
  • to provide a security sandbox;
  • to make arbitrary JavaScript safe;
  • to infer receiver types for dynamic method calls;
  • to shift literal prototypes without a separate realm;
  • to provide Node-wide performance conclusions.

Run

Correctness and robustness gates:

npm run check

Main benchmark:

npm run bench

Smoke benchmark for CI runners:

npm run bench:smoke

Soft performance guard after a benchmark run:

npm run bench:guard

Everything for local validation:

npm run all

The benchmark command accepts positional arguments:

node tools/bench.js <iterations> <samples> <warmup>

Preset benchmark scripts:

  • npm run bench:smoke: 200 iterations, 50 samples, 20 warmup.
  • npm run bench:ci: 1000 iterations, 300 samples, 100 warmup.
  • npm run bench:perf: 5000 iterations, 2000 samples, 300 warmup.

Defaults:

  • iterations: 1000
  • samples: 1000
  • warmup: 100

Reports:

  • reports/verification.md
  • reports/latest.md

Correctness Matrix

Generated by npm run check:

Test baseline primordials explicit-contract
normal correctness pass pass pass
after Array.prototype.push pollution fail pass pass
after Array.prototype.join pollution fail pass pass
internal queue FIFO after pollution fail pass pass
after Promise.resolve pollution fail pass pass
mixed workload after pollution fail pass pass
instanceof returned values pass pass pass
no globalThis mutation pass pass pass
literal prototypes shifted fail fail fail
pre-bootstrap pollution protected outside-model outside-model outside-model
exposed internal object protected outside-model outside-model outside-model

The literal prototypes shifted row is intentional. This model does not shift literal prototypes. Array/object literals still belong to the current realm unless a separate realm is used.

The outside-model rows are also intentional. Pre-bootstrap pollution is outside the trusted bootstrap boundary, and deliberately exposed internal objects remain mutable JavaScript objects.

Runtime API

Each runtime exposes the same API:

runtime.sumNumbers([1, 2, 3]);
runtime.formatDiagnostic(["sum", "6", "done"]);

const queue = runtime.internalQueue();
queue.push("a");
queue.shift();
queue.isEmpty();

runtime.resolveValue("value");

The baseline uses ordinary dynamic calls such as .reduce(), .push(), .shift(), .join(), and Promise.resolve().

The primordials variant captures operations at runtime creation time and uses those captured operations after pollution.

The explicit-contract variant, also described as scoped internal ops, uses captured operations where they are the right tool, but avoids generic dynamic dispatch in hot containers. Its queue is an indexed ring worklist, not a wrapper around Array.prototype.push.

Why Not Dynamic Method Lowering?

This PoC deliberately does not lower arbitrary calls such as:

queue.push(item);
cache.get(key);
promise.then(fn);

into internal operations.

Without strong receiver type information, that rewrite would be unsound. In robustness-zone code, the operation must be explicit instead of inferred.

Test Coverage

Correctness tests verify that all runtimes produce the same normal observable results.

Negative tests verify that the unsafe baseline really fails after relevant prototype pollution. This is important: it proves the hardening solves a real failure mode rather than merely adding ceremony.

Robustness tests verify this sequence:

capture trusted operations
then pollute userland built-ins
then run selected internal paths

The pollution set includes:

  • Array.isArray
  • Array.prototype.reduce
  • Array.prototype.push
  • Array.prototype.shift
  • Array.prototype.pop
  • Array.prototype.join
  • Promise.resolve

Additional boundary tests verify two non-claims:

  • if pollution happens before trusted capture, protected variants may capture polluted operations;
  • if an internal object is deliberately exposed to userland, userland can mutate it.

Pollution-sensitive scenarios are also executed in isolated child processes, so the check suite does not depend only on in-process descriptor restoration.

Static Policy

Robustness-zone files are checked for ambiguous dynamic method calls:

  • .push()
  • .pop()
  • .shift()
  • .unshift()
  • .slice()
  • .reduce()
  • .map()
  • .filter()
  • .join()
  • .get()
  • .set()
  • .then()
  • .catch()
  • .finally()

The checker supports an escape hatch:

// robustness-ignore-next-line: receiver is not a built-in container
customLogger.get("x");

The checker is intentionally over-approximating. It is designed to catch ambiguous calls in PoC robustness-zone files, not to be used as-is across a large production codebase. A production version would need AST rules, per-zone allowlists, local suppressions, and better receiver analysis.

CI and Performance Validation

CI is used for correctness and robustness gates.

GitHub-hosted runners are also used for smoke benchmarks, but their numbers should not be treated as stable performance evidence. Stable performance numbers should be collected from pinned Node versions on known machines, preferably self-hosted runners, with CPU and OS metadata recorded in the report.

Recommended validation levels:

  1. npm run check on every commit.
  2. npm run bench:smoke on every pull request.
  3. npm run bench:guard after smoke or perf benchmark runs.
  4. npm run bench:perf manually on self-hosted machines.
  5. Compare reports across Node 20, 22, and 24.

The GitHub Actions workflows follow that split:

  • .github/workflows/ci.yml runs correctness checks, smoke benchmarks, and the soft guard across Linux, macOS, Windows, and Node 20/22/24.
  • .github/workflows/benchmark.yml is a manual benchmark workflow for full benchmark runs on GitHub-hosted runners.
  • .github/workflows/perf-lab.yml is a manual self-hosted runner workflow for controlled performance lab runs.

GitHub-hosted benchmark artifacts are useful for catching obvious breakage and large regressions. They are not used as final performance claims.

The benchmark guard always checks polluted-environment pass/fail behavior. It only enforces slowdown thresholds for npm run bench:ci, npm run bench:perf, or when BENCH_GUARD_STRICT=1 is set, so smoke runs do not fail on timing noise.

Benchmark Methodology

The main benchmark refuses to run until verification passes.

Metadata recorded in reports/latest.md and reports/latest.json:

  • date
  • Node, V8, and libuv versions
  • platform and architecture
  • OS release
  • CPU model and count
  • total memory
  • power mode if known
  • git commit
  • GitHub SHA and run id when available
  • GitHub runner name, OS, and architecture when available
  • CI flag
  • iterations, samples, warmup

Metrics:

  • min
  • median
  • mean
  • p75
  • p95
  • p99
  • max
  • standard deviation
  • slowdown vs baseline

Benchmark workloads:

  • sum-numbers: repeated runtime.sumNumbers.
  • queue: internal queue push/shift.
  • simple-join: diagnostic formatting.
  • mixed: queue plus diagnostic formatting.
  • promise: promise resolution path.
  • polluted:mixed: mixed workload after monkey-patching; baseline is expected to fail and is excluded from slowdown.

These results should not be interpreted as Node-wide performance claims. They compare cost models under this benchmark harness on one machine.

Latest Main Benchmark

Generated by npm run bench on this machine:

Environment Workload Runtime Median ms p75 ms p95 ms p99 ms Mean ms Stddev ms Slowdown Status
normal sum-numbers 01-baseline 0.626 0.630 0.730 1.287 0.641 0.124 1.000x passed
normal sum-numbers 02-primordials 0.626 0.629 0.670 0.757 0.615 0.068 0.999x passed
normal sum-numbers 03-explicit-contract 0.531 0.533 0.550 1.074 0.536 0.070 0.848x passed
normal queue 01-baseline 0.046 0.046 0.057 0.088 0.047 0.007 1.000x passed
normal queue 02-primordials 0.047 0.048 0.053 0.092 0.049 0.007 1.040x passed
normal queue 03-explicit-contract 0.018 0.018 0.019 0.032 0.018 0.006 0.389x passed
normal simple-join 01-baseline 0.059 0.061 0.090 0.115 0.061 0.012 1.000x passed
normal simple-join 02-primordials 0.062 0.063 0.099 0.114 0.064 0.012 1.044x passed
normal simple-join 03-explicit-contract 0.062 0.063 0.105 0.128 0.065 0.013 1.042x passed
normal mixed 01-baseline 0.046 0.047 0.050 0.100 0.047 0.008 1.000x passed
normal mixed 02-primordials 0.051 0.052 0.056 0.102 0.052 0.008 1.110x passed
normal mixed 03-explicit-contract 0.018 0.018 0.019 0.022 0.018 0.005 0.387x passed
normal promise 01-baseline 0.037 0.041 0.074 0.085 0.040 0.015 1.000x passed
normal promise 02-primordials 0.048 0.049 0.082 0.088 0.047 0.014 1.295x passed
normal promise 03-explicit-contract 0.045 0.048 0.076 0.082 0.047 0.012 1.208x passed
polluted mixed 01-baseline n/a n/a n/a n/a n/a n/a n/a failed
polluted mixed 02-primordials 0.052 0.053 0.056 0.116 0.054 0.020 n/a passed
polluted mixed 03-explicit-contract 0.018 0.018 0.020 0.030 0.018 0.011 n/a passed

Why Explicit-contract Can Be Faster Here

The key result is not that captured intrinsics are magically faster. They are not.

In this benchmark, 03-explicit-contract is faster than baseline in workloads where the baseline relies on generic dynamic methods and the explicit variant uses specialized internal structures. The queue does not call Array.prototype.push, shift, or pop at all. It uses indexed storage with explicit read/write state, so the hot path has less generic JavaScript semantics to preserve.

The point is not that primordials are wrong. The point is that robustness should be scoped, explicit, tested, and benchmarked.

Primordials vs Explicit-contract Policy

Criterion Primordials Explicit-contract
Protection from prototype pollution yes yes, in selected zones
Manual coding tax high and pervasive if used broadly scoped; can be lint/autofix-assisted
Hot queue path generic captured methods specialized indexed container
Whole-program inference not needed not needed
Realm boundary no no
Sandbox no no
Node-wide replacement possible partially not claimed

Threat Model

This PoC protects selected internal paths from accidental or incidental prototype pollution after trusted bootstrap.

It aims to handle:

  • userland modifying Array.prototype.push;
  • userland modifying Array.prototype.reduce;
  • userland modifying Array.prototype.join;
  • userland modifying Promise.resolve;
  • userland modifying selected built-in prototype methods after bootstrap.

It does not aim to handle:

  • malicious code with arbitrary access to internal objects;
  • pollution before trusted bootstrap capture;
  • sandbox escape prevention;
  • protecting values deliberately exposed to userland;
  • changing literal prototypes without a separate realm;
  • full JavaScript semantic isolation.

References

License

MIT

About

#1439. Lab for scoped robustness in nodejs internals

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors