fix(audit): keep a declared .map from reading as a linker map

A script's sourceMappingURL=assets.map was dropped because the name
already looked like a map. A malformed assets.map then settled as a
linker map, so both source-map checks read not_observed and
native.debug_companions reported an exposure the release does not ship.

Keep a generic .map name as a declared target and read it as a
JavaScript map, in either scan order, so it stays unresolved.
This commit is contained in:
Matt Van Horn committed 2026-11-02 08:04:45 -08:00
1 parent 3d0283dc21
commit a1919fe276
2 files changed
+36 -3

No files matched your search

@@ -2631,6 +2631,32 @@ describe("ReleaseExposureReader member identity", () => {
},
);
it.each([
["app.js", { embedded: 0, unparsed: 1, skipped: 0 }],
["z.js", { embedded: 0, unparsed: 0, skipped: 1 }],
])(
"keeps a malformed assets.map that %s declares unresolved",
async (script, expected) => {
const root = await createTestTempDirectory("rea-release-generic-map-");
await writeFile(
join(root, script),
"const value = 1;\n//# sourceMappingURL=assets.map\n",
);
await writeFile(join(root, "assets.map"), "not a map\n");
const scanned = await inspect(root);
expect(scanned.source_maps).toMatchObject(expected);
expect(scanned.debug_companions.paths).toEqual([]);
},
);
it("keeps an undeclared malformed assets.map a linker map", async () => {
const root = await createTestTempDirectory("rea-release-linker-map-");
await writeFile(join(root, "assets.map"), "not a map\n");
const scanned = await inspect(root);
expect(scanned.source_maps).toMatchObject({ embedded: 0, skipped: 0 });
expect(scanned.debug_companions.paths).toHaveLength(1);
});
it("counts a declared map target that never ships as skipped", async () => {
const root = await createTestTempDirectory("rea-release-missing-map-");
await writeFile(
+10 -3
View File
@@ -987,8 +987,11 @@ const examineMapMember = async (
member: MemberView,
): Promise<boolean> => {
// A member a script names or an owner marks as a map is a JavaScript map
// whatever it is called.
const declared = member.logical.toLowerCase().endsWith(".map")
// whatever it is called. Only an undeclared `.map` may be a linker map.
const undeclared =
member.logical.toLowerCase().endsWith(".map") &&
!state.mapTargets.has(memberLocationKey(member.location));
const declared = undeclared
? member
: { ...member, logical: `${member.logical}.js.map` };
const loaded = await examineMap(state, declared);
@@ -1380,7 +1383,9 @@ const noteLocalMapReference = (
}
if (/^[a-z][a-z0-9+.-]+:/iu.test(url)) return;
const path = url.replace(/[?#].*$/su, "");
if (path.length === 0 || isSourceMap(path, "file")) return;
// A generic `.map` name stays a target: only the declaration tells it from a
// linker map.
if (path.length === 0 || isJavaScriptSourceMapName(path)) return;
let decoded: string;
try {
decoded = decodeURIComponent(path);
@@ -1398,6 +1403,8 @@ const noteLocalMapReference = (
*/
const noteMapTarget = (state: ScanState, location: string): void => {
const target = memberLocationKey(location);
// A map read before its declaration may have settled as a linker map.
if (state.companionPaths.delete(location)) state.skippedMaps += 1;
if (state.examinedMaps.has(target) || state.mapTargets.has(target)) return;
if (state.examinedLocations.has(target)) {
state.skippedMaps += 1;