Skip to content

ipk-verify: handle JS services that bundle their own Node runtime#7

Merged
mariotaku merged 3 commits into
mainfrom
claude/nodejs-dev-toolbox-detection-k6dtai
Jul 14, 2026
Merged

ipk-verify: handle JS services that bundle their own Node runtime#7
mariotaku merged 3 commits into
mainfrom
claude/nodejs-dev-toolbox-detection-k6dtai

Conversation

@mariotaku

Copy link
Copy Markdown
Member

Motivation

A webOS JS service is normally run by the TV's firmware Node, so ipk-verify grades its JavaScript against that Node's version. But some apps ship their own Node runtime inside the IPK (e.g. a bundled node18 + ffmpeg) and run the heavy code off that, spawned as a child process. For those, the current checks are both wrong (they gate vendored server code against the firmware Node it never touches) and incomplete (they say nothing about whether the bundled runtime actually loads on the target firmware).

This came up on a real submission (apps-repo#203) whose author correctly pointed out the app ships its own Node 18. This PR makes the tool model that packaging correctly.

Changes

1. Scope the service ES-level scan to the entry graph (webdetect)

The service ES level was derived from every .js under the service directory, which sweeps in node_modules and a bundled server written for a newer Node — code the firmware Node never runs. Now the scan starts at package.json main and follows only the static, relative module graph (require('./x'), import/export … from, import('./x'), side-effect import './x'); bare specifiers (npm/built-ins) are not followed. This fixes the false failure where a self-shipped-Node app was gated against the TV's old Node.

2. Report a JS service's bundled native binaries (ipk-verify)

Scan a non-native service's directory and classify each bundled ELF (executable vs shared library — a PT_INTERP header distinguishes a PIE executable from a real shared object) with its architecture. Surfaced as supplementary --details info, folded into a <details> block on Markdown. New bin_lib::BundledArtifact does the header-only classification.

3. Verify the bundled runtime against firmware libraries (ipk-verify)

Each bundled executable becomes a verifiable unit (its exe plus the libraries reachable via its rpath and sibling lib/ dir — these runtimes locate libs through the loader's --library-path at spawn, not a DT_RPATH), then gets checked against each firmware's libraries like a native component. The report renders one native-style table — bundled executables, then the unique bundled libraries, OK/failed per firmware — folded into a <details> block. This is supplementary and never gates the package verdict.

Also hardens BinaryInfo/LibraryInfo parsing to treat an ELF with no .dynstr/.dynsym as having no dynamic deps instead of panicking (a bundled statically-linked ffmpeg hit that latent unwrap()).

What it catches

Run against real packages (Markdown output):

  • StremioBrew — service now correctly reads as ES5 (not gated against the TV Node); bundled node18/ffmpeg/libs load from webOS 2.2.3 onward, failing only on webOS 1.x where glibc is too old — consistent with the app's stated webOS 4.x target.
  • Homebrew Channel — surfaced a genuine gap: its bundled rsync dynamically links libcrypto.so.1.1, present on only 3 of 13 firmware generations, so rsync silently won't run on the rest. dropbear/scp/sftp-server/telnetd are fine everywhere.

Testing

  • New/updated unit tests: entry-graph scan (require + import, node_modules/bare-specifier exclusion), ELF classification, parser hardening, and the parse-time bundled-binary wiring.
  • cargo test green for webdetect-lib, bin-lib, ipk-lib, ipk-verify.
  • End-to-end verified on the StremioBrew and Homebrew Channel IPKs in both Markdown and plain formats; confirmed the supplementary tables do not change the exit code.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 3 commits July 13, 2026 10:35
A Node service's ES level was derived from every `.js` under the
service directory (a recursive `collect_js` walk). That wrongly pulls in
vendored code — `node_modules`, or a streaming server the app spawns on
its own bundled Node — whose syntax level says nothing about what the
firmware's Node.js executes. A package that ships its own newer Node
would be failed against the TV's old Node purely on that vendored code.

Grade only the code the firmware Node actually runs: start at
`package.json` `main` (default `index.js`) and follow the static,
relative module graph via `require('./x')`, `import`/`export … from`,
`import('./x')`, and side-effect `import './x'`. Bare specifiers
(npm packages, built-ins) are not followed, and traversal is confined
to the service directory. Add unit tests covering entry-graph scoping,
require/import resolution, and exclusion of vendored/bare-specifier code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtKyCd9ucV7PRGH9LQZRRg
A JS/Node service run by the firmware's Node can still ship native ELF
binaries beside its scripts — its own `node`, `ffmpeg`, and `.so`s — but
the report showed nothing about them (only native components list an
exe/libs). That hides exactly the detail a reviewer needs to understand a
"bundles its own runtime" package, e.g. that every binary is 32-bit ARM
(so aarch64 TVs are out).

Scan a non-native service's directory at parse time and classify each
bundled ELF as an executable or shared library (a PT_INTERP program
header distinguishes a PIE executable from a real shared object) with its
architecture. Surface the list as supplementary `--details` info that
never affects the verdict: folded into a <details> block on Markdown,
a plain bullet list otherwise.

New `bin_lib::BundledArtifact` does the header-only classification;
`ServiceInfo` carries the list to the verifier and renderer. Adds unit
tests for classification and the parse-time scan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtKyCd9ucV7PRGH9LQZRRg
Building on the bundled-binary inventory, actually check whether the
native runtime a JS service ships (its own node/ffmpeg + their .so's)
can load on each target firmware — the same binary/library resolution a
native component gets, but supplementary (it never gates the verdict).

At parse time each bundled executable becomes a verifiable unit: its
`exe` plus the libraries reachable via its rpath and its sibling `lib`
dir (these runtimes locate libs through the loader's `--library-path`
at spawn, not a DT_RPATH, so the sibling `lib/` is treated as a search
path). `verify_for_firmware` then verifies each against the firmware's
libraries. The report renders one native-style table — bundled
executables, then the unique bundled libraries, OK/failed per firmware —
folded into a <details> block.

Also harden `BinaryInfo`/`LibraryInfo` parsing to treat an ELF with no
`.dynstr`/`.dynsym` as having no dynamic deps instead of panicking; a
bundled statically-linked `ffmpeg` hit that latent unwrap.

Adds Clone to BinaryInfo/Component (to store bundled units), a `bundled`
field on ComponentVerifyResult, and tests for the parse-time wiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtKyCd9ucV7PRGH9LQZRRg
@mariotaku mariotaku merged commit a981208 into main Jul 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants