Previously, the compiler disallowed translation of any attribute starting with 'on' for security reasons. This incorrectly disallowed translation of the 'on' attribute itself, which is not an event handler.
This commit introduces `isPossibleEventHandler` to verify that the property name has a length greater than 2 in addition to starting with 'on'. This allows attributes like 'on' to be translated while still correctly disallowing actual event handlers like 'onerror', 'onclick', etc.
The previous fix for GHSA-692r-grfm-v8x7 was incomplete because it rejected script tags only when locating an explicit host element. Dynamic component instantiation can also infer the host element from the component selector.
Move the script-host rejection to the point where ComponentFactory has resolved the host element for either path, so createComponent rejects script hosts consistently.
(cherry picked from commit 135f3755b4)
Only cache locale data loaded from the global locale registry when an actual locale entry is found.
This prevents attacker-controlled missing locale identifiers from being retained indefinitely in SSR when locale lookup falls back to a parent locale or the built-in English locale, avoiding unbounded process memory growth in locale-aware pipes and formatters.
(cherry picked from commit ea8277ae37)
Normalize SVG animation attributeName lookup to also recognize lowercase attributename before allowing dynamic animation value bindings.
Add runtime and platform-server SSR regression coverage for lowercase attributename retargeting.
(cherry picked from commit d5e689af80)
Add allowOriginChange option to ResolveUrlOptions in resolveUrl to enforce same-origin validation on resolved URLs. When set to false, it prevents any cross-origin changes (including HTTP/HTTPS URLs), aligning the emulated server-side platform location environment with browser security behavior.
Refactor ServerPlatformLocation.replaceState to use allowOriginChange: false instead of manual comparison, hardening state change validation against cross-origin URLs.
Add unit tests in url_spec.ts and platform_location_spec.ts for the origin validation changes.
Replace the custom 64-bit non-cryptographic combined DJB2 hashing implementation in HttpTransferCache with a robust, pure JavaScript, synchronous SHA-256 algorithm.
Using DJB2 is vulnerable to pre-image and second-preimage attacks due to its small 64-bit keyspace and mathematical simplicity. An attacker could craft colliding request inputs to poison the cache, potentially causing a CDN or the application to serve the wrong cached response to legitimate users.
SHA-256 provides strong cryptographic collision resistance, preventing cache key collision attacks. A custom synchronous implementation is required because the Web Crypto API (`crypto.subtle.digest`) is asynchronous, whereas the transfer cache state lookup and interceptor flow must operate synchronously.
Also, update the unit tests to dynamically verify the custom SHA-256 output against the native Web Crypto API.
Do not store HTTP transfer cache entries when either the request or response uses `Cache-Control: no-store`, `Cache-Control: private`, or `Cache-Control: no-cache`.
Also skip transfer cache when requests use the Fetch API `cache` option with `no-store` or `no-cache`.
Because transfer cache serializes SSR HTTP responses into the rendered HTML, Angular now treats these directives conservatively to avoid exposing sensitive or explicitly uncacheable data through `TransferState`.
(cherry picked from commit 4d150156ca)
(cherry picked from commit 64ce11fcd4)
Treat HttpClient requests using `credentials: 'include'` and `same-origin` as credentialed when deciding whether a response can be stored in the HTTP transfer cache.
The transfer cache already skips requests with `withCredentials`, `Cookie`, `Authorization`, or `Proxy-Authorization` because those responses may contain user-specific data. Fetch-backed requests can express the same credentialed behavior through the `credentials` option, so these responses must not be serialized into the SSR HTML.
This keeps credentialed SSR responses out of TransferState and aligns the cache eligibility check with the fetch request options supported by HttpClient.
(cherry picked from commit 8ec01970d2)
Reject non-script elements when reading the SSR transfer state payload by id.
This prevents attacker-controlled elements with a clobbered id from spoofing
hydration state.
Stop inheritance traversal before built-in prototype objects and only read `ɵcmp`/`ɵdir` when they are own properties of a super type. This prevents polluted inherited properties from being treated as Angular defs during inheritance merging.
Also adds regression tests covering polluted `Object.prototype.ɵdir` and `Object.prototype.ɵcmp` to ensure polluted host metadata is not inherited.
(cherry picked from commit e695379354)
Prior to this commit, `LOCALE_DATA` was initialized as a plain object literal:
```typescript
let LOCALE_DATA: {[localeId: string]: any} = {};
```
While `__proto__` is neutralized by the `replace(/_/g, '-')` sanitization step (becoming `--proto--`), keys like `constructor` and `prototype` pass through unchanged and would modify special properties on `Object.prototype` if used as bracket notation keys on a plain object.
**Example attack through the public API:**
```typescript
// attacker calls the public registerLocaleData API with a crafted localeId
registerLocaleData(data, 'constructor');
// internally becomes:
LOCALE_DATA['constructor'] = data;
// → modifies Object.prototype.constructor for every object in the process
// or with extraData:
registerLocaleData(data, 'constructor', extraData);
// LOCALE_DATA['constructor'][LocaleDataIndex.ExtraData] = extraData;
// → Object.prototype[LocaleDataIndex.ExtraData] = extraData
// → every plain object in the process now has this property
// → affects JSON serialization, property enumeration, and framework internals
// consequence — any subsequent object created in the process is affected:
const user = getUserFromSession();
console.log(user[LocaleDataIndex.ExtraData]); // → attacker-controlled value
```
In a long-running SSR server this pollution persists for the lifetime of the process and affects all subsequent requests from all users.
**The fix** initializes `LOCALE_DATA` with `Object.create(null)`:
```typescript
let LOCALE_DATA: {[localeId: string]: any} = Object.create(null);
```
A null-prototype object has no prototype chain, so any key is treated as a plain string with no special behavior, making prototype pollution impossible regardless of input — without relying on the sanitization step as the sole protection.
(cherry picked from commit 0deac976f3)
Removes the `should throw error on translated SVG script ResourceURL
attributes` integration test from `security_integration_spec.ts`.
This test is now obsolete because SVG `<script>` elements are stripped during
template compilation (implemented in 90494cd909). As a result, they are no
longer present in the compiled template to trigger runtime sanitization,
causing this test (which expected a sanitization error to be thrown) to fail.
PR Close#68925
Ensures that namespaced <script> elements (such as :svg:script) are correctly classified as PreparsedElementType.SCRIPT by the template preparser and stripped during compilation to prevent potential XSS vulnerabilities. Consequently, obsolete security schema mappings and runtime sanitization checks for <script> attributes have been removed since these elements are never present in compiled template outputs.
PR Close#68925
Normalize namespaced tag names (e.g., :xhtml:a to a) inside i18nResolveSanitizer before looking up their security context. This ensures custom namespaced tag attributes undergo correct translation sanitization at runtime.
PR Close#68925
Custom XML/XHTML namespaced elements (e.g., <xhtml:a>) fall back to the standard HTML namespace during element creation at compile-time/runtime. However, their property and security context lookups inside the schema registry were incorrectly performed using the full namespaced tag name (e.g., :xhtml:a), which bypassed the default a|href sanitization registry and incorrectly returned SecurityContext.NONE instead of SecurityContext.URL.
This commit introduces tag name normalization inside DomElementSchemaRegistry for custom namespaces (other than the built-in svg and math namespaces). Custom namespaced tag names are now normalized to their simple HTML element counterparts for all registry queries, ensuring that correct property schema validation and dynamic security sanitization rules (such as URL sanitization) are enforced at runtime.
PR Close#68925
Dynamic bindings to `href` and `xlink:href` attributes on SVG `<a>` elements (`<svg:a>`) were previously unmapped in the DOM security schema. As a result, they bypassed sanitization completely, creating a potential XSS vulnerability if bound to untrusted user inputs (e.g., `javascript:` URLs).
This fix mitigates this risk by:
1. Registering `href` and `xlink:href` on `<svg:a>` elements under the `SecurityContext.URL` context in both the compiler and core DOM security schemas.
2. Enabling template compilation to output runtime URL sanitization checks (`ɵɵsanitizeUrl`) on these attributes.
3. Adding regression and verification test cases to ensure dynamic SVG link bindings are safely sanitized at runtime while static values are correctly allowed.
PR Close#68925
Updates `DomElementSchemaRegistry` to strip `:svg:` and `:math:` namespace prefixes
from tag names before querying `SECURITY_SCHEMA` at compile-time. This allows SVG
and MathML attributes to correctly match their security contexts during compilation.
PR Close#68925
The previous documentation for `DoCheck` / `ngDoCheck` implied that the
default change-detector had run on the directive itself, which is
misleading. `ngDoCheck` is actually invoked when the *parent's*
change-detector checks the directive's input bindings — meaning it fires
even for `OnPush` components whose own change detection was skipped.
Updated three places in lifecycle_hooks.ts:
- Interface description: scopes "the check" to input bindings in the
parent template and adds an explicit OnPush callout.
- "detects changes" clarified to "detects changes to the directive's
input bindings".
- Method description: "after the default change-detector runs" →
"after the default change-detector has checked the directive's input
bindings in the parent template".
Fixes#48140
(cherry picked from commit ca44055166)
Ensures that namespaced <script> elements (such as :svg:script) are correctly classified as PreparsedElementType.SCRIPT by the template preparser and stripped during compilation to prevent potential XSS vulnerabilities. Consequently, obsolete security schema mappings and runtime sanitization checks for <script> attributes have been removed since these elements are never present in compiled template outputs.
(cherry picked from commit 90494cd909)
The following:
`@Output() someChange = new EventEmitter<void>();`
is correctly migrated to:
`readonly someChange = output<void>();`
However, a TODO is incorrectly inserted for subsequent emissions from
`someChange`, stating that an argument is expected.
(cherry picked from commit 16fe27bfef)
To enhance application security and prevent accidental or malicious script execution, this change ensures that dynamically mounting a component via createComponent directly onto a <script> element throws a runtime error in development mode. SVG <script> elements are also rejected. The error message is designed to be fully tree-shakable under production builds where ngDevMode is disabled.
(cherry picked from commit 0011664d1c)
Refactors the element security schema lookups and runtime attribute validation to
consistently account for SVG and MathML namespaces. This improves the modularity
and accuracy of security context mapping during template compilation and runtime
constant evaluation, eliminating redundant or false-positive lifecycle checks.
(cherry picked from commit cef4a095a2)
Ensures the resource map for URL sanitization is queried using lowercase tag and property names, improving robustness by handling case variations consistently.
(cherry picked from commit 00c284015c)
Moves the event attribute validation check outside of `ngDevMode` in the `elementAttributeInternal` instruction to ensure that bindings to event attributes like `on*` are always blocked at runtime.
(cherry picked from commit 5b421c61cd)
The i18n sub-system has the `changeMask` and `changeMaskCounter` flags which are set by i18n-related instructions and reset once the state is applied. The problem is that if something throws within the application logic, the flags would never be reset. This is currently causing flakes in our CI runs.
These changes resolve the issue by adding a try/finally around the flags.
(cherry picked from commit 6339d264eb)
Using explicit single generic arguments with transforms (for example, input<boolean>(false, {transform: booleanAttribute})) previously failed overload resolution.
Before this fix, type-checking produced:
````
✘ [ERROR] TS2769: No overload matches this call.
Overload 1 of 5, '(initialValue: boolean, opts?: InputOptionsWithoutTransform<boolean> | undefined): InputSignal<boolean>', gave the following error.
Type '(value: unknown) => boolean' is not assignable to type 'undefined'.
Overload 2 of 5, '(initialValue: undefined, opts: InputOptionsWithoutTransform<boolean>): InputSignal<boolean | undefined>', gave the following error.
Argument of type 'true' is not assignable to parameter of type 'undefined'. [plugin angular-compiler]
```
This change adds specialized overloads for explicit read generics.
(cherry picked from commit 1ab654cf28)
When a component is created dynamically via ViewContainerRef.createComponent
and receives projectable nodes (e.g. raw DOM nodes or embedded view root nodes),
applying ngSkipHydration to its host element did not prevent NG0503 from being
thrown during SSR serialization.
The root cause is an asymmetry in the serialization pipeline. For inline child
components, serializeLView already guards the annotateHostElementForHydration
call with a ngSkipHydration attribute check, so the component's lView is never
serialized when hydration is opted out. For components hosted inside an
LContainer (created via ViewContainerRef.createComponent), serializeLContainer
called serializeLView unconditionally — bypassing that guard entirely. When
serializeLView then encountered a projection slot backed by a raw DOM node
array, it threw NG0503 regardless of the ngSkipHydration flag.
The fix adds the same guard inside serializeLContainer before calling
serializeLView: if the child lView belongs to a component whose host element
carries ngSkipHydration, the lView serialization is skipped. This matches the
existing behavior for inline components and allows the documented workaround to
actually work for dynamically created ones.
Fixes#67928
(cherry picked from commit 4c9afb68a3)
AOT was generating an array that was ordered as signal queries first, then the decorator queries.
Aligning JIT with AOT fixes the issue illustrated by the test.
fixes#68404
(cherry picked from commit 8c11816490)
Two issues caused browser test failures after the event replay fix:
1. `markEventHandledForElement` used the event object as a WeakMap key, but
`DebugElement.triggerEventHandler` can pass null or primitive values as the
event argument. Added an early return for non-object values.
2. Registering a separate `domListener` closure with `renderer.listen` instead of
`wrappedListener` caused `DebugElement.triggerEventHandler` to invoke the
handler twice: once via `this.listeners` (which holds `wrappedListener`) and
once via Zone.js's `eventListeners` (which holds the unwrapped `domListener`).
The existing dedup logic in `triggerEventHandler` checks if the unwrapped
Zone.js listener is already in `invokedListeners`, but with two different
function objects that check always fails.
Replaced the `domListener` wrapper with a property (`__ngNativeEl__`) stored
directly on `wrappedListener`. `wrapListenerIn_markDirtyAndPreventDefault` reads
this property and calls `markEventHandledForElement` when the listener fires,
while `renderer.listen` receives the same `wrappedListener` function that
Angular stores in `lCleanup`, preserving the dedup invariant.
(cherry picked from commit 3583c01bf9)
When `withEventReplay()` is enabled and a component hydrates before the
application becomes stable (e.g. while a pending HTTP request is in
flight), a user interaction on the hydrated element triggers both the
real DOM listener registered by Angular and the jsaction replay path.
This causes the event handler to be invoked twice.
The root cause is that `listenToDomEvent` registers the same
`wrappedListener` both as a stashed jsaction handler (via
`stashEventListenerImpl`) and as a native DOM listener (via
`renderer.listen`). When the user interacts after hydration but before
app stability, jsaction queues the event because no dispatcher is
registered yet. Once the app stabilises and `initEventReplay` runs,
jsaction replays the queued event through `invokeListeners`, which
calls the stashed handler a second time.
The fix tracks dispatched `(event, element)` pairs in a
`WeakMap<Event, WeakSet<Element>>`. The native DOM listener wrapper
records each pair via `markEventHandledForElement`, and `invokeListeners`
skips replay for any pair already present. Keying by element (rather
than event alone) preserves incremental hydration behaviour, where
jsaction legitimately replays the same event on a different element
(the deferred block content) from the one that originally triggered
hydration.
Fixes#67328
(cherry picked from commit d5fd51e956)
This fixes an issue where when removing NgClass from the imports array of a component, an extra trailing comma would be left behind if it was the last element in that component`.
(cherry picked from commit b395173cf2)
This fixes an issue where when removing NgClass from the imports array of a component, an extra trailing comma would be left behind if it was the last element in that component`.
(cherry picked from commit 27f021248d)
Improves error messages shown during hydration mismatches to better
surface cases where third-party scripts or browser extensions have
modified the DOM outside of Angular's control.
Fixed#59224
(cherry picked from commit d771a65ac0)
This commit escapes forward slashes in the transfer state JSON output as \u002F to prevent search engine crawlers from aggressively indexing relative paths inside the inline script tag. It also updates related unit and integration tests across core and platform-server.
Fixes#65310
(cherry picked from commit 3c7641151c)