Merge pull request #874 from morluto/fix/javascript-http-method-endpoints
fix(javascript): keep keyed-collection reads out of endpoints
This commit is contained in:
commit
9075ce71ad
|
|
@ -162,7 +162,7 @@ const inspectEndpointCall = (
|
|||
value: first,
|
||||
mechanism: `call:${name}`,
|
||||
});
|
||||
const endpoint = endpointArgumentHelper(name, node.arguments);
|
||||
const endpoint = endpointArgumentHelper(name, node.arguments, node.callee);
|
||||
if (endpoint !== undefined)
|
||||
addEndpoint(context, {
|
||||
node,
|
||||
|
|
|
|||
|
|
@ -370,6 +370,43 @@ const isXhrMethodArgument = (node: t.Node | null | undefined): boolean => {
|
|||
);
|
||||
};
|
||||
|
||||
const KEYED_COLLECTION_CONSTRUCTORS = new Set([
|
||||
"FormData",
|
||||
"Headers",
|
||||
"Map",
|
||||
"Set",
|
||||
"URLSearchParams",
|
||||
"WeakMap",
|
||||
"WeakSet",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Whether a get/delete receiver is provably a keyed collection rather than an
|
||||
* HTTP client: a constructed Map, Set, Headers, FormData, or URLSearchParams,
|
||||
* or the platform `headers`/`searchParams` properties of a request, response,
|
||||
* or URL. Other receivers stay possible clients.
|
||||
*/
|
||||
const isKeyedCollectionReceiver = (callee: t.Node): boolean => {
|
||||
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
|
||||
return false;
|
||||
const receiver = callee.object;
|
||||
if (t.isNewExpression(receiver))
|
||||
return (
|
||||
t.isIdentifier(receiver.callee) &&
|
||||
KEYED_COLLECTION_CONSTRUCTORS.has(receiver.callee.name)
|
||||
);
|
||||
if (
|
||||
!t.isMemberExpression(receiver) &&
|
||||
!t.isOptionalMemberExpression(receiver)
|
||||
)
|
||||
return false;
|
||||
const property = semanticStaticPropertyName(
|
||||
receiver.property,
|
||||
receiver.computed,
|
||||
);
|
||||
return property === "headers" || property === "searchParams";
|
||||
};
|
||||
|
||||
/** Select the literal URL argument for recognized network callees. */
|
||||
export const endpointArgument = (
|
||||
name: string,
|
||||
|
|
@ -379,18 +416,22 @@ export const endpointArgument = (
|
|||
| t.JSXNamespacedName
|
||||
| t.ArgumentPlaceholder
|
||||
)[],
|
||||
callee: t.Node,
|
||||
): string | undefined => {
|
||||
if (name === "fetch" || name.endsWith(".fetch") || name === "WebSocket")
|
||||
return stringValue(args[0]);
|
||||
// `XMLHttpRequest.open(method, url)`.
|
||||
if (name.endsWith(".open") && isXhrMethodArgument(args[0]))
|
||||
return stringValue(args[1]);
|
||||
if (
|
||||
["get", "post", "put", "patch", "delete", "request"].some(
|
||||
(method) => name === method || name.endsWith(`.${method}`),
|
||||
)
|
||||
)
|
||||
return stringValue(args[0]);
|
||||
const method = ["get", "post", "put", "patch", "delete", "request"].find(
|
||||
(candidate) => name === candidate || name.endsWith(`.${candidate}`),
|
||||
);
|
||||
if (method !== undefined)
|
||||
// Only get and delete also belong to the keyed-collection APIs.
|
||||
return (method === "get" || method === "delete") &&
|
||||
isKeyedCollectionReceiver(callee)
|
||||
? undefined
|
||||
: stringValue(args[0]);
|
||||
if (name.endsWith("loadURL")) return stringValue(args[0]);
|
||||
return undefined;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -190,6 +190,34 @@ describe("web bundle static-analysis parity", () => {
|
|||
).toEqual(["/dav/", "/xhr-get", "https://xhr.example.test/submit"]);
|
||||
});
|
||||
|
||||
it("keeps HTTP-method-named reads of provable keyed collections out of endpoints", () => {
|
||||
const result = analyzeCapturedWebBundle(
|
||||
inspection(`
|
||||
axios.get("https://api.example.test/users");
|
||||
api.get("users");
|
||||
client.delete("cache-key");
|
||||
new Map().get("map-key");
|
||||
new URLSearchParams(location.search).get("q");
|
||||
new Headers(init).delete("x-trace");
|
||||
new FormData(form).get("file");
|
||||
response.headers.get("content-type");
|
||||
request?.headers.delete("cookie");
|
||||
new URL(location.href).searchParams.get("page");
|
||||
api.headers.post("/v1/headers");
|
||||
api.searchParams.request("/search");
|
||||
`),
|
||||
);
|
||||
expect(
|
||||
result.observations.endpoints.map(({ value }) => value).sort(),
|
||||
).toEqual([
|
||||
"/search",
|
||||
"/v1/headers",
|
||||
"cache-key",
|
||||
"https://api.example.test/users",
|
||||
"users",
|
||||
]);
|
||||
});
|
||||
|
||||
it("recognizes loadURL endpoints like static analysis", () => {
|
||||
const result = analyzeCapturedWebBundle(
|
||||
inspection('window.win.loadURL("https://embedded.test/app");'),
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ const inspectCall = (
|
|||
accumulator,
|
||||
});
|
||||
}
|
||||
const endpoint = endpointArgument(name, node.arguments);
|
||||
const endpoint = endpointArgument(name, node.arguments, node.callee);
|
||||
if (endpoint !== undefined)
|
||||
addFinding({
|
||||
collection: accumulator.endpoints,
|
||||
|
|
|
|||
Loading…
Reference in New Issue