Skip to content

Use a Map for the script registry and guard reserved names in add()#8835

Merged
mvaligursky merged 1 commit into
mainfrom
mv-script-registry-map
Jun 3, 2026
Merged

Use a Map for the script registry and guard reserved names in add()#8835
mvaligursky merged 1 commit into
mainfrom
mv-script-registry-map

Conversation

@mvaligursky
Copy link
Copy Markdown
Contributor

@mvaligursky mvaligursky commented Jun 3, 2026

Summary

Follow-up to #8831 hardening the script registry against name collisions.

Map-backed registry

ScriptRegistry._scripts is now a Map instead of a plain object. With a plain object, a script name that collides with an Object.prototype member breaks the registry:

  • hasOwnProperty / toString etc. shadow the method on the backing object — the next internal _scripts.hasOwnProperty(...) call then invokes a class constructor and throws a TypeError.
  • __proto__ is worse: _scripts['__proto__'] = cls pokes the prototype rather than storing a key, so the script is silently lost.

A Map treats all of these as ordinary keys. All access sites (add, including the swap path and the deferred awaiting-component check, plus remove/get/has) now use .get/.set/.has/.delete. _list is unchanged, so registration order and list() are unaffected. No public API changes (_scripts is private; get/has/list signatures are identical).

Reserved-name guard on the add() path

The reserved-name check was previously only enforced in registerScript/createScript. Since #8831 made app.scripts.add() a documented registration path, it's now guarded there too (logs and returns false). registerScript/createScript still throw on their own paths — unchanged. The reserved-name set is moved to constants.js to avoid a script-create → AppBase → script-registry import cycle.

Tests

  • Registering scripts named hasOwnProperty, toString, constructor, __proto__, valueOf — all stored and retrievable via add/has/get/list without collision.
  • add() rejects a reserved name (destroy) and does not register it.

Full suite passes; types and lint clean.

Store registered scripts in a Map instead of a plain object so that script
names colliding with `Object.prototype` members (e.g. `hasOwnProperty`,
`toString`, `__proto__`) are stored and looked up safely, rather than shadowing
methods on the backing object (which caused a TypeError) or, in the case of
`__proto__`, silently failing to store.

Also enforce the reserved-name check in `ScriptRegistry.add()` so the public
`app.scripts.add()` path is validated (returns false), not only the
`registerScript`/`createScript` paths (which still throw, unchanged). The
reserved-name set is moved to `constants.js` to avoid an import cycle.

Adds tests for collision-prone names and reserved-name rejection via add().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the framework script registry against script-name collisions by switching the backing store from a plain object to a Map, and it extends reserved-name enforcement to the now-documented app.scripts.add() registration path.

Changes:

  • Replace ScriptRegistry._scripts with a Map and migrate all reads/writes to .get() / .set() / .has() / .delete() to safely support keys like __proto__ and hasOwnProperty.
  • Add a reserved-name guard in ScriptRegistry.add() (log + return false) to match the reserved-name protections already present in createScript / registerScript.
  • Centralize reservedScriptNames in src/framework/script/constants.js and add tests covering both prototype-collision names and reserved-name rejection via add().

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
test/framework/script/script-registry.test.mjs Adds regression tests for Object.prototype-colliding script names and reserved-name rejection via app.scripts.add().
src/framework/script/script-registry.js Switches registry storage from object to Map and adds reserved-name guard in add().
src/framework/script/script-create.js Moves reserved-name set to shared constants and reuses it for createScript / registerScript.
src/framework/script/constants.js Introduces shared reservedScriptNames set used across script registration paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mvaligursky mvaligursky merged commit 5013eda into main Jun 3, 2026
9 checks passed
@mvaligursky mvaligursky deleted the mv-script-registry-map branch June 3, 2026 15:46
mvaligursky added a commit that referenced this pull request Jun 4, 2026
…8835)

Store registered scripts in a Map instead of a plain object so that script
names colliding with `Object.prototype` members (e.g. `hasOwnProperty`,
`toString`, `__proto__`) are stored and looked up safely, rather than shadowing
methods on the backing object (which caused a TypeError) or, in the case of
`__proto__`, silently failing to store.

Also enforce the reserved-name check in `ScriptRegistry.add()` so the public
`app.scripts.add()` path is validated (returns false), not only the
`registerScript`/`createScript` paths (which still throw, unchanged). The
reserved-name set is moved to `constants.js` to avoid an import cycle.

Adds tests for collision-prone names and reserved-name rejection via add().

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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