Compare commits

...
Author SHA1 Message Date
N0zoM1z0 19a770a705 fix(javascript): integrate media preload evidence correction 2026-10-10 21:57:18 +08:00
Claude 5fd9f4ad60 fix(javascript): stop reading media preload attributes as Electron preloads
HTML audio and video elements have their own `preload` attribute ("none",
"metadata", "auto"). A React renderer compiles `<audio preload="metadata">`
to `jsx("audio", { preload: "metadata" })`, and the `property:preload`
heuristic reported it as an Electron preload script named `metadata`.

Skip `preload` properties in the props object of a call whose first
argument is the static tag `audio` or `video`. The callee is not checked,
because minified element factories have arbitrary names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPY2GmufMNGQKoEgRUx1pd
2026-10-10 01:33:30 +00:00
3 changed files with 37 additions and 0 deletions
@@ -24,6 +24,20 @@ describe("Electron option evidence", () => {
});
});
it("does not read an audio or video preload attribute as a preload script", () => {
const analysis = analyzeJavaScriptStaticSource(`
jsx("audio", { controls: true, preload: "metadata", src });
(0, React.createElement)(\`video\`, { preload: "auto" });
jsx("webview", { preload: "./guest.js" });
const options = { preload: "./preload.js" };
`);
expect(analysis.role_paths.map(({ path }) => path)).toEqual([
"./guest.js",
"./preload.js",
]);
});
it("keeps values unknown when a later computed key can override them", () => {
const analysis = analyzeJavaScriptStaticSource(`
new BrowserWindow({ webPreferences: { preload: "./maybe.js" }, [key]: null });
@@ -65,6 +65,8 @@ export const inspectCall = (
addReference(context, { node, kind: "service-worker", specifier: first });
inspectEndpointCall(node, name, first, context);
inspectStorageCall(node, name, first, context);
if (first === "audio" || first === "video")
noteMediaElementPreloads(node, accumulator);
if (name.endsWith("loadFile") && first !== undefined)
addLocatedFinding(context, {
collection: accumulator.roles,
@@ -95,6 +97,23 @@ export const inspectCall = (
});
};
// Element factories such as jsx("audio", props) and createElement("video",
// props) carry the HTML media `preload` hint ("none", "metadata", "auto"),
// which never names an Electron preload script.
const noteMediaElementPreloads = (
node: JavaScriptCallLike,
accumulator: AnalysisAccumulator,
): void => {
const props = node.arguments[1];
if (!t.isObjectExpression(props)) return;
for (const property of props.properties)
if (
t.isObjectProperty(property) &&
semanticStaticPropertyName(property.key, property.computed) === "preload"
)
accumulator.mediaElementPreloads.add(property);
};
const requireCallSpecifier = (
node: JavaScriptCallLike,
name: string,
@@ -201,6 +220,7 @@ export const inspectRoleProperty = (
context: FindingContext,
): void => {
if (semanticStaticPropertyName(node.key, node.computed) !== "preload") return;
if (context.accumulator.mediaElementPreloads.has(node)) return;
const path = staticPath(node.value);
if (path === undefined) return;
addLocatedFinding(context, {
@@ -47,6 +47,8 @@ export interface JavaScriptAnalysisAccumulator {
readonly utilityProcesses: LocatedJavaScriptFinding<ElectronUtilityProcessFinding>[];
readonly nativeAddonBindings: LocatedJavaScriptFinding<ElectronNativeAddonBindingFinding>[];
readonly moduleRangeIndex: JavaScriptModuleRangeIndex;
/** `preload` attributes of audio and video elements, which are not scripts. */
readonly mediaElementPreloads: WeakSet<t.Node>;
readonly seen: Set<string>;
visitedNodes: number;
unknownFindings: number;
@@ -111,6 +113,7 @@ export const createJavaScriptAnalysisAccumulator = (
utilityProcesses: [],
nativeAddonBindings: [],
moduleRangeIndex: new JavaScriptModuleRangeIndex(sourceLength),
mediaElementPreloads: new WeakSet(),
seen: new Set(),
visitedNodes: 0,
unknownFindings: 0,