fix(javascript): keep literal HTTP methods on storage-named receivers

A receiver spelled indexedDB or caches does not prove the binding, so
indexedDB.open("GET", "/api") keeps its endpoint. Such receivers
still need a standard HTTP method literal: a computed first argument or
an uppercase database name such as "APP" reads as storage, not XHR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 committed 2026-10-07 08:12:14 +00:00
1 parent 898b0f62d7
commit 9f48a76e11
2 files changed
+20 -11

No files matched your search

@@ -410,7 +410,7 @@ const BROWSING_CONTEXT_KEYWORDS = new Set([
* `window.open(url, "_blank")` share the callee name. A literal method must
* be an HTTP method; with a computed method, the second literal is a URL
* unless it is provably an fs flag or a browsing-context keyword, or the
* receiver is spelled as a browsing context. XHR normalizes the standard
* receiver is spelled as a browsing context or storage. XHR normalizes the standard
* methods' case; extension methods such as WebDAV `PROPFIND` are
* conventionally uppercase tokens.
*/
@@ -422,13 +422,18 @@ const xhrOpenUrl = (
const url = stringValue(urlNode);
if (url === undefined) return undefined;
const method = stringValue(methodNode);
// IndexedDB and Cache Storage `open` take a name and a version, so a
// receiver spelled as storage needs a standard HTTP method literal.
const storage = storageKind(name) !== undefined;
if (method === undefined)
return WINDOW_OPEN_CALL.test(name) ||
return storage ||
WINDOW_OPEN_CALL.test(name) ||
FS_OPEN_FLAGS.has(url) ||
BROWSING_CONTEXT_KEYWORDS.has(url.toLowerCase())
? undefined
: url;
return XHR_METHODS.has(method.toUpperCase()) || /^[A-Z][A-Z-]*$/u.test(method)
return XHR_METHODS.has(method.toUpperCase()) ||
(!storage && /^[A-Z][A-Z-]*$/u.test(method))
? url
: undefined;
};
@@ -483,11 +488,7 @@ export const endpointArgument = (
): string | undefined => {
if (name === "fetch" || name.endsWith(".fetch") || name === "WebSocket")
return stringValue(args[0]);
// IndexedDB and Cache Storage `open` take a name and a version, not a URL.
if (name.endsWith(".open"))
return storageKind(name) === undefined
? xhrOpenUrl(name, args[0], args[1])
: undefined;
if (name.endsWith(".open")) return xhrOpenUrl(name, args[0], args[1]);
const method = ["get", "post", "put", "patch", "delete", "request"].find(
(candidate) => name === candidate || name.endsWith(`.${candidate}`),
);
+11 -3
View File
@@ -213,15 +213,23 @@ describe("web bundle static-analysis parity", () => {
const source = `
indexedDB.open(databaseName, "2");
window.indexedDB.open(databaseName, "/v3");
indexedDB.open("APP", "4");
indexedDB.open("GET", "/storage-named-xhr");
`;
expect(
analyzeCapturedWebBundle(inspection(source)).observations.endpoints,
).toEqual([]);
analyzeCapturedWebBundle(inspection(source)).observations.endpoints.map(
({ value }) => value,
),
).toEqual(["/storage-named-xhr"]);
const analysis = analyzeJavaScriptStaticSource(source);
expect(analysis.endpoints).toEqual([]);
expect(analysis.endpoints.map(({ value }) => value)).toEqual([
"/storage-named-xhr",
]);
expect(analysis.storage.map(({ kind }) => kind)).toEqual([
"indexed-db",
"indexed-db",
"indexed-db",
"indexed-db",
]);
});