Docs
/
Recipes
/

VM Defense Telemetry & Reactions

VM Defense Telemetry & Reactions

Pro
v7.1.0+

Report VM defense detections to your backend with vmDefenseHook, and tune how each detection category reacts with vmDefenseReaction - from a fully non-breaking telemetry-only build to breaking hard on a stolen bundle.

The problem

The VM defenses - vmSelfDefending, vmDebugProtection and vmDomainLock - act locally: when a debugger, automation tool, tampered environment, or unauthorized domain is detected, the protected code breaks or silently poisons its own results. That stops the attacker, but by default you never hear about it. You can't tell how often your bundle is being probed, which detector fired, or whether a defense is breaking a legitimate user.

Since v7.1.0 two options close that gap. Neither of them enables any defense - they only observe and steer the defenses you have already turned on:

  • vmDefenseHook - a global callback that receives a signal object every time a defense detects something. Use it to send telemetry to your backend.
  • vmDefenseReaction - a per-category map that selects how an enabled defense reacts: break, poison, or do nothing locally.

Recipe 1 - report detections to your backend

Step 1 - register a global hook function, before the obfuscated bundle loads

The VM runtime and its defenses run before your protected program, so many detections fire during startup. Define the hook as a plain global in the host page, ahead of the obfuscated script tag:

<script>
    // In your page, BEFORE the obfuscated script:
    window.__vmDetection = function (signal) {
        navigator.sendBeacon('/api/vm-defense', JSON.stringify(signal));
    };
</script>
<script src="/app.obfuscated.js"></script>

Step 2 - point vmDefenseHook at it

The option value is the name of the global function, as a string:

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    // the hook alone enables nothing - a defense must be on for detectors to run:
    vmSelfDefending: true,
    vmDebugProtection: true,
    vmDefenseHook: '__vmDetection'
});

In the dashboard, the VM Defense Hook field appears in the VM options panel once at least one defense (vmSelfDefending, vmDebugProtection, or vmDomainLock) is enabled.

Step 3 - receive the signal on your backend

Every detection calls the hook with a single signal object:

  • source - the specific detector: inspector, headless, node, agent, domain, debugger, sandbox, env, nativeHook, or timing
  • category - automation, debugger, sandbox, domain, tamper, or integrity
  • score, threshold - the detection score and the threshold it crossed

A minimal receiving endpoint (Express shown; any backend that accepts a POST works). It normalizes the body to an array so it also handles the batched form posted by the buffer pattern below:

app.post('/api/vm-defense', express.text({ type: '*/*' }), (req, res) => {
    // a signal: { source: 'headless', category: 'automation', score: 7, threshold: 4 }
    const signals = [].concat(JSON.parse(req.body));
    for (const signal of signals) {
        console.warn('vm-defense', { ...signal, ip: req.ip, ua: req.get('user-agent') });
    }
    res.sendStatus(204);
});

Recipe 2 - adjust the default reactions

vmDefenseReaction configures how each detection category reacts. It does not enable anything - the defenses themselves are turned on by vmSelfDefending, vmDebugProtection, and vmDomainLock; this option only selects how an enabled defense reacts. The category is the unit of control: every detector in a category enacts that category's reaction, and a reaction set for a category whose option is off simply has no effect.

CategoryEnabled byReacts when
automationvmSelfDefending or vmDebugProtectionThe code is being driven by software instead of a person: a headless or automated browser, a scraping / testing framework, or an AI coding-agent stepping the page.
debuggervmDebugProtection or vmSelfDefendingSomeone has a debugger or the browser's developer-tools inspector open and is stepping through the running code to understand it.
sandboxvmDebugProtectionThe code is not running in a real browser at all - it has been lifted into an emulated or scripted JavaScript environment to be executed and studied offline.
domainvmDomainLockThe code is running on a site you did not authorize: a host not in your vmDomainLockallow-list (for example, your bundle copied onto someone else's domain).
tampervmSelfDefendingThe JavaScript environment around the VM has been modified to watch or hijack it, such as native browser built-ins swapped out for instrumented versions.
integrityvmSelfDefendingThe protected bundle's own code has been edited or patched since you generated it.

Keys are these six category names, or default (a fallback for unspecified categories). Values are:

  • break - break immediately
  • decoy - keep running on poisoned state, silently producing wrong results
  • none - do nothing locally (telemetry only)

A category you don't set falls back to the built-in defaults:

// built-in defaults
vmDefenseReaction: {
    automation: 'break',
    debugger: 'decoy',
    sandbox: 'decoy',
    domain: 'break',
    tamper: 'break',
    integrity: 'break'
}

default reaches every category, including the correct-by-construction ones (integrity, tamper), so { default: 'none' } is a genuinely non-breaking, telemetry-only build:

vmDefenseReaction: { default: 'none' } // never break - pair with vmDefenseHook
vmDefenseReaction: { automation: 'none' } // tolerate automation FPs; the rest keep their defaults (a bad domain still breaks)

In the dashboard, the VM Defense Reactions selects appear in the VM options panel once a defense is enabled; each category is editable only while a defense that emits its detectors is on.

From telemetry to enforcement

You don't have to choose between visibility and enforcement on day one. Roll the defenses out in two builds: one that only reports, then - once the telemetry looks clean - one that reacts.

Step 1 - ship an observe-only build

Enable every defense you plan to use, point vmDefenseHook at your endpoint, and turn all reactions off. Every detector still runs and reports each hit to your backend - it just never breaks anything:

JavaScriptObfuscator.obfuscate(source, {
    vmObfuscation: true,
    vmSelfDefending: true,
    vmDebugProtection: true,
    vmDomainLock: ['example.com'],
    vmDefenseHook: '__vmDetection',
    vmDefenseReaction: { default: 'none' } // observe only
});

Step 2 - review the collected signals

After the build has seen real traffic, look for detections that legitimate use triggered. The two most common:

  • automation hits from your own end-to-end tests or uptime monitoring - build those artifacts without the defenses instead of tolerating the category in production.
  • domain hits from a staging or preview host you forgot to include in the vmDomainLock allow-list - add the host.

Prefer fixing the cause over softening a reaction: every category left at none is a detector an attacker no longer has to worry about.

Step 3 - switch on the reactions

Remove the default: 'none'override so the built-in per-category reactions apply - the entire switch is that one line. If a category kept producing false positives you can't eliminate, keep just that category at none (e.g. vmDefenseReaction: { automation: 'none' }) and enforce the rest.