Skip to content

fix(core): don't serve in-page POST/unsafe requests from the discovery cache#2325

Open
Sriram567 wants to merge 1 commit into
masterfrom
fix/PER-9766-method-aware-discovery-cache
Open

fix(core): don't serve in-page POST/unsafe requests from the discovery cache#2325
Sriram567 wants to merge 1 commit into
masterfrom
fix/PER-9766-method-aware-discovery-cache

Conversation

@Sriram567

Copy link
Copy Markdown
Contributor

Fixes PER-9766PSE team can't snapshot pages behind form authentication.

Root cause

Asset-discovery's request interception serves cached responses keyed on URL only, ignoring the HTTP method:

  • packages/core/src/discovery.js:463lookupCacheResource keys purely on URL (snapshotResources.get(url) || cache.get(url)).
  • packages/core/src/network.js (sendResponseResource) — fulfils any cached resource via Fetch.fulfillRequest regardless of request.method.

A customer logging in from an execute script did:

await fetch(form.action, { method: 'POST', credentials: 'same-origin' }); // login
const html = await (await fetch('/my-account', { credentials: 'same-origin' })).text();
document.open(); document.write(html); document.close();

The login POST went to the site root, which had already been captured (GET) and cached during navigation. Discovery answered the POST from that cached login-page body, so it never reached the origin — no session was established, and the follow-up /my-account fetch returned unauthenticated content that document.write then captured.

Debug-log proof: Evaluate JavaScriptHandling request: https://…/ (the POST) → - Resource cache hit/my-account fetched unauthenticated → Snapshot taken.

Fix

Only serve cached resources for safe, cacheable methods (GET/HEAD). POST/PUT/PATCH/DELETE (or an absent method) fall through to Fetch.continueRequest → origin. Answering a state-changing request with a cached GET body is incorrect in general; page navigation and asset fetches are GET, so normal snapshotting is unaffected.

let cacheableMethod = request.method === 'GET' || request.method === 'HEAD';
// …
} else if (resource && cacheableMethod && (resource.root || resource.provided || !disableCache)) {

Testing

Added discovery.test.jsresource caching › "serves cached resources only for cacheable methods (PER-9766)": caches an asset on the initial GET, then issues a POST to the same URL from an execute script and asserts the origin sees the POST (i.e. it is not served from cache). Verified the spec fails without the fix (Expected ['GET'] to contain 'POST') and passes with it.

Review notes

This touches core discovery interception (100%-coverage gate). The new cacheableMethod branch is exercised by the GET+POST test. Worth a prod-replay sanity check, though the only behavior change is: an unsafe-method request whose URL happens to be cached now hits the origin instead of replaying a GET body.

🤖 Generated with Claude Code

…y cache

Asset-discovery's request interception serves cached responses keyed on URL
only, ignoring the HTTP method. An in-page POST to a URL that was already
fetched via GET (e.g. a login form submitted from an `execute` script) was
answered from the cached GET body and never reached the origin — so the
request never authenticated, no session cookie was set, and the snapshot
captured unauthenticated content.

Only serve cached resources for safe, cacheable methods (GET/HEAD). POST/PUT/
PATCH/DELETE (or an absent method) now fall through to a real origin request.

Fixes PER-9766.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Sriram567 Sriram567 requested a review from a team as a code owner June 29, 2026 04:22
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.

1 participant