Skip to content

fix(http): use unambiguous framing for transfer cache keys - #70715

Open
Adyej999 wants to merge 1 commit into
angular:mainfrom
Adyej999:fix/http-transfer-cache-key-framing
Open

Adyej999 wants to merge 1 commit into
angular:mainfrom
Adyej999:fix/http-transfer-cache-key-framing

Conversation

@Adyej999

Copy link
Copy Markdown
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our guidelines
  • Tests for the changes have been added
  • All affected tests pass locally

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update
  • Refactoring
  • Build related changes
  • CI related changes
  • Other

What is the current behavior?

HttpTransferCache currently creates its cache key by concatenating several variable-length request fields using a NUL (\0) delimiter:

const key = [
  method,
  responseType,
  mappedRequestUrl,
  serializedBody,
  encodedParams,
].join('\0');

This assumes that the delimiter cannot occur inside any of the serialized fields.

However, a literal U+0000 can occur in the raw HttpRequest.url string as well as in a string request body. This makes it possible for the delimiter to be shifted across a field boundary.

For example, these two distinct POST requests:

Request A:
URL  = /items/a#
body = b\0c

and:

Request B:
URL  = /items/a#\0b
body = c

produce the same NUL-delimited pre-hash representation.

As a result, two semantically different requests can generate the same TransferCache key and the second request can incorrectly reuse the response cached for the first request instead of reaching the backend.

More details and reproduction information are available in the related Google security report:

https://issuetracker.google.com/u/1/issues/561141793

What is the new behavior?

The cache-key fields are serialized as a structured JSON array before hashing:

const key = JSON.stringify([
  method,
  responseType,
  mappedRequestUrl,
  serializedBody,
  encodedParams,
]);

This makes the representation unambiguous because each field remains structurally separated regardless of the characters contained in the field itself.

The two requests above therefore generate different key material and are treated as separate requests.

The PR also adds a regression test covering the NUL-shifted URL/body boundary.

Security impact

The previous delimiter-based representation could cause two different cacheable POST requests to share the same HttpTransferCache entry.

When this happens, a later request can receive a response belonging to an earlier request while its backend request is skipped.

The regression test verifies that requests whose URL/body boundary differs only by placement of a literal NUL are assigned separate TransferCache identities.

This change addresses the underlying framing problem rather than replacing NUL with another sentinel character.

Why structured serialization?

A delimiter-based encoding is only unambiguous if the delimiter is guaranteed to be impossible in every serialized field.

Using a different delimiter would retain the same class of problem if that value can appear in field content.

Structured serialization provides explicit field boundaries and avoids depending on a sentinel-value assumption.

For example:

JSON.stringify([
  'POST',
  'text',
  '/items/a#',
  'b\0c',
  '',
]);

is structurally different from:

JSON.stringify([
  'POST',
  'text',
  '/items/a#\0b',
  'c',
  '',
]);

even though the equivalent delimiter-joined representations collide.

Tests

A regression test was added to:

packages/common/http/test/transfer_cache_spec.ts

covering:

/items/a#       + body b\0c
/items/a#\0b    + body c

The affected HTTP test target passes on the rebased branch:

//packages/common/http/test:test  PASSED

Executed 1 out of 1 test: 1 test passes.

The expected TransferState hashes in the existing test were also updated because changing the pre-hash representation intentionally changes generated cache keys.

Does this PR introduce a breaking change?

  • Yes
  • No

The TransferCache key representation is an internal implementation detail. Existing entries are generated as part of the server-rendering / hydration lifecycle rather than being a public persisted cache-key format.

Other information

The change is intentionally limited to the TransferCache key representation and its regression coverage.

More information about the security issue, reproduction, and affected behavior can be found here:

https://issuetracker.google.com/u/1/issues/561141793

Use structured serialization to prevent request fields from shifting TransferCache key boundaries.
@pullapprove
pullapprove Bot requested a review from JeanMeche September 13, 2026 22:14
@angular-robot angular-robot Bot added the area: common/http Issues related to HTTP and HTTP Client label Sep 13, 2026
@ngbot ngbot Bot added this to the Backlog milestone Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: common/http Issues related to HTTP and HTTP Client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant