[GH-ISSUE #8543] [Feature] Format-native inspector for V8 code cache (.jsc / bytenode artifacts) #6638

Open
opened 2026-11-01 21:12:51 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @Raed2180416 on GitHub (Nov 1, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8543

Area

Native analysis or decompilation

Current limitation

Bytenode-compiled .jsc files — the V8 code cache container written by vm/v8.compileFunction, used by bytenode 2.x and shipped inside Node single-executable applications and Electron apps — have no format-native inspector in REA.

What an agent can do today:

  • inspect_binary_layout on an SEA shows the ELF/PE container, not the embedded code cache.
  • inspect_artifact inventories the file but decodes no V8 structures.
  • Source recovery requires version-specific tooling (View8-style decompilers pinned to V8 builds) that REA neither installs nor runs, and nothing in the current results points an agent there.

The code cache is opaque bytes to the current tool family even though its header is structurally parseable and its payload retains readable string literals.

Use cases

  • Identify a .jsc shipped inside a Node SEA or Electron app and report which V8 build produced it, before investing in decompilation attempts.
  • Report that a .jsc is metadata only (no source) so agents stop trying text-recovery paths against it.
  • Locate retained string literals (config keys, endpoints, log strings) with byte ranges as leads, with an explicit statement that the ranges are byte-pattern observations, not parsed heap objects.

Desired outcome and success criteria

A format-native inspection in the same family as inspect_python_pyc, inspect_erlang_beam and inspect_kernel_module that, for one selected .jsc:

  1. Validates the container (magic, header size, payload-length consistency) and reports observed header facts: magic, version hash, source hash, flag hash, payload length, total byte size and sha256.
  2. Reports embedded string literals with byte ranges, labeled as byte-pattern observations inside the payload (not parsed heap objects).
  3. States explicit limitations: the payload is serialized V8 heap data, not source; logic recovery requires version-specific decompilers (View8-style) that REA does not install or run; header field roles beyond the observed table are version-dependent.
  4. Never executes the selected file and never claims recovered source.

Success criteria: an agent selecting a bytenode .jsc learns the producing V8 line and its extraction options without opening a disassembler or guessing.

Expected interaction with targets

  • Read existing files or captured data

Target and scope details

  • Scope question: whether this joins the format-native parser family as a new MCP tool (contract, composition, availability, docs per docs/tool-design.md) or starts smaller. The header parse is small; the tool wiring is the bulk.
  • Non-goals: decompiling or executing the payload; no bytenode or View8 dependency inside REA.
  • The header table below is observed on one V8 build. A tool must treat offsets and the magic constant as version-dependent facts, not a fixed ABI, and fail closed on unknown magic.

Verification available so far

I have a prototype or observed results

Verification details

Observed on node v24.22.1 (V8 13.6.233.17-node.58) with bytenode 2.1.0, generated locally from an authored sample; no private data:

  • Bytenode compileFile output for a ~120-byte source: 944 bytes = 32-byte header + 912-byte payload.
  • Header as little-endian u32s at offsets 0..28:
    • 0x00 magic 0xC0DE0689 (V8's 0xC0DE0000 ^ ExternalReferenceTable::kSize; the constant varies per V8 build, so it is a build fingerprint, not a universal constant)
    • 0x04 version hash 0xDC338CFA (identical across two scripts on the same V8 build)
    • 0x08 source hash (differs per script: 187 vs 67 observed)
    • 0x0C flag hash (differs across compilation modes)
    • 0x10 constant 1273523860 across both samples on this build (role inferred: build-level constant such as a read-only snapshot checksum; unconfirmed)
    • 0x14 payload length (912 and 488 for the two samples; consistent with total size minus a 32-byte header)
    • 0x18, 0x1C zeros on both samples (role unconfirmed: checksum or reserved)
  • vm.Script.createCachedData() on the same Node produces the same container: magic and version hash match the bytenode file; source and flag hashes differ per script, as expected. This gives a dependency-free fixture path: tests can generate the fixture from the running V8 instead of committing a version-pinned binary.
  • String literals survive verbatim in the payload: a marker token is present as plain bytes in both the bytenode output and the createCachedData output. A scan yields byte ranges, not heap objects.
  • Loading the bytenode output through the bytenode require hook on the same Node runs, confirming the file is a loadable code cache, not an archive.

Alternatives considered

  • inspect_artifact / inspect_binary_layout: container-level only; no V8 header decode.
  • Generic string scanning without a format-native wrapper: loses the header facts, the validation, and the typed limitations.
  • A decompiler integration (View8): out of scope for REA's no-install, no-execution boundaries; this request only asks that limitations point at such tooling.

Possible direction

Draft tool shape following inspect_python_pyc:

  • Name: inspect_v8_code_cache
  • Input: path (absolute), optional expected_sha256, optional detail
  • Output: artifact identity (sha256, bytes), a header facts table with per-field observed/inferred roles, payload-length consistency check, embedded strings with half-open UTF-8/Latin-1 byte ranges labeled as byte-pattern observations, and limitations naming the source-inability and the View8/bytenode references.
  • Fail closed: unknown magic, truncated header, or payload-length mismatch is an explicit error, never a partial parse.
  • Test plan: (1) in-test fixture via vm.Script.createCachedData() asserting header fields, payload-length consistency and token presence with byte range; (2) error cases: truncated header, corrupted magic, payload-length mismatch; (3) no false-positive overlap with the other format-native parsers; (4) boundary test that the operation never writes or executes the input.
  • Wiring checklist if approved as a tool: contract (toolFamilies, toolEffects), provider + service + port, composition + capability declarations, server registration, analysis view, CLI command, docs page, generated-catalog regeneration, conformance coverage.
  • Scope note: if the full tool is too large for one review, the header/facts half alone (without embedded-string extraction) is a smaller first PR; the string scan can ship separately with its own observation label.

References

Originally created by @Raed2180416 on GitHub (Nov 1, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8543 ### Area Native analysis or decompilation ### Current limitation Bytenode-compiled `.jsc` files — the V8 code cache container written by `vm`/`v8.compileFunction`, used by bytenode 2.x and shipped inside Node single-executable applications and Electron apps — have no format-native inspector in REA. What an agent can do today: - `inspect_binary_layout` on an SEA shows the ELF/PE container, not the embedded code cache. - `inspect_artifact` inventories the file but decodes no V8 structures. - Source recovery requires version-specific tooling (View8-style decompilers pinned to V8 builds) that REA neither installs nor runs, and nothing in the current results points an agent there. The code cache is opaque bytes to the current tool family even though its header is structurally parseable and its payload retains readable string literals. ### Use cases - Identify a `.jsc` shipped inside a Node SEA or Electron app and report which V8 build produced it, before investing in decompilation attempts. - Report that a `.jsc` is metadata only (no source) so agents stop trying text-recovery paths against it. - Locate retained string literals (config keys, endpoints, log strings) with byte ranges as leads, with an explicit statement that the ranges are byte-pattern observations, not parsed heap objects. ### Desired outcome and success criteria A format-native inspection in the same family as `inspect_python_pyc`, `inspect_erlang_beam` and `inspect_kernel_module` that, for one selected `.jsc`: 1. Validates the container (magic, header size, payload-length consistency) and reports observed header facts: magic, version hash, source hash, flag hash, payload length, total byte size and sha256. 2. Reports embedded string literals with byte ranges, labeled as byte-pattern observations inside the payload (not parsed heap objects). 3. States explicit limitations: the payload is serialized V8 heap data, not source; logic recovery requires version-specific decompilers (View8-style) that REA does not install or run; header field roles beyond the observed table are version-dependent. 4. Never executes the selected file and never claims recovered source. Success criteria: an agent selecting a bytenode `.jsc` learns the producing V8 line and its extraction options without opening a disassembler or guessing. ### Expected interaction with targets - Read existing files or captured data ### Target and scope details - Scope question: whether this joins the format-native parser family as a new MCP tool (contract, composition, availability, docs per docs/tool-design.md) or starts smaller. The header parse is small; the tool wiring is the bulk. - Non-goals: decompiling or executing the payload; no bytenode or View8 dependency inside REA. - The header table below is observed on one V8 build. A tool must treat offsets and the magic constant as version-dependent facts, not a fixed ABI, and fail closed on unknown magic. ### Verification available so far I have a prototype or observed results ### Verification details Observed on node v24.22.1 (V8 13.6.233.17-node.58) with bytenode 2.1.0, generated locally from an authored sample; no private data: - Bytenode `compileFile` output for a ~120-byte source: 944 bytes = 32-byte header + 912-byte payload. - Header as little-endian u32s at offsets 0..28: - `0x00` magic `0xC0DE0689` (V8's `0xC0DE0000 ^ ExternalReferenceTable::kSize`; the constant varies per V8 build, so it is a build fingerprint, not a universal constant) - `0x04` version hash `0xDC338CFA` (identical across two scripts on the same V8 build) - `0x08` source hash (differs per script: 187 vs 67 observed) - `0x0C` flag hash (differs across compilation modes) - `0x10` constant `1273523860` across both samples on this build (role inferred: build-level constant such as a read-only snapshot checksum; unconfirmed) - `0x14` payload length (912 and 488 for the two samples; consistent with total size minus a 32-byte header) - `0x18`, `0x1C` zeros on both samples (role unconfirmed: checksum or reserved) - `vm.Script.createCachedData()` on the same Node produces the same container: magic and version hash match the bytenode file; source and flag hashes differ per script, as expected. This gives a dependency-free fixture path: tests can generate the fixture from the running V8 instead of committing a version-pinned binary. - String literals survive verbatim in the payload: a marker token is present as plain bytes in both the bytenode output and the `createCachedData` output. A scan yields byte ranges, not heap objects. - Loading the bytenode output through the bytenode require hook on the same Node runs, confirming the file is a loadable code cache, not an archive. ### Alternatives considered - `inspect_artifact` / `inspect_binary_layout`: container-level only; no V8 header decode. - Generic string scanning without a format-native wrapper: loses the header facts, the validation, and the typed limitations. - A decompiler integration (View8): out of scope for REA's no-install, no-execution boundaries; this request only asks that limitations point at such tooling. ### Possible direction Draft tool shape following `inspect_python_pyc`: - Name: `inspect_v8_code_cache` - Input: `path` (absolute), optional `expected_sha256`, optional `detail` - Output: artifact identity (sha256, bytes), a header facts table with per-field observed/inferred roles, payload-length consistency check, embedded strings with half-open UTF-8/Latin-1 byte ranges labeled as byte-pattern observations, and limitations naming the source-inability and the View8/bytenode references. - Fail closed: unknown magic, truncated header, or payload-length mismatch is an explicit error, never a partial parse. - Test plan: (1) in-test fixture via `vm.Script.createCachedData()` asserting header fields, payload-length consistency and token presence with byte range; (2) error cases: truncated header, corrupted magic, payload-length mismatch; (3) no false-positive overlap with the other format-native parsers; (4) boundary test that the operation never writes or executes the input. - Wiring checklist if approved as a tool: contract (`toolFamilies`, `toolEffects`), provider + service + port, composition + capability declarations, server registration, analysis view, CLI command, docs page, generated-catalog regeneration, conformance coverage. - Scope note: if the full tool is too large for one review, the header/facts half alone (without embedded-string extraction) is a smaller first PR; the string scan can ship separately with its own observation label. ### References - View8: https://github.com/suleram/View8 (version-pinned V8 bytecode decompiler) - Bytenode: https://github.com/bytenode/bytenode - Bytenode issue about View8-era protection limits: https://github.com/bytenode/bytenode/issues/241 - Check Point Research on compiled V8 JavaScript and View8: https://research.checkpoint.com/2024/exploring-compiled-v8-javascript-usage-in-malware - nodejs/single-executable#66 "Compiled to bytecode or something?" (open) - nodejs/single-executable#73 "bytecode-only SEA caching?" (closed) - V8 code serializer header (`SerializedCodeData` in V8 `src/snapshot/`), magic `0xC0DE0000 ^ ExternalReferenceTable::kSize` - Node `vm.Script.createCachedData()`: https://nodejs.org/api/vm.html#scriptcreatecacheddata
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6638
No description provided.