diff --git a/src/application/releaseExposure/ReleaseExposureService.test.ts b/src/application/releaseExposure/ReleaseExposureService.test.ts index 69197e6446..be719469bd 100644 --- a/src/application/releaseExposure/ReleaseExposureService.test.ts +++ b/src/application/releaseExposure/ReleaseExposureService.test.ts @@ -1323,6 +1323,42 @@ describe("ReleaseExposureService Go Windows paths", () => { }); }); +describe("ReleaseExposureService Go relative flag paths", () => { + const goModule = (settings: { key: string; value: string }[]) => ({ + path: "github.com/acme/app", + main: null, + dependencies: [], + settings, + unparsed_lines: [], + unparsed_line_bytes_base64: [], + complete: true, + }); + + it("keeps relative paths given to compiler path flags, attached or not", async () => { + const report = await goReport( + goModule([ + { key: "CGO_CFLAGS", value: "-Iprivate-sdk -O2 -isystem vendor/sdk" }, + { key: "CGO_LDFLAGS", value: "-Llibs -lssl" }, + ]), + ); + const check = report.checks.find((item) => item.id === "go.build_paths"); + expect(check?.status).toBe("exposed"); + expect(check?.observation.samples).toEqual([ + "private-sdk", + "vendor/sdk", + "libs", + ]); + }); + + it("does not read Go linker arguments as relative paths", async () => { + const report = await goReport( + goModule([{ key: "-ldflags", value: "-B gobuildid -T 0x1000 -s -w" }]), + ); + const check = report.checks.find((item) => item.id === "go.build_paths"); + expect(check?.status).toBe("not_observed"); + }); +}); + describe("ReleaseExposureService Go paths", () => { it("keeps Go paths with spaces and attached compiler flags whole", async () => { const report = await goReport({ diff --git a/src/application/releaseExposure/ReleaseExposureService.ts b/src/application/releaseExposure/ReleaseExposureService.ts index ec6feacf23..bfc2c4e231 100644 --- a/src/application/releaseExposure/ReleaseExposureService.ts +++ b/src/application/releaseExposure/ReleaseExposureService.ts @@ -784,19 +784,46 @@ const GO_FLAG_FIELD = /(?:[^\s"']|"[^"]*"|'[^']*')+/gu; const GO_FLAG_PATH = /(?:^|[\s=,])(?:-(?:idirafter|imacros|include|iprefix|iquote|isysroot|isystem|[BFILT]))?((?:\.{1,2}[\\/]|\/|[A-Za-z]:[\\/]|\\{2}[^\\\s=,]+\\)[^=,]+)/gu; -/** Paths in one flag value, with a quoted path kept whole up to the next flag. */ -const goFlagPaths = (value: string): string[] => +/** + * Settings read by a C compiler, where the flags below take a directory or file. + * Go's own tools reuse those letters: `-ldflags` takes `-B gobuildid` and + * `-T 0x1000`, so their relative arguments are not paths. + */ +const GO_COMPILER_SETTING = /^(?:CGO_[A-Z]+FLAGS|-gccgoflags)$/u; +const GO_COMPILER_PATH_FLAG = + /^-(?:idirafter|imacros|include|iprefix|iquote|isysroot|isystem|[BFILT])=?(.*)$/su; + +/** Flags in one value, with a quoted path kept whole up to the next flag. */ +const goFlags = (value: string): string[] => [...value.matchAll(GO_FLAG_FIELD)].flatMap(([field]) => - field - .replace(/["']/gu, "") - .split(/\s+(?=-)/u) - .flatMap((flag) => - [...flag.matchAll(GO_FLAG_PATH)].flatMap(([, path]) => - path === undefined ? [] : [path.trimEnd()], - ), - ), + field.replace(/["']/gu, "").split(/\s+(?=-)/u), ); +const goFlagPaths = (value: string): string[] => + goFlags(value).flatMap((flag) => + [...flag.matchAll(GO_FLAG_PATH)].flatMap(([, path]) => + path === undefined ? [] : [path.trimEnd()], + ), + ); + +/** + * 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[] => + goFlags(value).flatMap((flag, index, flags) => { + const attached = GO_COMPILER_PATH_FLAG.exec(flag)?.[1]?.trimEnd(); + if (attached === undefined) return []; + const next = flags[index + 1]; + const argument = + attached.length > 0 || next?.startsWith("-") === true ? attached : next; + return argument === undefined || + argument.length === 0 || + filesystemPath(argument) !== null + ? [] + : [argument]; + }); + const goPaths = ( module: NonNullable["module"], ): string[] => { @@ -815,6 +842,8 @@ 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); } return [...paths]; };