System Architecture & Pipeline
Chrome DevTools Protocol (CDP) instrumentation engine built for authorized security research, WebCrypto observation, and client-side reverse engineering in Chromium browsers.
87 passing unit and live browser integration tests. Zero npm audit vulnerabilities. Maintenance LTS on Node.js 22, Active LTS on Node.js 24.
The Boundary Problem with Network Proxies
Modern web applications and streaming video players rely on the W3C Web Cryptography API (window.crypto.subtle) to encrypt data directly inside the browser execution context prior to transmission.
Network proxies such as Burp Suite or mitmproxy inspect traffic downstream on the wire. By the time a packet hits the proxy socket, the payload is already an opaque encrypted ciphertext. Decryption occurs client-side after the response arrives.
webcrypto-interceptor solves this by attaching directly to the Chromium browser engine via the Chrome DevTools Protocol. It intercepts cryptographic routines at the V8 engine boundary, capturing cleartext arguments, keys, initialization vectors, and return values.
Core Pipeline Architecture
+-------------------------------------------------------------------------+
| Chromium Browser Process (V8 Engine) |
| |
| [Page / Worker Context] |
| crypto.subtle.encrypt(algorithm, key, cleartextBuffer) |
| | |
| v |
| [V8 Breakpoint / Stealth Trap] |
| - Normalizes ArrayBuffer and CryptoKey to JSON envelope |
| - Captures arguments, IV, cleartext, execution stack trace |
| | |
| v |
| [CDP Binding: __wci(payload)] |
+----------|--------------------------------------------------------------+
| (Stdio Pipe Transport: pipe=true, zero open TCP listening ports)
v
+-------------------------------------------------------------------------+
| Node.js Interceptor Process (capture_server.js) |
| |
| [CDP Protocol Dispatcher] |
| - Runtime.bindingCalled event listener |
| - Formats timestamp, context ID, and event category |
| | |
| +---> stdout (colorized live console stream) |
| +---> session_capture_<timestamp>.jsonl (offline archive) |
| +---> wasm_modules/module_<hash>.wasm (reconstructed bytecode) |
+-------------------------------------------------------------------------+Security & Stealth Guarantees
- Out-of-Process Pipe Transport: Puppeteer launches Chromium using anonymous standard input/output pipes (
pipe: true). It never opens exposed TCP listening ports onlocalhost, preventing local process hijacking. - V8 Breakpoints: Non-intrusive breakpoints armed via
Debugger.setBreakpointOnFunctionCall. Native function prototypes retain their originaltoString()signatures, evading anti-tamper heuristics. - CDP Binding Relay: Uses
Runtime.addBinding(__wci) to stream structured event envelopes from sandboxed Web Workers and cross-origin iframes directly to Node.js.
Installation & Quickstart
# Clone repository
git clone https://github.com/cipherwire/webcrypto-interceptor.git
cd webcrypto-interceptor
# Install dependencies (0 vulnerabilities verified by npm audit)
npm install
# Launch interactive inspection
node capture_server.js "http://127.0.0.1:8080/app" --gui
SubtleCrypto Observation & Serialization
Full-spectrum observation of window.crypto.subtle operations, key parameters, nonces, and call-stack attribution directly from the V8 execution engine.
Monitored SubtleCrypto Routines
| API Method | Intercepted Data | Target Algorithms |
|---|---|---|
crypto.subtle.encrypt() |
Algorithm parameters, IV/nonce, Key, Cleartext data | AES-GCM, AES-CBC, AES-CTR, RSA-OAEP |
crypto.subtle.decrypt() |
Algorithm parameters, IV/nonce, Key, Ciphertext data | AES-GCM, AES-CBC, AES-CTR, RSA-OAEP |
crypto.subtle.importKey() |
Key format, raw key bytes, extractable flag, key usages | raw, pkcs8, spki, jwk |
crypto.subtle.exportKey() |
Export format, CryptoKey reference, exported bytes | raw, pkcs8, spki, jwk |
crypto.subtle.generateKey() |
Algorithm parameters, extractable flag, usages | AES, HMAC, RSA, ECDSA, ECDH |
crypto.subtle.deriveKey() / deriveBits() |
KDF algorithm, salt, iterations, base key | PBKDF2, HKDF, ECDH |
crypto.subtle.sign() / verify() |
Signature algorithm, key, signature bytes, cleartext | HMAC, RSASSA-PKCS1-v1_5, ECDSA, Ed25519 |
crypto.subtle.digest() |
Hash algorithm, data buffer, digest output | SHA-256, SHA-384, SHA-512 |
Binary Serialization Protocol
JavaScript cryptographic APIs operate on binary buffers (ArrayBuffer, Uint8Array) and opaque CryptoKey handles. Because these types cannot be passed directly across JSON IPC boundaries, webcrypto-interceptor normalizes them using structured envelopes:
{
"__t": "Uint8Array",
"len": 16,
"hex": "f3a19b88210344c8b291845199201a4e",
"utf8": "..."
}
{
"__t": "CryptoKey",
"type": "secret",
"extractable": true,
"algorithm": { "name": "AES-GCM", "length": 256 },
"usages": ["encrypt", "decrypt"]
}
Nonces and getRandomValues Rate Limiting
Initialization vectors (IVs) and cryptographic nonces are frequently generated via crypto.getRandomValues() immediately prior to encryption.
To prevent terminal flooding from WebGL canvases or math libraries calling random functions thousands of times per second, the interceptor applies a token-bucket rate limiter that selectively logs cryptographic-length buffers (12 bytes for AES-GCM IVs, 16 bytes for AES-CBC IVs).
DRM & HLS Stream Key Extraction
Capturing and decrypting HTTP Live Streaming (HLS) media segments (MPEG-TS, fMP4) and correlating them with raw cryptographic keys.
HLS Envelope Encryption Mechanism
HLS streams frequently protect media segments using envelope encryption (METHOD=AES-128 or METHOD=SAMPLE-AES). The player fetches a playlist manifest containing key tags:
#EXT-X-KEY:METHOD=AES-128,URI="https://stream.target.local/keys/key_01.bin",IV=0x00000000000000000000000000000001
#EXTINF:6.000,
segment_001.ts
#EXTINF:6.000,
segment_002.ts
When video players use JavaScript libraries (like hls.js, shaka-player, or video.js), segment decryption is delegated to the browser's crypto.subtle.decrypt() routine, often inside a dedicated Web Worker thread.
Worker Thread Interception
Because Puppeteer's standard console output does not capture background worker contexts by default, webcrypto-interceptor registers automatic CDP listeners on every created Web Worker target (Target.attachedToTarget).
When a worker calls crypto.subtle.importKey() to load the 16-byte raw AES key and then calls crypto.subtle.decrypt() to unpack media segments, the interceptor logs the exact key bytes and initialization vectors.
Offline Correlation Utility
node scripts/extract-keys.js session_capture_1730000000.jsonl
url: https://media.example.local/stream/segment_001.ts
key: f3a19b88210344c8b291845199201a4e
iv: 00000000000000000000000000000001
cmd: openssl enc -d -aes-128-cbc -K f3a19b88210344c8b291845199201a4e -iv 00000000000000000000000000000001 -in segment_001.ts -out segment_001_decrypted.ts
WebAssembly Bytecode Dumping
Intercepting client-side WebAssembly module compilation, streaming instantiations, and saving reconstructed binary bytecode to disk for reverse engineering.
Bytecode Interception Hooks
Target applications frequently embed compiled C/Rust/Go cryptography routines inside WebAssembly binaries to defeat pure JavaScript deobfuscation.
webcrypto-interceptor instruments the standard WebAssembly lifecycle APIs in src/page/stealth.js:
WebAssembly.compile(bytes)WebAssembly.instantiate(bytes, importObject)WebAssembly.compileStreaming(responsePromise)WebAssembly.instantiateStreaming(responsePromise, importObject)
Payload Chunking & Reassembly
Large WebAssembly modules can exceed individual CDP IPC string length limits. The in-page hook splits large bytecode buffers into 32KB hex-encoded chunks tagged with sequence numbers (seq, of, hash).
In Node.js, src/cdp/events.js reassembles the chunks in order, verifies the hash integrity, and writes the reconstructed binary to:
wasm_modules/module_<hash>.wasm
Export Function Wrapping
When an instantiated WebAssembly instance exposes exported functions (e.g., instance.exports.encryptBlock), the toolkit wraps each export function to log its pointer arguments and return values across the JavaScript/WASM boundary:
[00:00.820] [WASM COMPILE] WebAssembly.compile called with 14,280 bytes
[00:00.825] [WASM DUMPED] Saved bytecode to wasm_modules/module_d41d8cd9.wasm
[00:00.831] [WASM HOOK] Wrapped exports: ["encryptBlock", "deriveSecret", "memory"]
CLI Flags & Environment Variables
Complete manual for all command-line arguments, runtime flags, and configuration options supported by capture_server.js.
Command Synopsis
node capture_server.js <URL> [options]
Options & Flags
| Flag / Option | Type | Default | Description |
|---|---|---|---|
<URL> |
string | required | Target URL to open and observe. Must use http:// or https://. |
--gui |
flag | false (headless) | Launch Chromium with a visible browser window for manual interaction. |
--brave |
flag | false | Prefer Brave Browser binary over default Google Chrome or Chromium. |
--full |
flag | false | Save all loaded scripts, sourcemaps, responses, and WASM bytecode to disk. |
--out <dir> |
string | ./<host>_extracted_<ts> |
Custom output directory for assets dumped under --full mode. |
--proxy <url> |
string | none | Route browser network requests through an upstream HTTP/SOCKS proxy. |
--ua <string> |
string | none | Override default Chromium User-Agent header string. |
--hook <expr> |
string | none | Trap and log every invocation of JavaScript expression with arguments (repeatable). |
--hook-return <expr> |
string | none | Trap arguments AND return values of expression (repeatable). |
--heap-diff <sec> |
number | disabled | Take baseline heap snapshot on load, second snapshot after N seconds, diff new objects. |
--timeout <sec> |
number | 60 | Maximum navigation and capture timeout in seconds. |
--help |
flag | false | Print CLI option help and exit. |
Environment Variables
| Variable | Default | Description |
|---|---|---|
PUPPETEER_EXECUTABLE_PATH |
auto-detected | Explicit filesystem path to Chrome, Chromium, or Brave executable. |
PUPPETEER_SKIP_DOWNLOAD |
false | Skip downloading Chromium archive on npm install. |
SKIP_E2E |
false | Skip live browser tests when running npm test. |
Verification Oracle (verify-reimpl.js)
An automated testing oracle for validating standalone JavaScript reimplementations of obfuscated client-side functions against recorded ground-truth session data.
The Clean-Room Verification Loop
When reverse-engineering proprietary client-side signers, token generators, or custom obfuscators, researchers frequently rewrite the logic into a standalone library.
scripts/verify-reimpl.js provides an automated oracle that proves whether your candidate implementation produces mathematically identical outputs across all recorded real-world inputs:
Record Ground Truth -> Write Clean-Room Candidate -> verify-reimpl.js -> VERIFIED
1. Recording Input/Output Pairs
node capture_server.js "https://target.local" --hook-return "window.signPayload" --gui
2. Executing the Oracle
node scripts/verify-reimpl.js --session session_capture_1730000000.jsonl --candidate ./my_signer.js --label "window.signPayload"
3. Sandboxed Execution Security
- Network Isolation: The oracle executes candidate code inside an isolated headless browser page loaded to
about:blankwith zero network privileges. - No Target Access: The candidate cannot communicate with the target website or exfiltrate session data.
- Strict Deep Equality: Compares candidate outputs against captured ground truth using type-strict, order-aware array checks and key-set-aware object diffing.
Live CDP Stream Simulator
Interactive console simulator showing real-time event logs, binary envelopes, and telemetry streamed by webcrypto-interceptor during live target capture.