Electron V8 bytecode adapter for jso-protector. Pair JSO obfuscation
with Electron's bytecode runtime to ship .jsc artefacts that combine
JSO protection with version-bound V8 cached data. Public bytecode tools
and an attached debugger still matter; this is a cost-raising layer, not
an unrecoverability guarantee.
src/main.js
| jso-protector (HTTP API: obfuscate + sign + watermark)
v
dist-protected/main.js
| jso-protector-electron (V8 cachedData -> .jsc)
v
dist-bytecode/main.js.jsc <-- ship this in your .asar
npm install --save-dev jso-protector \
https://javascriptobfuscator.com/download/jso-protector-electron/jso-protector-electron-0.1.0.tgzThe adapter is a versioned direct download and is not published in the npm registry. Pin the URL (or your mirrored copy) in the lockfile.
electron is a peer dependency. You need Electron in your build
environment for mode: embedded (the only mode that emits real
bytecode).
Run under Electron's Node:
electron $(npm bin)/jso-protector-electron \
--input dist-protected \
--output dist-bytecode \
--mode embeddedThis walks dist-protected/, runs vm.Script(src).createCachedData()
for each .js file, and emits dist-bytecode/<rel>.jsc containing a
small JSON header followed by the V8 cachedData blob. The header
records the source bytes, jsc bytes, and Electron version so an
auditor can validate coverage:
[ 8 bytes magic "JSOJSC\x01\x00" ]
[ 4 bytes LE uint32 header length ]
[ N bytes JSON: { filename, electronVersion, srcBytes, jscBytes } ]
[ remaining bytes: V8 cachedData ]
The output directory also contains bytecode.manifest.json listing
every processed + skipped file. Hand it to jso-protector compliance pci-dss-v4 as additional evidence.
jso-protector-electron \
--input dist-protected \
--output dist-bytecode \
--mode scaffold-onlyUseful in CI environments where you can't run Electron (e.g. a
lint stage that just wants to confirm coverage). Output is the
original .js files plus per-file <file>.bytecode-meta.json
recording { sourceSha256, sourceBytes, generatedAt }. The
top-level bytecode.manifest.json carries mode: "scaffold-only"
so a downstream verifier can refuse to ship until the real bytecode
build has run.
In your Electron main process (or preload):
const fs = require("node:fs");
const vm = require("node:vm");
function loadJsc(jscPath) {
const buf = fs.readFileSync(jscPath);
// Skip the 8-byte magic + 4-byte length + JSON header.
const headerLen = buf.readUInt32LE(8);
const cachedData = buf.subarray(12 + headerLen);
const filename = JSON.parse(buf.subarray(12, 12 + headerLen).toString("utf8")).filename;
const script = new vm.Script("", { filename: filename, cachedData: cachedData });
return script.runInThisContext();
}Wrap that in app.whenReady().then(...) and you've replaced your
ordinary require for protected modules.
| Layer | Defends against |
|---|---|
| JSO obfuscation | Casual reverse-engineering of the JS bundle |
| V8 bytecode | Skilled JS reverse-engineering; LLM-assisted decompilation |
| Ed25519 signed manifest | Post-build tampering (CDN swaps, supply-chain attacks) |
| Watermark | Anti-piracy / leak attribution |
| Beacon + SIEM | Detection / personnel alerts when any of the above fires |
None of these defeat a debugger attached to the running Electron process. For that you need OS-level mitigations: code-signing, notarization (macOS), hardened runtime, and Windows AppContainer.
const electron = require("jso-protector-electron");
const result = await electron.compileDirectory({
input: "dist-protected",
output: "dist-bytecode",
mode: "auto", // "auto" | "embedded" | "scaffold-only"
extensions: [".js", ".cjs", ".mjs"],
});
// result: { mode, processed, skipped, manifestPath }The auto mode picks embedded when running under Electron and
scaffold-only otherwise. Always pass an explicit mode in CI so
the build fails loudly if Electron is missing.
npm test14 tests covering both modes, the CLI surface, the .jsc header layout, the directory walk, and the actionable-error path when embedded mode is invoked without Electron.