fix(audit): read rooted Go tool path arguments whole

`-L "/tmp/private,sdk"` and `-L=/tmp/a=b` name one directory, so a `,` or
`=` in a documented path flag's argument is literal. `-trimpath` keeps its
prefix=replacement structure.
This commit is contained in:
Matt Van Horn committed 2026-11-03 08:13:54 -08:00
1 parent 1d4853f073
commit 87af4ef472
2 files changed
+60 -2

No files matched your search

@@ -1437,6 +1437,21 @@ describe("ReleaseExposureService Go linker path options", () => {
});
});
describe("ReleaseExposureService Go rooted linker flag paths", () => {
it.each([
[`-L "/tmp/private,sdk" -s -w`, "/tmp/private,sdk"],
[`-L "/tmp/private=sdk" -s -w`, "/tmp/private=sdk"],
[`-L=/tmp/private,sdk -s -w`, "/tmp/private,sdk"],
[`all=-L=/tmp/private,sdk`, "/tmp/private,sdk"],
[`-tmpdir /srv/obj,1`, "/srv/obj,1"],
])("keeps the whole rooted argument in %s", async (value, path) => {
const report = await goReport(goModule([{ key: "-ldflags", value }]));
const check = report.checks.find((item) => item.id === "go.build_paths");
expect(check?.status).toBe("exposed");
expect(check?.observation.samples).toEqual([path]);
});
});
describe("ReleaseExposureService Go relative flag paths", () => {
it("keeps relative paths given to compiler path flags, attached or not", async () => {
const report = await goReport(
@@ -957,6 +957,41 @@ const goRelativeFlagPaths = (flags: string[], pathFlag: RegExp): string[] =>
: [argument];
});
/**
* `-trimpath` takes `prefix=replacement` rewrites separated by `;`, so its
* rooted value is read by `flagPaths` with that structure kept.
*/
const GO_PATH_LIST_FLAG = /^-{1,2}trimpath(?:=|\s+|$)/u;
/**
* A Go tool path flag's whole argument, rooted or relative, attached after `=`
* or in the next field, with the fields it was read from. The tool takes the
* argument as one path, so a `,` or `=` in it is a literal character.
* `-trimpath` is the one list among them.
*/
const goToolFlagPaths = (
flags: string[],
pathFlag: RegExp,
): { paths: string[]; fields: Set<string> } => {
const paths: string[] = [];
const fields = new Set<string>();
for (const [index, flag] of flags.entries()) {
const attached = pathFlag.exec(flag)?.[1]?.trimEnd();
if (attached === undefined) continue;
const next = flags[index + 1];
const fromNext =
attached.length === 0 && next !== undefined && !next.startsWith("-");
const argument = fromNext ? next : attached;
if (argument.length === 0) continue;
if (GO_PATH_LIST_FLAG.test(flag) && filesystemPath(argument) !== null)
continue;
paths.push(argument);
fields.add(flag);
if (fromNext) fields.add(next);
}
return { paths, fields };
};
/** A C compiler's own flags, apart from the options it forwards to the linker. */
const splitForwardedLinkerFlags = (
flags: string[],
@@ -990,9 +1025,17 @@ const goSettingPaths = (key: string, value: string): string[] => {
if (GO_COMPILER_SETTING.test(key)) return compilerPaths(value);
const pathFlag = GO_TOOL_PATH_FLAGS.get(key);
const { flags, own, external } = goToolFlags(value);
const whole =
pathFlag === undefined
? { paths: [], fields: new Set<string>() }
: goToolFlagPaths(flags, pathFlag);
return [
...flagPaths(own),
...(pathFlag === undefined ? [] : goRelativeFlagPaths(flags, pathFlag)),
...flagPaths(
own.filter(
(field) => !whole.fields.has(field.replace(GO_PACKAGE_SELECTOR, "")),
),
),
...whole.paths,
...external.flatMap(compilerPaths),
];
};