Skip to content

fix(amplify-velocity-template): consistent handling of reserved property names in reference resolution - #14992

Merged
sharonyajain merged 4 commits into
aws-amplify:devfrom
sarayev:fix/velocity-template-reserved-property-names-v2
Nov 2, 2026
Merged

sharonyajain merged 4 commits into
aws-amplify:devfrom
sarayev:fix/velocity-template-reserved-property-names-v2

Conversation

@sarayev

@sarayev sarayev commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Follow-on to #14964, which made the VTL reference resolver skip a set of reserved property names but left three code paths that still resolved or wrote those names. This makes the handling consistent across all of them.

What changed

  1. getter() case-normalized lookup (src/compile/references.js) — getter() only checked its first base[property] lookup. When that returns undefined, it re-cases the property name (for example getConstructor() -> Constructor -> lowercased constructor) and does a second lookup with no check, so a reserved name still resolved through the re-cased path. Added the same reserved-name check before both lookups.

  2. Write branches in getPropMethod (src/compile/references.js) — set("k", v), put("k", v), and the setX(v) accessor (baseRef[id.slice(3)] = ...) assigned to a computed key without the same check the read paths use. Each computed key is now checked before the write.

  3. setValue() #set path walk (src/compile/set.js) — the #set directive walks ref.path doing baseRef[key] = val per segment with no check (for example #set($x.__proto__.foo = ...)). Added a local copy of the reserved-name list (no cross-module import) and check every path segment before traversal and write.

The generic method fall-through is left as-is; the existing top-level guard already covers a literal reserved name there.

Tests

Adds tests/reserved-property-names.test.js (matching the existing test style): the getter re-cased path, get("constructor") / get("__proto__"), the #set path walk, and the put / set write cases all resolve to empty and leave Object.prototype untouched, while normal getters / #set / put still work as before.

mocha tests: 166 passing, 0 failing (13 new assertions, no regressions).

…rty names in reference resolution

Follow-on to PR aws-amplify#14964. The initial change made the reference resolver skip a set of reserved property names but left three paths that still resolved or wrote them: the getter case-normalized second lookup, the set/put/setX write branches, and the set.js setValue path walk. Apply the same reserved-name check in all three. Adds tests/reserved-property-names.test.js.
@sharonyajain
sharonyajain marked this pull request as ready for review October 23, 2026 12:17
@sharonyajain
sharonyajain requested review from a team as code owners October 23, 2026 12:17
… strengthen write-path tests

Extract the prototype-chain denylist and isBlockedProperty into a single
reserved-properties module imported by both references.js and set.js, so the
two copies can no longer drift and silently reopen the pollution vector for a
name added to only one list.

Make the two write-path regression tests discriminating: put now exercises
"constructor" (an own-key write that fails without the guard) instead of the
spec-ignored string assignment to __proto__, and the setter accessor test uses
the lowercase "setconstructor" slice that the guard actually matches instead of
the capitalized form that never reached it. Verified by mutation: disabling the
guard now fails both tests.

@sharonyajain sharonyajain left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Good to ship: No blocking issues; 2 should-fix notes — the reserved-name denylist is duplicated across set.js/references.js (drift risk that could reopen the vuln) and two write-path regression tests pass even without the patch.
2 should-fix 🟡 (non-blocking)

[code-review-sage]

…emove()

Reserved names were still reachable through paths that bypassed the
shared accessors:

- the root identifier of a reference ($constructor, $__proto__), read
  from the render context and from #foreach/#macro local scopes, which
  are plain objects whose `in` checks match inherited names
- the root identifier of #set, both with and without a property path
- remove(), which read and deleted a computed key unchecked

Route these through the shared denylist, use an own-property check for
the full-text context lookup, and make a rejected setX(...) render ''
like the other rejected writes. The #set path walk now relies on the
accessors instead of a separate pre-check, and the module comment no
longer claims coverage it did not have.
@sharonyajain
sharonyajain requested a review from soberm November 2, 2026 13:00
@sharonyajain
sharonyajain merged commit 0db4384 into aws-amplify:dev Nov 2, 2026
5 checks passed
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.

3 participants