[GH-ISSUE #6553] [Bug] A shadowed Object.defineProperty call creates a nonexistent bundle export #3944

Closed
opened 2026-10-31 00:45:28 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

The installed production static analyzer records exports:["ghost"] for a Webpack factory whose Object.defineProperty is a local no-op method. Executing this synthetic factory leaves its exports object empty. An otherwise equivalent control using the real global Object.defineProperty correctly reports and creates real.

Reproduction

Executed analyzeJavaScriptStaticSource from the installed package's dist/domain/javascript/javascriptStaticAnalysis.js on this exact fixture:

globalThis.webpackChunkFixture.push([[1], {
  17(module, exports, require) {
    const Object = {defineProperty() {}};
    Object.defineProperty(exports, "ghost", {value: 1, enumerable: true});
  }
}]);
  1. Inspect result.bundler_registrations[0].modules[0].exports: the observed value is ["ghost"], with parse_status:"complete".
  2. Execute the synthetic registration in a fresh Node VM context initialized with {webpackChunkFixture:[]}. Retrieve context.webpackChunkFixture[0][1][17], invoke the factory with a fresh shared module.exports/exports object and an unused require stub, then inspect Object.getOwnPropertyNames(exports): the observed result is [].
  3. Control executed in the same probe: remove the local Object declaration and change the property name to real. Both REA and Object.getOwnPropertyNames(exports) return ["real"].

One probe executed both cases. The public CLI/MCP entry point and application-graph projection were not run.

Expected behavior

A method on a lexically local object is not the global property-definition intrinsic. Its call must not establish a statically declared export. The real-global control should continue to recover real. The owning extractor explicitly describes its contract as collecting statically declared CommonJS/bundler export names in collectJavaScriptExports; the local no-op does not declare the reported property.

Impact and scope

The demonstrated valid factory gains a phantom export in its retained module facts. Source inspection shows that the application graph copies this exports list into its javascript-module observation, so consumers can inventory an API that the source never creates. That graph projection was not executed in this probe. Confirmed for the local-object case and real-global control above; other shadowing forms and real-application frequency remain unmeasured. The exact factory source and digest remain available for manual inspection.

REA version

Published rea-agents npm package 6.7.0; source inspection at 6c4d880f2dd9ca7d42593be916f90ec19b8a09d7 shows the same name-only call recognition.

Runtime and platform

Node.js v26.12.1; Linux x64. No provider, device or external service was used.

Target and artifact context

Two synthetic JavaScript Webpack chunk registrations, each containing one factory and a fresh exports object. Only the supplied benign fixtures were executed for the control observations.

Interface

Other

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

Observed published-runtime production-boundary output:

{"name":"shadowed","runtimeExports":[],"parseStatus":"complete","module":{"module_key":"17","exports":["ghost"]}}
{"name":"control","runtimeExports":["real"],"parseStatus":"complete","module":{"module_key":"17","exports":["real"]}}

Source-derived explanation: collectCallExports selects Object.defineProperty by the two text segments and adds the literal second argument without resolving Object's lexical owner. addBundlerModuleNodes copies moduleValue.exports into graph observations. No full suite or build was run, and this finding was not rerun after drafting.

Additional context

Restoration check (not executed): the local no-op fixture must not report ghost as a statically established export, while the real-global control must still report real. No exact runtime evaluation of arbitrary local helpers is required; uncertainty is preferable to asserting the global intrinsic's effect for a known different receiver.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded valid bundle fixture produces a false exported API name despite a complete parse, while the direct factory control shows no such property.
  • Workaround status: available
  • Workaround basis: Inspect the retained factory source before accepting the export inventory; the local no-op and exact source digest remain present, though this does not repair the incorrect export fact.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6553 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior The installed production static analyzer records `exports:["ghost"]` for a Webpack factory whose `Object.defineProperty` is a local no-op method. Executing this synthetic factory leaves its exports object empty. An otherwise equivalent control using the real global `Object.defineProperty` correctly reports and creates `real`. ### Reproduction Executed `analyzeJavaScriptStaticSource` from the installed package's `dist/domain/javascript/javascriptStaticAnalysis.js` on this exact fixture: ```js globalThis.webpackChunkFixture.push([[1], { 17(module, exports, require) { const Object = {defineProperty() {}}; Object.defineProperty(exports, "ghost", {value: 1, enumerable: true}); } }]); ``` 1. Inspect `result.bundler_registrations[0].modules[0].exports`: the observed value is `["ghost"]`, with `parse_status:"complete"`. 2. Execute the synthetic registration in a fresh Node VM context initialized with `{webpackChunkFixture:[]}`. Retrieve `context.webpackChunkFixture[0][1][17]`, invoke the factory with a fresh shared `module.exports`/`exports` object and an unused require stub, then inspect `Object.getOwnPropertyNames(exports)`: the observed result is `[]`. 3. Control executed in the same probe: remove the local `Object` declaration and change the property name to `real`. Both REA and `Object.getOwnPropertyNames(exports)` return `["real"]`. One probe executed both cases. The public CLI/MCP entry point and application-graph projection were not run. ### Expected behavior A method on a lexically local object is not the global property-definition intrinsic. Its call must not establish a statically declared export. The real-global control should continue to recover `real`. The owning extractor explicitly describes its contract as collecting statically declared CommonJS/bundler export names in [`collectJavaScriptExports`](https://github.com/morluto/rea/blob/6c4d880f2dd9ca7d42593be916f90ec19b8a09d7/src/domain/javascript/javascriptAstFingerprint.ts#L35); the local no-op does not declare the reported property. ### Impact and scope The demonstrated valid factory gains a phantom export in its retained module facts. Source inspection shows that the application graph copies this `exports` list into its `javascript-module` observation, so consumers can inventory an API that the source never creates. That graph projection was not executed in this probe. Confirmed for the local-object case and real-global control above; other shadowing forms and real-application frequency remain unmeasured. The exact factory source and digest remain available for manual inspection. ### REA version Published `rea-agents` npm package `6.7.0`; source inspection at `6c4d880f2dd9ca7d42593be916f90ec19b8a09d7` shows the same name-only call recognition. ### Runtime and platform Node.js `v26.12.1`; Linux x64. No provider, device or external service was used. ### Target and artifact context Two synthetic JavaScript Webpack chunk registrations, each containing one factory and a fresh exports object. Only the supplied benign fixtures were executed for the control observations. ### Interface Other ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence Observed published-runtime production-boundary output: ```json {"name":"shadowed","runtimeExports":[],"parseStatus":"complete","module":{"module_key":"17","exports":["ghost"]}} {"name":"control","runtimeExports":["real"],"parseStatus":"complete","module":{"module_key":"17","exports":["real"]}} ``` Source-derived explanation: [`collectCallExports`](https://github.com/morluto/rea/blob/6c4d880f2dd9ca7d42593be916f90ec19b8a09d7/src/domain/javascript/javascriptAstFingerprint.ts#L173) selects `Object.defineProperty` by the two text segments and adds the literal second argument without resolving `Object`'s lexical owner. [`addBundlerModuleNodes`](https://github.com/morluto/rea/blob/6c4d880f2dd9ca7d42593be916f90ec19b8a09d7/src/application/javascript/JavaScriptArtifactGraphBundlers.ts#L135) copies `moduleValue.exports` into graph observations. No full suite or build was run, and this finding was not rerun after drafting. ### Additional context Restoration check (not executed): the local no-op fixture must not report `ghost` as a statically established export, while the real-global control must still report `real`. No exact runtime evaluation of arbitrary local helpers is required; uncertainty is preferable to asserting the global intrinsic's effect for a known different receiver. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded valid bundle fixture produces a false exported API name despite a complete parse, while the direct factory control shows no such property. - Workaround status: available - Workaround basis: Inspect the retained factory source before accepting the export inventory; the local no-op and exact source digest remain present, though this does not repair the incorrect export fact.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked on main at d87e73f2db50be72d33a4b3c7199af35518b441c: The export extractor now checks lexical and ambient Object.defineProperty identity. Regressions exclude ghost for the local no-op case and retain the unshadowed global control. javascriptAstFingerprint.ts javascriptAstFingerprint.ts javascriptStaticAnalysisBundler.ts javascriptFactoryExportRoles.test.ts

I ran the cited regression file on this revision; all passed as part of the focused 28-file, 603-test run. Provider-facing checks here use controlled fixtures; this does not claim a run with the real analysis engine. Closing as completed on main.

<!-- gh-comment-id:6380634660 --> @coygeek commented on GitHub (Oct 24, 2026): Checked on main at `d87e73f2db50be72d33a4b3c7199af35518b441c`: The export extractor now checks lexical and ambient Object.defineProperty identity. Regressions exclude ghost for the local no-op case and retain the unshadowed global control. [javascriptAstFingerprint.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptAstFingerprint.ts#L99) [javascriptAstFingerprint.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptAstFingerprint.ts#L229) [javascriptStaticAnalysisBundler.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptStaticAnalysisBundler.ts#L265) [javascriptFactoryExportRoles.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptFactoryExportRoles.test.ts#L54) I ran the cited regression file on this revision; all passed as part of the focused 28-file, 603-test run. Provider-facing checks here use controlled fixtures; this does not claim a run with the real analysis engine. Closing as completed on main.
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#3944
No description provided.