fix(audit): report relative paths given to compiler path flags

go.build_paths only kept rooted paths, so retained build settings like
CGO_CFLAGS=-Iprivate-sdk read not_observed even though the setting names
a directory.

Take the relative argument of a C compiler path flag, attached or in the
next field. Scope this to compiler settings, because Go's own tools use
the same letters for non-paths such as -B gobuildid and -T 0x1000.
This commit is contained in:
Matt Van Horn committed 2026-11-02 01:08:50 -08:00
1 parent 236f2fb51a
commit ab06b3e2ec
2 files changed
+75 -10

No files matched your search

@@ -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({
@@ -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<GoBinary["build_info"]>["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];
};