Documentation

Environment Integrity

Reference guides for release workflows, command-line usage, cross-file protections, and the desktop app.

Inside the Docs

Practical guides for real release work.

How-to guides Start with release sequencing and command-line usage, then move into feature-specific references.
Advanced protection Browse cross-file controls like Replace Globals and Protect Members when a build spans multiple scripts.

Environment Integrity

  • browser runtime module, npm package jso-protector/runtime/environment-integrity
  • detecting instrumented builtins and automated analysis harnesses

Integrity hashing answers “has my code been altered?” and third-party script inventory answers “where is my page talking to?”. This module answers a third question: has the JavaScript environment been instrumented underneath my code? A tracing harness usually replaces a handful of builtins — Function.prototype.call, Object.defineProperty, JSON.parse — so it can watch arguments and return values without touching your bundle at all. Bundle hashing cannot see that, because the bundle is untouched.

What it costs an attacker, and what it does not

These checks raise the cost of automated analysis. They do not make anything irreversible, and they are bypassable by an attacker who controls the runtime — every check here is itself JavaScript running in the attacker’s browser, so a sufficiently patient attacker can neutralise it. Use them to make casual and scripted analysis expensive and noisy, not as a guarantee. Pair them with runtime defense response actions so a detection produces telemetry and a reaction rather than only a log line.

The three checks

hookedBuiltins
Compares each watched builtin against the [native code] shape that Function.prototype.toString produces for an un-replaced native. Cheap, synchronous, needs no DOM.
crossRealm
Builds a throwaway same-origin iframe as a pristine realm and diffs your builtins against it. Catches a hook that also spoofed its own toString, which defeats hookedBuiltins on its own.
automation
Looks for driver and headless markers: navigator.webdriver, PhantomJS and Selenium leftovers, and ChromeDriver-family cdc_ properties.
watch
The list of builtins the first two checks inspect. Defaults to Function.prototype.toString, call, apply, bind, Object.defineProperty, Object.getOwnPropertyDescriptor, JSON.parse, and JSON.stringify.

Usage

The policy is pure data until you call inspect(), so you can construct it at startup and probe on a timer, on a route change, or before a sensitive operation. The report is shaped to drop straight into a countermeasure policy with no adapter.

const ei = require("jso-protector/runtime/environment-integrity");
const cm = require("jso-protector/runtime/countermeasures");

const probe  = ei.createEnvironmentIntegrityPolicy({
  checks: ["hookedBuiltins", "crossRealm", "automation"],
  watch:  ["Function.prototype.call", "JSON.parse"]
});
const policy = cm.createCountermeasurePolicy({ onTamper: "log" });

const report = probe.inspect(window);
if (!report.ok) {
  policy.react(window, report.violation);
}

inspect() is read-only apart from the iframe the crossRealm check creates and then removes. Because window is a parameter rather than an ambient global, the module unit-tests against a stub with no DOM.

Reading the report

Severity is deliberately graded, and automation findings are capped at signal — never high — because navigator.webdriver is a one-line patch and a tuned headless browser leaves none of these markers. Treat automation findings as signals, never as proof.

high
A watched builtin is not reporting as native, i.e. it has been replaced or wrapped.
signal
A heuristic automation marker is present. Evadable; corroborate before acting harshly.
unavailable
A check could not run — for example a Content Security Policy frame-src rule blocked the probe iframe.
complete
False when any check reported unavailable. Could not look is not the same as nothing found, so test this alongside ok.

ok means “found nothing actionable”: no high and no signal findings. An unavailable check does not by itself clear the report, so a caller that reads only ok still learns from complete that something could not be verified.

Two defaults worth knowing

console.* is not watched by default. Analytics, logging, and error-reporting libraries legitimately wrap console methods, so watching them produces false positives on ordinary production pages. Opt in explicitly with watch: ["console.log"] only if your build has no such library.

An unreliable stringifier reports once, not eight times. Every hookedBuiltins result depends on Function.prototype.toString, so it is probed first. If it misreports itself, the module emits a single toStringUnreliable finding and stops, rather than flagging every watched builtin and burying the one finding that matters. crossRealm is the check that can still get a truthful answer in that state.

Where results appear

The report.violation payload uses the same { reason, src, findings } shape as the rest of the runtime suite, with reason set to environment-hooked, automation-signal, or environment-unverified. Pass it to a countermeasure policy to reuse the existing response actions and beacon channel described in Runtime Defense — there is no separate collector or dashboard to configure.

Frequently asked questions

What question does Environment Integrity answer that hashing does not?

Whether the JavaScript environment underneath your code has been instrumented. Integrity hashing tells you if your code was altered and a third-party script inventory tells you where your page is talking to. Neither sees a tracing harness, because a harness typically replaces a handful of built-ins so it can watch arguments and return values without touching your bundle at all. The bundle hash is unchanged and correct, and the analysis is happening anyway.

What are the three checks and what does each cost an attacker?

The first compares each watched built-in against the shape a native function produces when it is converted to a string, which is cheap and synchronous and needs no DOM. The second builds a throwaway same-origin frame as a pristine realm and diffs your built-ins against it, which catches a hook that also spoofed its own string representation. The third looks for driver and headless markers. Together they make casual and scripted analysis expensive and noisy. None of them makes anything irreversible, because every check is itself JavaScript running in the reader's browser.

How much weight should an automation finding carry?

Very little on its own, and the module is deliberately built to say so. Automation findings are capped at signal severity and never reported as high, because the usual driver marker is a one-line patch and a tuned headless browser leaves none of these traces at all. Treat them as a signal to corroborate rather than as proof, and reserve the harsher response actions for a finding that a watched built-in is no longer reporting as native.

What does it mean when a report is ok but not complete?

That nothing actionable was found but something could not be looked at, and the distinction matters. A check can come back unavailable, for example when a content security policy frame rule blocks the probe frame the cross-realm check needs. Could not look is not the same as nothing found, so a caller that reads only the ok flag should read the complete flag alongside it. That is why the two are separate fields rather than one summary verdict.

Why are the console methods not watched by default?

Because watching them produces false positives on ordinary production pages. Analytics, logging and error-reporting libraries legitimately wrap those methods, so a default that treated wrapping as hostile would fire on a very large share of normal sites. You can opt in explicitly if your build genuinely has no such library, which is the right shape for a check whose correct setting depends on what else you ship.

How do I turn a detection into a response?

Pass the report's violation payload straight to a countermeasure policy. It uses the same shape as the rest of the runtime suite, with a reason field set according to whether a built-in was hooked, an automation marker was seen, or the environment could not be verified. That means it reuses the existing response actions and the existing beacon channel, and there is no separate collector or dashboard to configure. The policy itself is pure data until you call the inspect method, so you can build it at startup and probe on a timer, on a route change, or before a sensitive operation.

Try this in the online obfuscator

Paste your own code and see this option applied, or compare plans for larger projects and the desktop app.

Try It Free See Pricing