From 87af4ef47201db56b419e7fee9a38650f8b99560 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Tue, 3 Nov 2026 08:13:54 -0800 Subject: [PATCH] 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. --- .../ReleaseExposureService.test.ts | 15 ++++++ .../releaseExposure/ReleaseExposureService.ts | 47 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/application/releaseExposure/ReleaseExposureService.test.ts b/src/application/releaseExposure/ReleaseExposureService.test.ts index 1d5313c873..473a949e1e 100644 --- a/src/application/releaseExposure/ReleaseExposureService.test.ts +++ b/src/application/releaseExposure/ReleaseExposureService.test.ts @@ -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( diff --git a/src/application/releaseExposure/ReleaseExposureService.ts b/src/application/releaseExposure/ReleaseExposureService.ts index 4a668a69a6..d3450990d1 100644 --- a/src/application/releaseExposure/ReleaseExposureService.ts +++ b/src/application/releaseExposure/ReleaseExposureService.ts @@ -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 } => { + const paths: string[] = []; + const fields = new Set(); + 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() } + : 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), ]; };