fix(codegen-ui-react): validate schema-supplied strings before emitting identifiers - #1204
Merged
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1204 +/- ##
==========================================
+ Coverage 93.92% 93.96% +0.03%
==========================================
Files 151 151
Lines 6142 6180 +38
Branches 1842 1847 +5
==========================================
+ Hits 5769 5807 +38
Misses 355 355
Partials 18 18
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
…ng identifiers
factory.createIdentifier(), createPropertyAccessChain() and
createPropertyAccessExpression() emit their string argument verbatim as source.
Where a component, view, form or theme schema string reached one of those
positions without validation, it was written into the generated .tsx as
executable code rather than as data, allowing code injection into generated
components.
Earlier fixes addressed individual call sites. This change covers the
schema-derived emit sites across the package. All schema-derived values are now
validated against an allowlist before emission; compile-time constants and
internally derived names are unchanged.
Sites now guarded:
Component properties and conditions
- buildConditionalExpression: condition.property and condition.field
- buildOpeningElementProperties: property key -> JSX attribute name
- buildOpeningElementEvents: unmapped event name -> JSX attribute name
- bindingProperties / collectionProperties keys -> generated declarations
Collections and data bindings
- CollectionRenderer.findItemsVariableName: a collectionProperties key flowing
into the collection's items={...} variable
- buildUseEffectStatements: stateReference dataDependencies, which carry
bindingProperties.property and condition.property through to the useEffect
guard and its dependency array
- buildSortFunction: sort.field
- predicateToObjectLiteralExpression (component and view): predicate field,
operator and keys
Views, tables and forms
- ReactExpanderRenderer: componentSlot binding property/field, binding property
keys, componentSlot.componentName, collection title binding field
- createFieldAccessExpression and table body cell: column field and header
- objectToExpression: view fieldFormatting keys
- buildDefaultModelDisplayValue: a relationship field's default displayValue
mapping property and field
- buildActionArgument / assignFieldProperties: action parameter keys
Validation is consolidated in lib/utils/identifiers.ts rather than duplicated per
call site. escapePropertyValue() now delegates to escapeIdentifierPath() and
buildThemePropertyName() to buildIdentifierOrStringLiteral(), removing three
copies of the same allowlist regex. All Object.entries access to
bindingProperties / collectionProperties routes through safeIdentifierEntries(),
including sites that discard the key, so a new caller cannot bypass the guard.
One additional validator was necessary. JSX attribute names follow the
JSXIdentifier grammar, which permits '-' -- legitimate schemas use data-testid
and aria-label -- and forbids the dot-paths that property bindings allow.
Validating attribute names with the plain-identifier rule would reject valid
input, so JSX_ATTRIBUTE_NAME_RE covers that position specifically.
Each position fails closed in the way appropriate to its syntax, and no position
emits an empty identifier: an empty identifier cannot execute, but it is
unparseable and surfaces later as an opaque formatter error. Positions accepting
a literal emit a quoted string literal. Bare-identifier and
property-access-member positions throw, naming the offending schema field and
value. JSX attribute names have no literal form (JsxAttribute.name is typed as
Identifier), so the attribute is omitted. JSX element names likewise have no
literal form and are rejected. Collection item variables fall back to items.
Behavior for valid schemas is unchanged: all 457 existing snapshots are byte
identical. Adds lib/__tests__/identifier-injection.test.ts with 31 tests covering
each guarded position, including tests that exercise the full render pipeline
rather than the AST printer alone. 634 tests pass across 32 suites.
Changes are limited to source; build artifacts are regenerated.
soberm
force-pushed
the
fix/cve-2025-4318-codegen-injection-sweep
branch
from
July 31, 2026 11:47
e605727 to
d000c9e
Compare
osama-rizk
approved these changes
Jul 31, 2026
bobbor
approved these changes
Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardens
@aws-amplify/codegen-ui-reactagainst the CVE-2025-4318 code-injection class.TypeScript's
factory.createIdentifier(),createPropertyAccessChain()andcreatePropertyAccessExpression()emit their string argument verbatim as source. Where a component, view, form or theme schema string reaches one of those positions without validation, it is written directly into the generated.tsxas executable code rather than as data — so a malicious or malformed schema can inject arbitrary code into a generated component.Earlier fixes for this class addressed individual call sites. This change covers the schema-derived emit sites across the package.
What is fixed
All schema-derived values are validated against an allowlist before emission. Sites now guarded include:
Component properties and conditions
buildConditionalExpression—condition.propertyandcondition.fieldbuildOpeningElementProperties— property key emitted as a JSX attribute namebuildOpeningElementEvents— unmapped event name emitted as a JSX attribute namebindingProperties/collectionPropertieskeys emitted as generated declarationsCollections and data bindings
CollectionRenderer.findItemsVariableName— acollectionPropertieskey flowing into the collection'sitems={…}variablebuildUseEffectStatements—stateReference.dataDependencies, which carrybindingProperties.propertyandcondition.propertythrough to theuseEffectguard and its dependency arraybuildSortFunction—sort.fieldpredicateToObjectLiteralExpression(component and view) — predicate field, operator and keysViews, tables and forms
ReactExpanderRenderer— componentSlot binding property/field, binding property keys,componentSlot.componentName, collection title binding fieldcreateFieldAccessExpressionand table body cell rendering — column field and headerobjectToExpression— viewfieldFormattingkeysbuildDefaultModelDisplayValue— a relationship field's defaultdisplayValuemapping property and fieldbuildActionArgument/assignFieldProperties— action parameter keysCompile-time constants and internally derived names are left unchanged.
Shared validators
Validation is consolidated in
lib/utils/identifiers.tsrather than duplicated per call site.escapePropertyValue()now delegates toescapeIdentifierPath(), andbuildThemePropertyName()tobuildIdentifierOrStringLiteral(), removing three copies of the same allowlist regex.One additional validator was necessary. JSX attribute names follow the
JSXIdentifiergrammar, which permits-— legitimate schemas usedata-testidandaria-label— and forbids the dot-paths that property bindings allow. Validating attribute names with the plain-identifier rule would reject valid input, soJSX_ATTRIBUTE_NAME_REcovers that position specifically.All
Object.entriesaccess tobindingProperties/collectionPropertiesroutes throughsafeIdentifierEntries(), including sites that discard the key, so the guard cannot be bypassed by a new caller.Fail-closed behavior
Each position fails closed in the way appropriate to its syntax, and no position emits an empty identifier — an empty identifier cannot execute, but it is unparseable and surfaces later as an opaque formatter error.
JsxAttribute.nameis typed asIdentifier), so the attribute is omitted.items.Behavior for valid schemas is unchanged.
Testing
lib/__tests__/cve-2025-4318-injection.test.tswith 31 tests covering each guarded position, including tests that exercise the full render pipeline rather than the AST printer alone.Changes are limited to source; build artifacts are regenerated.