FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(service-worker): validate redirected assets against the original hash by mohass1927 · Pull Request #70373 · angular/angular · GitHub

fix(service-worker): validate redirected assets against the original hash - #70373

Open
mohass1927 wants to merge 1 commit into
angular:mainfrom
mohass1927:fix-sw-redirect-hash-validation
Open

fix(service-worker): validate redirected assets against the original hash#70373
mohass1927 wants to merge 1 commit into
angular:mainfrom
mohass1927:fix-sw-redirect-hash-validation

Conversation

Copy link
Copy Markdown

PR Checklist

PR Type

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

AssetGroup.fetchFromNetwork() hash-validates a response, and then, if the response was
redirected, throws that validated body away and re-requests the redirect target:

const res = await this.cacheBustedFetchFromNetwork(req);

if ((res as any)['redirected'] && !!res.url) {
  // ...
  return this.fetchFromNetwork(this.newRequestWithMetadata(res.url, req), redirectLimit - 1);
}

The recursive call re-derives the canonical hash from res.url. That URL is the redirect
target, which is not a manifest entry, so cacheBustedFetchFromNetwork() takes its unhashed
branch and returns the second response without comparing it to anything. fetchAndCacheOnce()
then caches that second body under the original request.

So for a manifest-hashed asset served through a redirect, the body that ends up in the cache is
not the body that was validated. A server that answers the first request with the expected
contents and the second with different contents gets those different contents cached under the
original hashed URL, and ngsw.json's hash for that URL no longer describes what is stored.

This is reachable on a normal ng add @angular/pwa production build whenever a hashed build
artifact is served via a redirect — e.g. the app origin redirects its bundles to an asset host.

The reason this wasn't caught: every existing redirect test uses a URL with no manifest hash.
/redirected.txt and /lazy/redirected.txt are declared in asset group urls but are not files
in dist, so they never enter the hash table and the hashed-plus-redirected path is never
exercised.

What is the new behavior?

The original normalized URL is carried through the redirect recursion, so the body that is
ultimately cached is validated against the hash the manifest specifies for the URL it is being
cached under. A redirect whose target serves the expected contents behaves exactly as before; a
target that serves something else now fails hash validation like any other mismatch, and nothing
is cached.

Two tests are added to prefetch_spec.ts covering a hashed asset served through a redirect:
one where the target matches the manifest hash (must still cache and serve), and one where it
does not (must throw Hash mismatch and cache nothing). MockServerStateBuilder.withRedirect()
gains an optional body so the mock can model the browser behavior of following the redirect and
exposing the target's contents on the redirected response; the default is unchanged.

Verification:

  • Full packages/service-worker/worker suite passes with the change (155 specs).
  • The new mismatch test fails against unpatched main (no error is thrown, and the second body
    is cached) and passes with the change, so it genuinely covers the path.
  • Checked in a real browser as well, using a stock ng new + ng add @angular/pwa production
    build whose generated loadChildren() chunk is served through a redirect: before the change the
    redirect target's second response is what lands in the SW cache under the app-origin chunk URL;
    after the change nothing is cached, while the legitimate case still caches the generated chunk
    and the lazy route loads normally.

Does this PR introduce a breaking change?

  • Yes
  • No

fetchFromNetwork() and cacheBustedFetchFromNetwork() gain an optional trailing parameter that
defaults to today's behavior. Both are internal to the worker.

Other information

The two existing redirect hardening advisories in this area address different state, and neither
covers this: GHSA-gv2q-mqqv-365m
preserves a caller's explicit redirect: "error", and
GHSA-qxh6-94w6-9r5p
strips sensitive headers on cross-origin redirects. This is about which body gets hash-checked,
under ordinary default redirect handling and with no special headers involved.

Happy to move this to a private advisory instead if the team would prefer to handle it that way.

…hash

`fetchFromNetwork()` validates a response against the manifest hash for the
requested URL, but when that response is a redirect it discards the validated
body and re-requests the redirect target. The recursive call re-derives the
canonical hash from the *target* URL, which is not a manifest entry, so it
takes the unhashed code path and returns the second response unvalidated.
`fetchAndCacheOnce()` then stores that second body under the original request.

A server that answers the first request with the expected contents and the
second with different contents therefore gets those different contents cached
under the original manifest-hashed URL, even though the manifest hash is what
the resource is supposed to be pinned to.

Carry the original normalized URL through the redirect recursion so that the
body which is ultimately cached is checked against the hash the manifest
specifies for the URL it is cached under. Redirects whose target serves the
expected contents are unaffected.

The existing redirect tests only cover URLs with no manifest hash, so this
path was never exercised; add coverage for a hashed asset served through a
redirect, in both the matching and non-matching cases.
pullapprove Bot requested a review from kirjs August 24, 2026 23:20
angular-robot Bot added the area: service-worker Issues related to the @angular/service-worker package label Aug 24, 2026
ngbot Bot added this to the Backlog milestone Aug 24, 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

Labels

area: service-worker Issues related to the @angular/service-worker package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant


Back | FazBrowse Home | New Git URL