From e13691160535855cd4df9701c3b704e595d4e395 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Mon, 2 Nov 2026 03:31:38 -0800 Subject: [PATCH] fix(audit): read relative -L paths and split flag arguments The relative-path reader handled C compiler settings only, so a directory kept through Go's own linker flag, such as -ldflags='-L private-sdk -s -w', reported go.build_paths as not_observed. A quoted flag and argument also reached it as one field. The separating space stayed on the argument, which reported rooted paths twice and kept a leading space on relative ones. Give -ldflags its own pattern that matches only -L, so -B gobuildid and -T 0x1000 stay ignored, and consume the separator in the flag patterns. --- .../ReleaseExposureService.test.ts | 25 +++++++++++++++++++ .../releaseExposure/ReleaseExposureService.ts | 22 ++++++++++++---- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/application/releaseExposure/ReleaseExposureService.test.ts b/src/application/releaseExposure/ReleaseExposureService.test.ts index be719469bd..3dee9a364f 100644 --- a/src/application/releaseExposure/ReleaseExposureService.test.ts +++ b/src/application/releaseExposure/ReleaseExposureService.test.ts @@ -1357,6 +1357,31 @@ describe("ReleaseExposureService Go relative flag paths", () => { const check = report.checks.find((item) => item.id === "go.build_paths"); expect(check?.status).toBe("not_observed"); }); + + it.each([ + ["-L private-sdk -s -w", "private-sdk"], + [`"-L private-sdk -s -w"`, "private-sdk"], + ["-L=private-sdk -s -w", "private-sdk"], + ["all=-L private-sdk -s -w", "private-sdk"], + ])("keeps the relative linker directory in %s", async (value, path) => { + const report = await goReport( + goModule([{ key: "-ldflags", value: `-B gobuildid ${value}` }]), + ); + const check = report.checks.find((item) => item.id === "go.build_paths"); + expect(check?.status).toBe("exposed"); + expect(check?.observation.samples).toEqual([path]); + }); + + it.each([ + [`"-isystem /opt/my sdk/include"`, "/opt/my sdk/include"], + [`"-I private sdk/include"`, "private sdk/include"], + ])("reports %s once, without the separating space", async (value, path) => { + const report = await goReport(goModule([{ key: "CGO_CFLAGS", value }])); + const check = report.checks.find((item) => item.id === "go.build_paths"); + expect(check?.status).toBe("exposed"); + expect(check?.observation.samples).toEqual([path]); + expect(check?.observation.counts.paths).toBe(1); + }); }); describe("ReleaseExposureService Go paths", () => { diff --git a/src/application/releaseExposure/ReleaseExposureService.ts b/src/application/releaseExposure/ReleaseExposureService.ts index d6fd7f2673..c2b5ff8d21 100644 --- a/src/application/releaseExposure/ReleaseExposureService.ts +++ b/src/application/releaseExposure/ReleaseExposureService.ts @@ -798,7 +798,17 @@ const GO_FLAG_PATH = */ const GO_COMPILER_SETTING = /^(?:CGO_[A-Z]+FLAGS|-gccgoflags)$/u; const GO_COMPILER_PATH_FLAG = - /^-(?:idirafter|imacros|include|iprefix|iquote|isysroot|isystem|[BFILT])=?(.*)$/su; + /^-(?:idirafter|imacros|include|iprefix|iquote|isysroot|isystem|[BFILT])=?\s*(.*)$/su; +/** + * `go tool link` takes a library directory as `-L dir` or `-L=dir`, never + * attached, and `go build` may prefix the flags with `pattern=`. + */ +const GO_LINKER_PATH_FLAG = /^(?:[^\s=-][^\s=]*=)?-{1,2}L(?:=|\s+|$)(.*)$/su; + +const goPathFlag = (key: string): RegExp | null => { + if (GO_COMPILER_SETTING.test(key)) return GO_COMPILER_PATH_FLAG; + return key === "-ldflags" ? GO_LINKER_PATH_FLAG : null; +}; /** Flags in one value, with a quoted path kept whole up to the next flag. */ const goFlags = (value: string): string[] => @@ -817,9 +827,9 @@ const goFlagPaths = (value: string): string[] => * A path flag's relative argument, attached or in the next field. A rooted * argument belongs to `goFlagPaths`, which keeps its `=` and `,` structure. */ -const goCompilerFlagPaths = (value: string): string[] => +const goRelativeFlagPaths = (value: string, pathFlag: RegExp): string[] => goFlags(value).flatMap((flag, index, flags) => { - const attached = GO_COMPILER_PATH_FLAG.exec(flag)?.[1]?.trimEnd(); + const attached = pathFlag.exec(flag)?.[1]?.trimEnd(); if (attached === undefined) return []; const next = flags[index + 1]; const argument = @@ -849,8 +859,10 @@ const goPaths = ( for (const setting of module.settings) { if (!GO_FLAG_SETTING.test(setting.key)) continue; for (const path of goFlagPaths(setting.value)) add(path); - if (GO_COMPILER_SETTING.test(setting.key)) - for (const path of goCompilerFlagPaths(setting.value)) paths.add(path); + const pathFlag = goPathFlag(setting.key); + if (pathFlag !== null) + for (const path of goRelativeFlagPaths(setting.value, pathFlag)) + paths.add(path); } return [...paths]; };