Skip to content

fix: avoid prototype-colliding names in execution values#4653

Merged
yaacovCR merged 1 commit into
graphql:17.x.xfrom
abishekgiri:fix-execution-values-own-property-returns
Apr 18, 2026
Merged

fix: avoid prototype-colliding names in execution values#4653
yaacovCR merged 1 commit into
graphql:17.x.xfrom
abishekgiri:fix-execution-values-own-property-returns

Conversation

@abishekgiri

@abishekgiri abishekgiri commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes an issue where prototype-colliding property names (e.g., __proto__, constructor) could interfere with execution value handling.

Changes

  • Ensured own-property checks are used when accessing execution values
  • Prevent potential prototype chain collisions

Motivation

JavaScript objects can inherit properties from the prototype chain. Without proper checks, this can lead to unexpected behavior when handling execution values.

Testing

  • Verified manually with edge-case inputs involving prototype properties
  • Existing tests pass locally

@vercel

vercel Bot commented Apr 2, 2026

Copy link
Copy Markdown

@abishekgiri is attempting to deploy a commit to the The GraphQL Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@abishekgiri

Copy link
Copy Markdown
Contributor Author

Status update

  • Fixed execution/values.ts so getArgumentValues() and getVariableValues() keep their null-prototype maps instead of reintroducing Object.prototype via object spread.
  • Added regression coverage for omitted prototype-colliding names like toString in both resolver args and coerced variable values.
  • Results: targeted mocha, eslint, and prettier checks passed for this change.
  • tsc in this environment still reports existing external errors from ../node_modules/@types/react-dom, which are outside this PR.

@yaacovCR

Copy link
Copy Markdown
Contributor

This would be a breaking change, and would have to land in v17, but we are considering doing so in:

See: #4634 (comment)

For background, see:

Note that #1056 does not fully address the problem raised by graphql/express-graphql#177. In particular, while @leebyron gave a prototype to the args map itself, an input object arg still itself has no prototype -- just as ExecutionResult.data and its descendants have no prototype.

@yaacovCR

yaacovCR commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Actually @abishekgiri -- on my box the new tests pass even without the suggested code changes. Is there an actual bug in this case

(we were considering doing so in the linked PR above just because the re-addition of the prototype didn't seem strictly necessary)

@abishekgiri

Copy link
Copy Markdown
Contributor Author

@yaacovCR I dug through this end to end and verified that there is an actual bug here on plain \16.x.x. I checked the new variables-test.tscases against an unmodified16.x.xcheckout, and two of them fail without theexecution/values.tschange: omitted resolver argtoStringresolves to the inherited function instead of behaving as missing, andgetVariableValues(...).coerced.toStringalso resolves to the inherited function instead ofundefined`.

So I do think the bug is real. The harder part is branch compatibility: the current fix changes getArgumentValues() / getVariableValues() from returning plain objects with a prototype to null-prototype maps, and that seems to be the long-standing/documented behavior on 16.x.x. So my read is: real bug, but the current fix is probably 17.x.x material rather than a safe 16.x.x backport.`

@abishekgiri abishekgiri mentioned this pull request Apr 13, 2026
17 tasks
@yaacovCR

Copy link
Copy Markdown
Contributor

Ah, my box duplicates it now as well. I wonder if there would be a 16.x.x compatible fix possible, but agree that the direction of this fix would have to wait until 17.x.x.

@yaacovCR

Copy link
Copy Markdown
Contributor

Ah, my box duplicates it now as well. I wonder if there would be a 16.x.x compatible fix possible, but agree that the direction of this fix would have to wait until 17.x.x.

Hmmm. actually, yet again looked closer, what the tests are testing is just the case @leebyron warns about in the changed comment, i.e. these objects have a prototype, and therefore one must be careful to not draw items from the prototype through an Object.hasOwn check or older equivalent. So there can't be a fix per se in 16.x.x -- although we can consider removing the prototype in 17.x.x.

@abishekgiri

Copy link
Copy Markdown
Contributor Author

@yaacovCR Makes sense to me. Looking closer, this does seem to match the long-standing 16.x.xcontract that these returned objects have a prototype and consumers need an own-property check when reading from them. In that case, I agree there is not really a16.x.xfix to land here without changing behavior. This seems better tracked asv17 work alongside #4634.

Comment thread src/execution/values.ts Outdated
@abishekgiri

Copy link
Copy Markdown
Contributor Author

Following up here as well: after tracing the history in #1056 and #4631, I agree this does not look like a safe 16.x.x fix. The spread was kept intentionally so getArgumentValues() / getVariableValues() continue returning plain objects to user code, even after the internal accumulators moved to Object.create(null). That means the current change would alter the long-standing public contract on 16.x.x, so this seems better handled as v17 work alongside #4634 rather than something to merge from this PR.

@abishekgiri abishekgiri requested a review from afurm April 15, 2026 23:05
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@yaacovCR yaacovCR force-pushed the fix-execution-values-own-property-returns branch from 869cb7c to e8ebbee Compare April 16, 2026 12:16
@yaacovCR yaacovCR changed the base branch from 16.x.x to 17.x.x April 16, 2026 12:17
@yaacovCR yaacovCR closed this Apr 16, 2026
@yaacovCR yaacovCR reopened this Apr 16, 2026
@yaacovCR

Copy link
Copy Markdown
Contributor

@abishekgiri I rebased this on 17.x.x and changed the PR base (after merging #4634). This PR now contains your tests and comment changes (as well as a small type change that could have also been in #4634 along with the similar type changes there).

Let me know if this looks good and will merge to 17.x.x.

@abishekgiri

Copy link
Copy Markdown
Contributor Author

@yaacovCR This looks good to me on 17.x.x. Rebasing it after #4634 makes the direction much clearer, and the current scope also looks right: the regression tests, the updated comments, and the small type change all seem consistent with the null-prototype behavior there. I don’t see anything else I’d want to change on this PR from my side, so I’d be happy with merging it into 17.x.x.

@yaacovCR yaacovCR added the PR: polish 💅 PR doesn't change public API or any observed behaviour label Apr 18, 2026
@yaacovCR yaacovCR changed the title Fix prototype-colliding names in execution values fix: avoid prototype-colliding names in execution values Apr 18, 2026
@yaacovCR yaacovCR merged commit afbe436 into graphql:17.x.x Apr 18, 2026
21 of 22 checks passed
@abishekgiri abishekgiri deleted the fix-execution-values-own-property-returns branch April 19, 2026 00:28
yaacovCR added a commit that referenced this pull request May 7, 2026
## v17.0.0-beta.0 (2026-05-07)

#### Breaking Change 💥
* [#4634](#4634) Use Object.create(null) over {} to avoid prototype issues ([@benjie](https://github.com/benjie))
* [#4700](#4700) Demote createSourceEventStream to helper taking ValidatedExecutionArgs ([@yaacovCR](https://github.com/yaacovCR))
* [#4703](#4703) rename executeQueryOrMutationOrSubscriptionEvent to executeRootSelectionSet ([@yaacovCR](https://github.com/yaacovCR))
* [#4708](#4708) fix(variables): treat undefined as absent with respect to variables ([@yaacovCR](https://github.com/yaacovCR))
* [#4710](#4710) fix: ignore undefined-valued unknown fields in input objects ([@yaacovCR](https://github.com/yaacovCR))

#### New Feature 🚀
* [#4658](#4658) feat(execution): expose asyncWorkFinished execution hook ([@yaacovCR](https://github.com/yaacovCR))
* [#4674](#4674) feat(execution): expose partial result on abort errors ([@yaacovCR](https://github.com/yaacovCR))
* [#4701](#4701) export validateExecutionArgs helper ([@yaacovCR](https://github.com/yaacovCR))
* [#4702](#4702) feat: add ValidatedSubscriptionArgs and validateSubscriptionArgs ([@yaacovCR](https://github.com/yaacovCR))

#### Bug Fix 🐞
* [#4637](#4637) refactor(queue): replace stopped promise with onStop handlers ([@yaacovCR](https://github.com/yaacovCR))
* [#4641](#4641) refactor(incremental): close stream iterator only on abnormal stop ([@yaacovCR](https://github.com/yaacovCR))
* [#4642](#4642) fix(incremental): await async incremental cleanup ([@yaacovCR](https://github.com/yaacovCR))
* [#4644](#4644) fix(withConcurrentAbruptClose): do not close unnecessarily ([@yaacovCR](https://github.com/yaacovCR))
* [#4645](#4645) fix(cancellablePromise): handle rejection when cancelling already-aborted promise ([@yaacovCR](https://github.com/yaacovCR))
* [#4643](#4643) fix(execute): handle list promise rejections ([@yaacovCR](https://github.com/yaacovCR))
* [#4646](#4646) fix(execute): handle defaultTypeResolver promise rejections ([@yaacovCR](https://github.com/yaacovCR))
* [#4648](#4648) refactor(executor): separate finish/abort orchestration ([@yaacovCR](https://github.com/yaacovCR))
* [#4655](#4655) fix(execution): finish executors before publishing responses ([@yaacovCR](https://github.com/yaacovCR))
* [#4661](#4661) fix: bubbling sync errors need not become async ([@yaacovCR](https://github.com/yaacovCR))
* [#4663](#4663) fix(executor): let aborted async paths reach finish ([@yaacovCR](https://github.com/yaacovCR))
* [#4657](#4657) refactor(execution): track pending work ([@yaacovCR](https://github.com/yaacovCR))
* [#4664](#4664) fix(perf): hoist error creation ([@yaacovCR](https://github.com/yaacovCR))
* [#4665](#4665) execution: reduce parent executor retention in incremental callbacks ([@yaacovCR](https://github.com/yaacovCR))
* [#4671](#4671) fix(valueFromAST): forward port #4652 ([@yaacovCR](https://github.com/yaacovCR))
* [#4672](#4672) fix(execution): convert all promise-like results to promises ([@yaacovCR](https://github.com/yaacovCR))
* [#4518](#4518) Fix TypeInfo.getInputType() for custom scalar list literals. ([@yuchenshi](https://github.com/yuchenshi))
* [#4692](#4692) fix incremental label null validation ([@jbellenger](https://github.com/jbellenger))
* [#4711](#4711) fix(coerceInputLiteral): null variable input object fields should override defaults ([@yaacovCR](https://github.com/yaacovCR))
* [#4712](#4712) fix: name fragment variables as such in execution errors ([@yaacovCR](https://github.com/yaacovCR))
* [#4714](#4714) fix: enhance runtime invalid default value error messages ([@yaacovCR](https://github.com/yaacovCR))
* [#4715](#4715) unify OneOf non-null and count error messages ([@yaacovCR](https://github.com/yaacovCR))
* [#4716](#4716) fix(OneOf): fail coercion when two fields are provided "pre-coercion-only" ([@yaacovCR](https://github.com/yaacovCR))
* [#4717](#4717) fix(valueFromAST): reject unknown input object fields ([@yaacovCR](https://github.com/yaacovCR))
* [#4718](#4718) fix(OneOf): count only known fields for one-of validation ([@yaacovCR](https://github.com/yaacovCR))
* [#4719](#4719) fix(OneOf): fail coercion for OneOf is the single field is invalidly provided by a default ([@yaacovCR](https://github.com/yaacovCR))

#### Polish 💅
<details>
<summary> 19 PRs were merged </summary>

* [#4635](#4635) polish: remove a few superfluous awaits ([@yaacovCR](https://github.com/yaacovCR))
* [#4636](#4636) allow forwarding of cancellation reasons within computations/queues ([@yaacovCR](https://github.com/yaacovCR))
* [#4638](#4638) test(queue): cover async onStop cleanup ([@yaacovCR](https://github.com/yaacovCR))
* [#4639](#4639) refactor(computation): rename cancel to abort ([@yaacovCR](https://github.com/yaacovCR))
* [#4640](#4640) refactor(workqueue): propagate async computation abort cleanup ([@yaacovCR](https://github.com/yaacovCR))
* [#4647](#4647) refactor(executor): rename finished state to aborted ([@yaacovCR](https://github.com/yaacovCR))
* [#4649](#4649) refactor(cancellablePromise): extract withCancellation ([@yaacovCR](https://github.com/yaacovCR))
* [#4650](#4650) refactor(executor): replace internal abort signal with lightweight version ([@yaacovCR](https://github.com/yaacovCR))
* [#4656](#4656) refactor(execution): introduce shared execution context ([@yaacovCR](https://github.com/yaacovCR))
* [#4653](#4653) fix: avoid prototype-colliding names in execution values ([@abishekgiri](https://github.com/abishekgiri))
* [#4673](#4673) refactor(execution): separate response construction from finish checks ([@yaacovCR](https://github.com/yaacovCR))
* [#4675](#4675) polish(execution): remove obsolete abort async checks ([@yaacovCR](https://github.com/yaacovCR))
* [#4686](#4686) refactor(benchmark): extract shared mean stats helper ([@yaacovCR](https://github.com/yaacovCR))
* [#4687](#4687) polish(benchmark): handle theoretical stats errors ([@yaacovCR](https://github.com/yaacovCR))
* [#4693](#4693) polish: add expectToThrow test helper ([@yaacovCR](https://github.com/yaacovCR))
* [#4694](#4694) polish: add expectPromise.toReject helper ([@yaacovCR](https://github.com/yaacovCR))
* [#4695](#4695) polish: clean up stream tests ([@yaacovCR](https://github.com/yaacovCR))
* [#4696](#4696) polish: introduce spy test helper ([@yaacovCR](https://github.com/yaacovCR))
* [#4704](#4704) refactor: add executionMode/serially argument to executeRootSelectionSet ([@yaacovCR](https://github.com/yaacovCR))
</details>

#### Internal 🏠
<details>
<summary> 17 PRs were merged </summary>

* [#4676](#4676) chore(deps): upgrade to ts v6 ([@yaacovCR](https://github.com/yaacovCR))
* [#4677](#4677) internal: refactor benchmark runner structure ([@yaacovCR](https://github.com/yaacovCR))
* [#4678](#4678) internal: run benchmarks through worker files ([@yaacovCR](https://github.com/yaacovCR))
* [#4679](#4679) internal: read benchmark names through dedicated worker ([@yaacovCR](https://github.com/yaacovCR))
* [#4680](#4680) internal: separate benchmark timing and memory sampling ([@yaacovCR](https://github.com/yaacovCR))
* [#4681](#4681) fix(benchmark): use node flags only for memory sampling ([@yaacovCR](https://github.com/yaacovCR))
* [#4682](#4682) internal: use mitata for benchmark timing ([@yaacovCR](https://github.com/yaacovCR))
* [#4683](#4683) refactor(benchmark): integrate run + sampling files ([@yaacovCR](https://github.com/yaacovCR))
* [#4684](#4684) benchmark: perform timing tests in rounds to reduce variance ([@yaacovCR](https://github.com/yaacovCR))
* [#4685](#4685) benchmark: stop timing after stable pairwise comparisons ([@yaacovCR](https://github.com/yaacovCR))
* [#4688](#4688) benchmark: cache revision archives ([@yaacovCR](https://github.com/yaacovCR))
* [#4689](#4689) benchmark: report paired benchmark comparisons ([@yaacovCR](https://github.com/yaacovCR))
* [#4690](#4690) benchmark: add async object field benchmark ([@yaacovCR](https://github.com/yaacovCR))
* [#4697](#4697) internal: add runtime option to benchmark ([@yaacovCR](https://github.com/yaacovCR))
* [#4713](#4713) benchmark: add benchmark for schema validation ([@yaacovCR](https://github.com/yaacovCR))
* [#4722](#4722) internal: npm release:prepare should set publishTag ([@yaacovCR](https://github.com/yaacovCR))
* [#4723](#4723) fix(changelog): ignore open associated PRs ([@yaacovCR](https://github.com/yaacovCR))
</details>

#### Committers: 5
* Abishek Kumar Giri([@abishekgiri](https://github.com/abishekgiri))
* Benjie([@benjie](https://github.com/benjie))
* James Bellenger([@jbellenger](https://github.com/jbellenger))
* Yaacov Rydzinski ([@yaacovCR](https://github.com/yaacovCR))
* Yuchen Shi([@yuchenshi](https://github.com/yuchenshi))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: polish 💅 PR doesn't change public API or any observed behaviour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants