Skip to content

Guard object lookups against inherited prototype properties#19725

Merged
RobinMalfait merged 4 commits intomainfrom
claude/review-tailwind-issue-QgsAc
Feb 25, 2026
Merged

Guard object lookups against inherited prototype properties#19725
RobinMalfait merged 4 commits intomainfrom
claude/review-tailwind-issue-QgsAc

Conversation

@adamwathan
Copy link
Member

When user-controlled candidate values like "constructor" are used as
keys to look up values in plain objects (staticValues, plugin values,
modifiers, config), they can match inherited Object.prototype properties
instead of returning undefined. This caused crashes like "V.map is not
a function" when scanning source files containing strings like
"row-constructor".

Use Object.hasOwn() checks before all user-keyed object lookups in:

  • utilities.ts (staticValues lookup)
  • plugin-api.ts (values, modifiers, and variant values lookups)
  • plugin-functions.ts (get() config traversal function)

Fixes #19721

https://claude.ai/code/session_011CYSGw3DLh2Z8xnuyoaCgC

When user-controlled candidate values like "constructor" are used as
keys to look up values in plain objects (staticValues, plugin values,
modifiers, config), they can match inherited Object.prototype properties
instead of returning undefined. This caused crashes like "V.map is not
a function" when scanning source files containing strings like
"row-constructor".

Use Object.hasOwn() checks before all user-keyed object lookups in:
- utilities.ts (staticValues lookup)
- plugin-api.ts (values, modifiers, and variant values lookups)
- plugin-functions.ts (get() config traversal function)

Fixes #19721

https://claude.ai/code/session_011CYSGw3DLh2Z8xnuyoaCgC
@adamwathan adamwathan requested a review from a team as a code owner February 25, 2026 14:46
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c76cb8 and c2b9cff.

📒 Files selected for processing (1)
  • CHANGELOG.md

Walkthrough

Replaces fragile property lookups with explicit own-property checks (Object.hasOwn) and adds null/undefined/non-object guards during path traversal to avoid indexing into non-object values. Converts certain static value maps to null-prototype objects before lookup to prevent inherited properties from Object.prototype. Adds tests for plugin and utility APIs using names matching Object.prototype keys (e.g. constructor, hasOwnProperty, toString, valueOf, __proto__) that assert no CSS output or errors. No public API signatures were changed.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Object.hasOwn guards to prevent inherited prototype property lookups during object operations.
Description check ✅ Passed The description is directly related to the changeset, explaining the problem, the solution approach, and linking to the specific issue being fixed (#19721).
Linked Issues check ✅ Passed The PR fully addresses the requirements from issue #19721: it adds Object.hasOwn checks in utilities.ts, plugin-api.ts, and plugin-functions.ts to prevent user-controlled keys from matching inherited prototype properties, fixing the crash when scanning files with strings like 'row-constructor'.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objective: adding Object.hasOwn guards in the three files mentioned, with supporting test cases, and a changelog entry. No unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/tailwindcss/src/utilities.test.ts (1)

1650-1655: Consider adding __proto__ (and related prototype members) to the test cases.

The four cases added here are the right set for the reported issue, and the structure is correct. However, __proto__ is a special accessor on Object.prototype that is equally dangerous as a keyed lookup — it's absent from the coverage. The Object.hasOwn() guard introduced in utilities.ts correctly returns false for it, but having an explicit test would prevent regressions if the guard were ever relaxed or refactored.

🧪 Proposed additional test candidates
       'row-constructor',
       'row-hasOwnProperty',
       'row-toString',
       'row-valueOf',
+      'row-__proto__',
+      'row-isPrototypeOf',
+      'row-propertyIsEnumerable',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/utilities.test.ts` around lines 1650 - 1655, Add an
explicit test candidate for "__proto__" alongside the existing entries
('row-constructor', 'row-hasOwnProperty', 'row-toString', 'row-valueOf') in the
candidates list in utilities.test.ts so the Object.hasOwn() guard in
utilities.ts is exercised for the special accessor; update the test array to
include "__proto__" (and any related prototype accessor names you want covered)
to ensure the guard continues to return false and prevent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/tailwindcss/src/compat/plugin-api.ts`:
- Around line 426-427: The call to values.__BARE_VALUE__ is not type-safe
because Object.hasOwn(values, '__BARE_VALUE__') does not narrow to a callable;
guard the invocation by checking the property is a function before calling it
(e.g. if (typeof values.__BARE_VALUE__ === 'function') { value =
values.__BARE_VALUE__(candidate.value) ?? null }) so TypeScript recognizes the
callable; reference symbols: __BARE_VALUE__, values, candidate.value, value,
Object.hasOwn.

---

Nitpick comments:
In `@packages/tailwindcss/src/utilities.test.ts`:
- Around line 1650-1655: Add an explicit test candidate for "__proto__"
alongside the existing entries ('row-constructor', 'row-hasOwnProperty',
'row-toString', 'row-valueOf') in the candidates list in utilities.test.ts so
the Object.hasOwn() guard in utilities.ts is exercised for the special accessor;
update the test array to include "__proto__" (and any related prototype accessor
names you want covered) to ensure the guard continues to return false and
prevent regressions.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 097f982 and ca04510.

📒 Files selected for processing (5)
  • packages/tailwindcss/src/compat/plugin-api.test.ts
  • packages/tailwindcss/src/compat/plugin-api.ts
  • packages/tailwindcss/src/compat/plugin-functions.ts
  • packages/tailwindcss/src/utilities.test.ts
  • packages/tailwindcss/src/utilities.ts

Otherwise the `Object.hasOwn` check runs for every utility where static
values have been defined and uses bare values (e.g. `row-constructor`)
but in most cases you won't hit prototype specific keys.

This takes it out of the hot path
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/tailwindcss/src/compat/plugin-api.ts (1)

418-430: values.DEFAULT at line 415 is the one remaining direct access without hasOwn.

All user-controlled key paths now use Object.hasOwn, but the !candidate.value branch still does values.DEFAULT ?? null via plain property access. While 'DEFAULT' is not an Object.prototype key and the lookup is not driven by user input, adding a hasOwn guard would make this block fully consistent with the rest of the change.

♻️ Optional consistency fix
-            if (!candidate.value) {
-              value = values.DEFAULT ?? null
-            } else if (candidate.value.kind === 'arbitrary') {
+            if (!candidate.value) {
+              value = Object.hasOwn(values, 'DEFAULT') ? values.DEFAULT : null
+            } else if (candidate.value.kind === 'arbitrary') {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tailwindcss/src/compat/plugin-api.ts` around lines 418 - 430, The
branch that handles the !candidate.value case currently reads values.DEFAULT via
direct property access; change it to first check Object.hasOwn(values,
'DEFAULT') and only then set value = values.DEFAULT ?? null (otherwise fall back
to null), keeping any existing ignoreModifier logic intact; update the code
around the candidate.value / values lookup (the same area using Object.hasOwn
for fraction and value keys) to use Object.hasOwn for 'DEFAULT' for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/tailwindcss/src/compat/plugin-api.ts`:
- Around line 418-430: The branch that handles the !candidate.value case
currently reads values.DEFAULT via direct property access; change it to first
check Object.hasOwn(values, 'DEFAULT') and only then set value = values.DEFAULT
?? null (otherwise fall back to null), keeping any existing ignoreModifier logic
intact; update the code around the candidate.value / values lookup (the same
area using Object.hasOwn for fraction and value keys) to use Object.hasOwn for
'DEFAULT' for consistency.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca04510 and 3c76cb8.

📒 Files selected for processing (2)
  • packages/tailwindcss/src/compat/plugin-api.ts
  • packages/tailwindcss/src/utilities.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/tailwindcss/src/utilities.ts

Copy link
Member

@RobinMalfait RobinMalfait left a comment

Choose a reason for hiding this comment

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

Made a few small changes, but looks good otherwise.

@RobinMalfait RobinMalfait enabled auto-merge (squash) February 25, 2026 15:13
@RobinMalfait RobinMalfait merged commit 9ded4a2 into main Feb 25, 2026
9 checks passed
@RobinMalfait RobinMalfait deleted the claude/review-tailwind-issue-QgsAc branch February 25, 2026 15:16
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.

Tailwind v4 crashes when scanned source contains plain string row-constructor (V.map is not a function)

3 participants