[GH-ISSUE #2171] fix(javascript): renamed bundler exports parameters produce empty export-name metadata #717

Closed
opened 2026-10-15 18:27:39 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2171

Summary

Public JavaScript application analysis recovers Webpack/Rspack literal chunk module factories but omits their statically declared export names when the exports object is bound to the second parameter named __webpack_exports__ or e. The equivalent factory using the parameter name exports returns the names correctly. This leaves the recovered module's exports observation incomplete even though the module is present and analysis completes successfully.

Runtime or environment

Tested separately against a prepared source build at c22111eca2 and the published rea-agents 6.4.0 package. Both ran on Node.js v24.21.0, macOS 27.0.1, arm64. This Node version satisfies the supported package runtime range; the exact contributor-pinned Node version was not used. No native analysis provider is required.

The target is a local directory containing one inert JavaScript asset with an initialized chunk registration and literal factory table. The interface is CLI. The affected area is Evidence, artifacts, or reconstruction; the earliest incorrect result is factory export-name collection during static reconstruction.

Steps to reproduce

  1. Use either tested baseline's CLI. For a built source checkout, replace rea below with node scripts/rea.mjs from that checkout.
  2. Create the fixture and analyze it using the following commands. The output file is outside the analyzed directory.
fixture_dir="$(mktemp -d)"
mkdir "$fixture_dir/input"
cat > "$fixture_dir/input/bundle.js" <<'JS'
(globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([["main"], {
  1: function(module, __webpack_exports__, __webpack_require__) {
    __webpack_require__.d(__webpack_exports__, { alpha: () => 1, "beta.gamma": () => 2 });
  },
  2: function(m, e, r) { r.d(e, { alpha: () => 1, "beta.gamma": () => 2 }); },
  3: function(module, exports, __webpack_require__) {
    __webpack_require__.d(exports, { alpha: () => 1, "beta.gamma": () => 2 });
  },
  4: function(module, __webpack_exports__, __webpack_require__) {
    Object.defineProperty(__webpack_exports__, "delta", { enumerable: true, value: 3 });
  },
  5: function(module, exports, __webpack_require__) {
    exports.ordinary = 1;
    module.exports.replaced = 2;
    Object.defineProperty(exports, "descriptor", { enumerable: true, value: 3 });
  },
  6: function(module, __webpack_exports__, __webpack_require__) {
    const other = {}; __webpack_require__.d(other, { unrelated: () => 1 });
  }
}]);
JS
rea analyze-javascript-application "$fixture_dir/input" \
  --artifact-format directory --json > "$fixture_dir/evidence.json"
python3 - "$fixture_dir/evidence.json" <<'PYCODE'
import json, sys
with open(sys.argv[1]) as stream:
    graph = json.load(stream)["normalized_result"]["graph"]
rows = []
for node in graph["nodes"]:
    if node["kind"] != "javascript-module":
        continue
    for observation in node["observations"]:
        properties = observation.get("properties", {})
        if properties.get("runtime") == "webpackChunkFixture":
            rows.append({"module_key": properties["module_key"],
                         "exports": properties["exports"]})
print(json.dumps(sorted(rows, key=lambda row: row["module_key"])))
PYCODE
  1. To check the actual exports-object binding on this benign fixture, run the following independent Node oracle. It passes each factory the same module's exports object as the second argument and supplies the define-export helper used by the factories. This executes only the supplied fixture; REA analysis itself does not execute it.
node - "$fixture_dir/input/bundle.js" <<'JS'
require(process.argv[2]);
const modules = globalThis.webpackChunkFixture[0][1];
function load(id) {
  const module = { exports: {} };
  modules[id](module, module.exports, load);
  return module.exports;
}
load.d = function(target, definitions) {
  for (const key of Object.keys(definitions))
    Object.defineProperty(target, key, { enumerable: true, get: definitions[key] });
};
console.log(JSON.stringify(Object.keys(modules).map(id => ({
  module_key: id, exports: Object.keys(load(id)).sort()
}))));
JS
  1. The Rspack variant replaces webpackChunkFixture with rspackChunkFixture in the input, output selector and oracle. Both variants and the same controls were independently executed on both tested builds. Remove the owned temporary directory after inspecting the results.

Expected behavior

For an admitted literal bundler factory, recover the keys statically declared on that factory's module exports object. The local name of the parameter receiving that object must not change the result. Modules 1 and 2 should have the same exports list as module 3, ["alpha","beta.gamma"]; module 4 should report ["delta"].

The ordinary CommonJS control in module 5 should keep ["descriptor","ordinary","replaced"]. Module 6 defines a property on a separate object and should remain empty. Its unrelated key is not a module export. The expected target depends on object binding, not on accepting every object passed to a helper.

Actual behavior

Both tested builds exit 0 and recover all six factories in each chunk-runtime variant. The graph reports coverage.status: "complete", truncated: false and omitted_count: 0. The relevant module observations are:

[
  {"module_key":"1","exports":[]},
  {"module_key":"2","exports":[]},
  {"module_key":"3","exports":["alpha","beta.gamma"]},
  {"module_key":"4","exports":[]},
  {"module_key":"5","exports":["descriptor","ordinary","replaced"]},
  {"module_key":"6","exports":[]}
]

The independent Node oracle returns ["alpha","beta.gamma"] for modules 1, 2 and 3, ["delta"] for module 4, and the same passing control results for modules 5 and 6. The missing names therefore result from recovery of the exports object, rather than a malformed factory or a helper that does not expose those properties.

Each reduced chunk variant was executed once per tested build. An earlier fixture with additional alias and shadowed-identifier controls also reproduced the direct-parameter omissions before those exploratory cases were removed. These are bounded fixture results; no production incidence or compiler-version coverage was measured.

Evidence

  • Expected source: docs/javascript-artifact-reconstruction.md:1-57,297-306 documents public inert directory analysis and Webpack/Rspack literal factory recovery. src/domain/javascript/javascriptAstFingerprint.ts:31-44 states the statically declared CommonJS/bundler export-name contract; tests/acceptance/applications/applicationWorkflowCli.test.ts:58-85 supplies a working define-export helper control. The native loader oracle establishes the exports-object identity for the supplied factories.
  • Failure source: The public CLI observations above on both tested builds. src/domain/javascript/javascriptStaticAnalysisBundler.ts:232-258 supplies the factory to export collection, while src/domain/javascript/javascriptAstFingerprint.ts:98-125 accepts only target paths spelled exports or module.exports. src/application/javascript/JavaScriptArtifactGraphBundlers.ts:132-150 projects that collected list into the module observation.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

The public route passes request validation, includes the ordinary .js source, parses it, recognizes a static push registration with literal chunk-ID and factory tables, and accepts every function-expression factory. The third positional require identifier is retained for the renamed factories. No parser recovery, resource truncation or omitted source explains the empty lists. The published and source-built export collectors were compared and have identical bytes.

The source build also recovers a standalone bootstrap that demonstrably dispatches its factories with the exports object as the second argument. The published build does not recover that bootstrap shape, so the shared reproduction and restoration check use chunk registrations. No Webpack/Rspack compiler was run, and no MCP runtime execution is claimed.

Restoration check

Reanalyze the supplied fixture through the public workflow. Modules 1 and 2 must report ["alpha","beta.gamma"], and module 4 must report ["delta"], for both initialized chunk-runtime names. Module 3 must retain its helper-declared names, module 5 must retain its ordinary CommonJS names, and module 6 must continue reporting no export keys. Keep the input's module identities and source provenance intact. A passing result must follow the factory's exports-object identity rather than collecting all helper targets or adding a particular identifier spelling.

Additional context

The related closed exact CommonJS export-key PR was superseded by a landed correction to static property-key handling. Its body and maintainer comments were checked. This report concerns the separately reproduced factory exports-target binding omission, which persists after that correction. Open and closed issue/PR searches, including the reporter's prior export/module reports, found no exact matching failure.

The generic analyze directory route shares the same application service. Public CLI options expose no exports-parameter override. The documented recover_javascript_sources workflow may provide useful readable modules, but requires a caller-supplied Wakaru engine on Linux x64 and was not exercised on this host. It has not been established as a way to restore the same recovered factory export metadata. Renaming a target artifact would change its bytes and provenance.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A supported static reconstruction workflow returns incomplete export-name metadata for admitted literal factories in both tested builds. The bounded cases recover the modules and complete analysis, but callers cannot rely on their empty exports lists. No broader workflow outage or production frequency was demonstrated.
  • Workaround status: unverified
  • Workaround basis: The generic directory route shares the affected service and no public export-binding override is exposed. The documented external-engine recovery route was not run on the required Linux x64 host, and restoration of the same factory export metadata through it remains unverified.
Originally created by @coygeek on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2171 ## Summary Public JavaScript application analysis recovers Webpack/Rspack literal chunk module factories but omits their statically declared export names when the exports object is bound to the second parameter named `__webpack_exports__` or `e`. The equivalent factory using the parameter name `exports` returns the names correctly. This leaves the recovered module's `exports` observation incomplete even though the module is present and analysis completes successfully. ## Runtime or environment Tested separately against a prepared source build at c22111eca2c4bc84a084f12711f405b3e7422a40 and the published `rea-agents` 6.4.0 package. Both ran on Node.js v24.21.0, macOS 27.0.1, arm64. This Node version satisfies the supported package runtime range; the exact contributor-pinned Node version was not used. No native analysis provider is required. The target is a local directory containing one inert JavaScript asset with an initialized chunk registration and literal factory table. The interface is CLI. The affected area is Evidence, artifacts, or reconstruction; the earliest incorrect result is factory export-name collection during static reconstruction. ## Steps to reproduce 1. Use either tested baseline's CLI. For a built source checkout, replace `rea` below with `node scripts/rea.mjs` from that checkout. 2. Create the fixture and analyze it using the following commands. The output file is outside the analyzed directory. ```sh fixture_dir="$(mktemp -d)" mkdir "$fixture_dir/input" cat > "$fixture_dir/input/bundle.js" <<'JS' (globalThis.webpackChunkFixture = globalThis.webpackChunkFixture || []).push([["main"], { 1: function(module, __webpack_exports__, __webpack_require__) { __webpack_require__.d(__webpack_exports__, { alpha: () => 1, "beta.gamma": () => 2 }); }, 2: function(m, e, r) { r.d(e, { alpha: () => 1, "beta.gamma": () => 2 }); }, 3: function(module, exports, __webpack_require__) { __webpack_require__.d(exports, { alpha: () => 1, "beta.gamma": () => 2 }); }, 4: function(module, __webpack_exports__, __webpack_require__) { Object.defineProperty(__webpack_exports__, "delta", { enumerable: true, value: 3 }); }, 5: function(module, exports, __webpack_require__) { exports.ordinary = 1; module.exports.replaced = 2; Object.defineProperty(exports, "descriptor", { enumerable: true, value: 3 }); }, 6: function(module, __webpack_exports__, __webpack_require__) { const other = {}; __webpack_require__.d(other, { unrelated: () => 1 }); } }]); JS rea analyze-javascript-application "$fixture_dir/input" \ --artifact-format directory --json > "$fixture_dir/evidence.json" python3 - "$fixture_dir/evidence.json" <<'PYCODE' import json, sys with open(sys.argv[1]) as stream: graph = json.load(stream)["normalized_result"]["graph"] rows = [] for node in graph["nodes"]: if node["kind"] != "javascript-module": continue for observation in node["observations"]: properties = observation.get("properties", {}) if properties.get("runtime") == "webpackChunkFixture": rows.append({"module_key": properties["module_key"], "exports": properties["exports"]}) print(json.dumps(sorted(rows, key=lambda row: row["module_key"]))) PYCODE ``` 3. To check the actual exports-object binding on this benign fixture, run the following independent Node oracle. It passes each factory the same module's exports object as the second argument and supplies the define-export helper used by the factories. This executes only the supplied fixture; REA analysis itself does not execute it. ```sh node - "$fixture_dir/input/bundle.js" <<'JS' require(process.argv[2]); const modules = globalThis.webpackChunkFixture[0][1]; function load(id) { const module = { exports: {} }; modules[id](module, module.exports, load); return module.exports; } load.d = function(target, definitions) { for (const key of Object.keys(definitions)) Object.defineProperty(target, key, { enumerable: true, get: definitions[key] }); }; console.log(JSON.stringify(Object.keys(modules).map(id => ({ module_key: id, exports: Object.keys(load(id)).sort() })))); JS ``` 4. The Rspack variant replaces `webpackChunkFixture` with `rspackChunkFixture` in the input, output selector and oracle. Both variants and the same controls were independently executed on both tested builds. Remove the owned temporary directory after inspecting the results. ## Expected behavior For an admitted literal bundler factory, recover the keys statically declared on that factory's module exports object. The local name of the parameter receiving that object must not change the result. Modules 1 and 2 should have the same `exports` list as module 3, `["alpha","beta.gamma"]`; module 4 should report `["delta"]`. The ordinary CommonJS control in module 5 should keep `["descriptor","ordinary","replaced"]`. Module 6 defines a property on a separate object and should remain empty. Its `unrelated` key is not a module export. The expected target depends on object binding, not on accepting every object passed to a helper. ## Actual behavior Both tested builds exit 0 and recover all six factories in each chunk-runtime variant. The graph reports `coverage.status: "complete"`, `truncated: false` and `omitted_count: 0`. The relevant module observations are: ```json [ {"module_key":"1","exports":[]}, {"module_key":"2","exports":[]}, {"module_key":"3","exports":["alpha","beta.gamma"]}, {"module_key":"4","exports":[]}, {"module_key":"5","exports":["descriptor","ordinary","replaced"]}, {"module_key":"6","exports":[]} ] ``` The independent Node oracle returns `["alpha","beta.gamma"]` for modules 1, 2 and 3, `["delta"]` for module 4, and the same passing control results for modules 5 and 6. The missing names therefore result from recovery of the exports object, rather than a malformed factory or a helper that does not expose those properties. Each reduced chunk variant was executed once per tested build. An earlier fixture with additional alias and shadowed-identifier controls also reproduced the direct-parameter omissions before those exploratory cases were removed. These are bounded fixture results; no production incidence or compiler-version coverage was measured. ## Evidence - Expected source: `docs/javascript-artifact-reconstruction.md:1-57,297-306` documents public inert directory analysis and Webpack/Rspack literal factory recovery. `src/domain/javascript/javascriptAstFingerprint.ts:31-44` states the statically declared CommonJS/bundler export-name contract; `tests/acceptance/applications/applicationWorkflowCli.test.ts:58-85` supplies a working define-export helper control. The native loader oracle establishes the exports-object identity for the supplied factories. - Failure source: The public CLI observations above on both tested builds. `src/domain/javascript/javascriptStaticAnalysisBundler.ts:232-258` supplies the factory to export collection, while `src/domain/javascript/javascriptAstFingerprint.ts:98-125` accepts only target paths spelled `exports` or `module.exports`. `src/application/javascript/JavaScriptArtifactGraphBundlers.ts:132-150` projects that collected list into the module observation. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete The public route passes request validation, includes the ordinary `.js` source, parses it, recognizes a static `push` registration with literal chunk-ID and factory tables, and accepts every function-expression factory. The third positional require identifier is retained for the renamed factories. No parser recovery, resource truncation or omitted source explains the empty lists. The published and source-built export collectors were compared and have identical bytes. The source build also recovers a standalone bootstrap that demonstrably dispatches its factories with the exports object as the second argument. The published build does not recover that bootstrap shape, so the shared reproduction and restoration check use chunk registrations. No Webpack/Rspack compiler was run, and no MCP runtime execution is claimed. ## Restoration check Reanalyze the supplied fixture through the public workflow. Modules 1 and 2 must report `["alpha","beta.gamma"]`, and module 4 must report `["delta"]`, for both initialized chunk-runtime names. Module 3 must retain its helper-declared names, module 5 must retain its ordinary CommonJS names, and module 6 must continue reporting no export keys. Keep the input's module identities and source provenance intact. A passing result must follow the factory's exports-object identity rather than collecting all helper targets or adding a particular identifier spelling. ## Additional context The related closed [exact CommonJS export-key PR](https://github.com/morluto/rea/pull/1600) was superseded by a landed correction to static property-key handling. Its body and maintainer comments were checked. This report concerns the separately reproduced factory exports-target binding omission, which persists after that correction. Open and closed issue/PR searches, including the reporter's prior export/module reports, found no exact matching failure. The generic `analyze` directory route shares the same application service. Public CLI options expose no exports-parameter override. The documented `recover_javascript_sources` workflow may provide useful readable modules, but requires a caller-supplied Wakaru engine on Linux x64 and was not exercised on this host. It has not been established as a way to restore the same recovered factory export metadata. Renaming a target artifact would change its bytes and provenance. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A supported static reconstruction workflow returns incomplete export-name metadata for admitted literal factories in both tested builds. The bounded cases recover the modules and complete analysis, but callers cannot rely on their empty exports lists. No broader workflow outage or production frequency was demonstrated. - Workaround status: unverified - Workaround basis: The generic directory route shares the affected service and no public export-binding override is exposed. The documented external-engine recovery route was not run on the required Linux x64 host, and restoration of the same factory export metadata through it remains unverified.
bsakaguchi 2026-10-15 18:27:39 +00:00
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#717
No description provided.