fix(audit): name a symlinked sibling debug companion

Selecting an executable skipped every symlink beside it before the name
check, so an app.pdb link left no companion path and no skip count, and
native.debug_companions read not_observed. Selecting the directory
reports the same link.

Check the name first. The map branch already needs a regular file, so no
link is followed.
This commit is contained in:
Matt Van Horn committed 2026-11-02 17:14:31 -08:00
1 parent 9fdb191d2c
commit 68fe35867c
3 files changed
+20 -2

No files matched your search

+1 -1
View File
@@ -56,7 +56,7 @@ A member of a package that inventory did not expand is reported as the host file
A package member skipped by a read allowance still counts as readable for `packaging.extractable_payload`, because the format stores it unencrypted. An encrypted member or one that failed to read counts as unreadable.
A map read counts against the source-map allowance when it starts, so an oversized or unreadable `.map` uses a read as a parsed one does. A sibling `.map` beside a selected file spends the same allowance, after the selected artifact has been read, and past it only the bounded header is read. A sibling whose header can begin a JSON object is then counted as skipped. A `.map` too large to parse is a linker map when its header cannot begin a JSON object. A larger `.map` that begins as JSON, or that has a JavaScript name such as `app.js.map`, is counted as skipped. `native.debug_companions` identifies a companion from its name or from a `.map` body that is not a source map, so its `exposed` result has `inferred` confidence.
A map read counts against the source-map allowance when it starts, so an oversized or unreadable `.map` uses a read as a parsed one does. A sibling `.map` beside a selected file spends the same allowance, after the selected artifact has been read, and past it only the bounded header is read. A sibling whose header can begin a JSON object is then counted as skipped. A `.map` too large to parse is a linker map when its header cannot begin a JSON object. A larger `.map` that begins as JSON, or that has a JavaScript name such as `app.js.map`, is counted as skipped. `native.debug_companions` identifies a companion from its name or from a `.map` body that is not a source map, so its `exposed` result has `inferred` confidence. A symlink with a companion name counts by that name, and its target is not read.
## Status
@@ -1037,6 +1037,24 @@ describe("ReleaseExposureReader mixed maps and sibling paths", () => {
expect(scanned.debug_companions.paths).toHaveLength(2);
},
);
it.skipIf(process.platform === "win32")(
"names a symlinked sibling companion without following it",
async () => {
const root = await createTestTempDirectory("rea-release-sibling-link-");
await writeFile(join(root, "app"), "plain file");
await writeFile(join(root, "symbols"), "debug companion");
await symlink(join(root, "symbols"), join(root, "app.pdb"));
await symlink(join(root, "missing"), join(root, "app.debug"));
await symlink(join(root, "app"), join(root, "plain-link"));
const expected = [join(root, "app.debug"), join(root, "app.pdb")];
const selected = await inspect(join(root, "app"));
expect([...selected.debug_companions.paths].sort()).toEqual(expected);
expect(selected.debug_companions.skipped).toBe(0);
const directory = await inspect(root);
expect([...directory.debug_companions.paths].sort()).toEqual(expected);
},
);
});
describe("ReleaseExposureReader acquisition and sibling review regressions", () => {
+1 -1
View File
@@ -2461,7 +2461,7 @@ const scanSiblings = async (
throw new AnalysisCancelledError(OPERATION, {
cause: input.signal.reason,
});
if (entry.name === own || entry.isSymbolicLink()) continue;
if (entry.name === own) continue;
const sibling = appendCallerPath(directory, entry.name);
if (hasCompanionName(entry.name)) {
state.companionPaths.add(sibling);