[GH-ISSUE #6507] [Bug] JavaScript analysis emits resolved Promise effects after visible global replacement #3903

Closed
opened 2026-10-31 00:45:04 +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/6507

Before submitting

  • I searched open issues for related reports.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

analyze-javascript-application emits a promise node for Promise.resolve(1) with source_resolution: "complete" and a creates-promise relationship with resolution: "resolved", even though the preceding statement replaces globalThis.Promise with an ordinary object whose resolve method returns the number 17. An independent execution of this inert fixture returned {"value":17,"type":"number"} with exit status 0.

Reproduction

The exact fixture below was analyzed once during initial investigation, and its runtime result was checked once in a separate Node process. Place it in app/main.js:

globalThis.Promise = { resolve() { return 17; } };
export const result = Promise.resolve(1);

Invoke the source CLI with the pinned Node runtime. REA_CHECKOUT denotes the source checkout; app is the absolute fixture directory. The original run used this argument sequence with disposable paths:

node "$REA_CHECKOUT/scripts/rea.mjs" analyze-javascript-application "$PWD/app" --format json

Inspect normalized_result.semantic_graph.nodes and .relations. The CLI exits 0. The separate runtime check imports this fixture into its own process and prints JSON.stringify({value: m.result, type: typeof m.result}) for its imported module m.

A second, independently analyzed fixture that replaces only the method also produced a complete Promise node:

Promise.resolve = value => value;
export const result = Promise.resolve(1);

An unmodified export const result = Promise.resolve(1); control retained a resolved intrinsic creation relationship, as expected.

Expected behavior

A visible replacement of the global Promise object or its selected factory must prevent an ordinary user method from receiving a resolved intrinsic Promise-creation fact. Retaining an unresolved/candidate operation is sufficient; a static analyzer need not execute the method. The unmodified intrinsic control should remain supported. The documented extractor covers explicit Promise effects and conservative relationships (workflow contract); the repository authority also requires preserving uncertainty rather than treating unknown applicability as aligned (AGENTS.md).

Impact and scope

The observed result invents Promise ownership for a number-returning call. Both a whole-global replacement and a direct factory replacement reproduced through the public CLI. Frequency in other applications and effects on downstream queries were not measured. This is a non-security analysis correctness defect; the fixture mutates only its separate disposable Node process during the optional runtime check.

REA version

6.7.0 source build at 4c547008c53ee9f0ff2368e7d874a2c3347aa235. This reports the stated revision, which is ahead of the published 6.7.0 tag; it does not assert reproduction on the release tag.

Runtime and platform

Node.js v24.18.0; Linux x86_64. No browser or native analysis provider was involved.

Target and artifact context

A disposable directory containing the JavaScript fixture shown above. Analysis used an isolated HOME with REA/client override environment variables removed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed excerpts, with graph identifiers omitted:

{"kind":"promise","label":"Promise.resolve","properties":{"method":"resolve","operation_kind":"static","ownership":"assigned","source_resolution":"complete"}}
{"relation":"creates-promise","resolution":"resolved"}

Separate Node result for the first fixture:

{"value":17,"type":"number"}
  • Evidence provenance: observed public CLI output; the explanation below is source-derived.
  • Local verification: reproduced.
  • Reproduction completeness: complete.
  • Expected source: the workflow contract and conservative-identity rules linked above, together with the exact fixture's visible replacement.
  • Failure source: the emitted complete Promise node and resolved creation relationship shown above.

Typed outcome or recovery expected

Omit the resolved intrinsic effect or retain a candidate with explicit Promise-identity uncertainty. Preserve ordinary lexical/module facts and the resolved unmodified control.

Analysis or suspected cause

Source-derived: isUnshadowedGlobalPromise checks lexical binding lookup and blocked resolution only. promiseCandidateDetails consequently accepts the factory name after a visible ambient replacement. promiseSources returns complete for static factories, and the graph projection promotes the creation edge to resolved. semanticResolutionBlocked also consults lexical lookup only; it does not supply the ambient-write guard missing here.

Additional context

Targeted open-issue searches included Promise in:title, "Promise.resolve", "globalThis.Promise", "Promise" "ambient", and "Promise" "unshadowed". No matching open report was found. Related native-loader shadowing reports #3884/#3885 concern a different extractor and trigger. Closed issues were not searched or attested. No fix was made, and no additional runtime re-verification was run after drafting.

Restoration criterion: the replacement fixtures must no longer contain a resolved intrinsic creation relationship for their number-returning calls, while the unmodified Promise control must retain its resolved relationship.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded valid JavaScript input produces an incorrect resolved Promise relationship in the public static-analysis result. Automated ownership/provenance consumers can follow that false fact; no target mutation or broad service failure was observed.
  • Workaround status: unverified
  • Workaround basis: Manual inspection of the retained source can expose the mismatch, but no CLI setting or end-to-end consumer workaround was verified to suppress just these incorrect Promise relationships.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6507 ## Before submitting - [x] I searched open issues for related reports. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior `analyze-javascript-application` emits a `promise` node for `Promise.resolve(1)` with `source_resolution: "complete"` and a `creates-promise` relationship with `resolution: "resolved"`, even though the preceding statement replaces `globalThis.Promise` with an ordinary object whose `resolve` method returns the number 17. An independent execution of this inert fixture returned `{"value":17,"type":"number"}` with exit status 0. ## Reproduction The exact fixture below was analyzed once during initial investigation, and its runtime result was checked once in a separate Node process. Place it in `app/main.js`: ```js globalThis.Promise = { resolve() { return 17; } }; export const result = Promise.resolve(1); ``` Invoke the source CLI with the pinned Node runtime. `REA_CHECKOUT` denotes the source checkout; `app` is the absolute fixture directory. The original run used this argument sequence with disposable paths: ```sh node "$REA_CHECKOUT/scripts/rea.mjs" analyze-javascript-application "$PWD/app" --format json ``` Inspect `normalized_result.semantic_graph.nodes` and `.relations`. The CLI exits 0. The separate runtime check imports this fixture into its own process and prints `JSON.stringify({value: m.result, type: typeof m.result})` for its imported module `m`. A second, independently analyzed fixture that replaces only the method also produced a complete Promise node: ```js Promise.resolve = value => value; export const result = Promise.resolve(1); ``` An unmodified `export const result = Promise.resolve(1);` control retained a resolved intrinsic creation relationship, as expected. ## Expected behavior A visible replacement of the global Promise object or its selected factory must prevent an ordinary user method from receiving a resolved intrinsic Promise-creation fact. Retaining an unresolved/candidate operation is sufficient; a static analyzer need not execute the method. The unmodified intrinsic control should remain supported. The documented extractor covers explicit Promise effects and conservative relationships ([workflow contract](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/docs/javascript-application-workflows.md#L177-L190)); the repository authority also requires preserving uncertainty rather than treating unknown applicability as aligned ([AGENTS.md](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/AGENTS.md#L20-L29)). ## Impact and scope The observed result invents Promise ownership for a number-returning call. Both a whole-global replacement and a direct factory replacement reproduced through the public CLI. Frequency in other applications and effects on downstream queries were not measured. This is a non-security analysis correctness defect; the fixture mutates only its separate disposable Node process during the optional runtime check. ## REA version 6.7.0 source build at `4c547008c53ee9f0ff2368e7d874a2c3347aa235`. This reports the stated revision, which is ahead of the published 6.7.0 tag; it does not assert reproduction on the release tag. ## Runtime and platform Node.js v24.18.0; Linux x86_64. No browser or native analysis provider was involved. ## Target and artifact context A disposable directory containing the JavaScript fixture shown above. Analysis used an isolated HOME with REA/client override environment variables removed. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence Observed excerpts, with graph identifiers omitted: ```json {"kind":"promise","label":"Promise.resolve","properties":{"method":"resolve","operation_kind":"static","ownership":"assigned","source_resolution":"complete"}} {"relation":"creates-promise","resolution":"resolved"} ``` Separate Node result for the first fixture: ```json {"value":17,"type":"number"} ``` - Evidence provenance: observed public CLI output; the explanation below is source-derived. - Local verification: reproduced. - Reproduction completeness: complete. - Expected source: the workflow contract and conservative-identity rules linked above, together with the exact fixture's visible replacement. - Failure source: the emitted complete Promise node and resolved creation relationship shown above. ## Typed outcome or recovery expected Omit the resolved intrinsic effect or retain a candidate with explicit Promise-identity uncertainty. Preserve ordinary lexical/module facts and the resolved unmodified control. ## Analysis or suspected cause Source-derived: [`isUnshadowedGlobalPromise`](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/javascript/javascriptSemanticPromises.ts#L167-L172) checks lexical binding lookup and blocked resolution only. [`promiseCandidateDetails`](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/javascript/javascriptSemanticPromises.ts#L150-L157) consequently accepts the factory name after a visible ambient replacement. [`promiseSources`](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/javascript/javascriptSemanticPromises.ts#L385-L412) returns complete for static factories, and the [graph projection](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/application/javascript/JavaScriptSemanticGraphFlowProjection.ts#L108-L122) promotes the creation edge to resolved. `semanticResolutionBlocked` [also consults lexical lookup only](https://github.com/morluto/rea/blob/4c547008c53ee9f0ff2368e7d874a2c3347aa235/src/domain/javascript/javascriptSemanticState.ts#L267-L274); it does not supply the ambient-write guard missing here. ## Additional context Targeted open-issue searches included `Promise in:title`, `"Promise.resolve"`, `"globalThis.Promise"`, `"Promise" "ambient"`, and `"Promise" "unshadowed"`. No matching open report was found. Related native-loader shadowing reports #3884/#3885 concern a different extractor and trigger. Closed issues were not searched or attested. No fix was made, and no additional runtime re-verification was run after drafting. Restoration criterion: the replacement fixtures must no longer contain a resolved intrinsic creation relationship for their number-returning calls, while the unmodified Promise control must retain its resolved relationship. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded valid JavaScript input produces an incorrect resolved Promise relationship in the public static-analysis result. Automated ownership/provenance consumers can follow that false fact; no target mutation or broad service failure was observed. - Workaround status: unverified - Workaround basis: Manual inspection of the retained source can expose the mismatch, but no CLI setting or end-to-end consumer workaround was verified to suppress just these incorrect Promise relationships.
Author
Owner

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

Checked on main at d87e73f2db50be72d33a4b3c7199af35518b441c: Promise extraction now requires unmodified global identity of the selected method. The regression covers both reported replacement forms and the unchanged complete control. javascriptSemanticPromises.ts javascriptSemanticAmbientWrites.ts JavaScriptSemanticGraphFlowProjection.ts javascriptSemanticAnalysis.initialization_boundaries.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:6380635458 --> @coygeek commented on GitHub (Oct 24, 2026): Checked on main at `d87e73f2db50be72d33a4b3c7199af35518b441c`: Promise extraction now requires unmodified global identity of the selected method. The regression covers both reported replacement forms and the unchanged complete control. [javascriptSemanticPromises.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticPromises.ts#L150) [javascriptSemanticAmbientWrites.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticAmbientWrites.ts#L775) [JavaScriptSemanticGraphFlowProjection.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/javascript/JavaScriptSemanticGraphFlowProjection.ts#L68) [javascriptSemanticAnalysis.initialization_boundaries.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticAnalysis.initialization_boundaries.test.ts#L181) 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#3903
No description provided.