Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions scripts/production-environment-governance-audit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,25 @@ export function createGhSubprocessEnvironment(sourceEnvironment = process.env) {
return childEnvironment;
}

/**
* Decode GitHub CLI output without allowing replacement characters to convert
* malformed remote evidence into a different, parseable JSON document.
*
* @param {Uint8Array} value Raw subprocess bytes.
* @param {string} [label="output"] Diagnostic stream label.
* @returns {string} Exact UTF-8 text.
*/
export function decodeGhOutput(value, label = "output") {
try {
return new TextDecoder("utf-8", { fatal: true }).decode(value);
} catch {
throw new Error(`GitHub CLI returned invalid UTF-8 in ${label}.`);
}
}

function runGh(args) {
const childEnvironment = createGhSubprocessEnvironment();
const completed = spawnSync("gh", args, {
encoding: "utf8",
env: childEnvironment,
maxBuffer: MAX_GH_OUTPUT_BYTES,
shell: false,
Expand All @@ -55,11 +70,18 @@ function runGh(args) {
throw new Error(`GitHub CLI could not start: ${bound(detail)}`);
}
if (completed.status !== 0) {
const rawDetail = completed.stderr || completed.stdout || `exit ${completed.status}`;
let rawDetail;
if (completed.stderr?.length) {
rawDetail = decodeGhOutput(completed.stderr, "stderr");
} else if (completed.stdout?.length) {
rawDetail = decodeGhOutput(completed.stdout, "stdout");
} else {
rawDetail = `exit ${completed.status}`;
}
const detail = redactSensitiveValue(rawDetail, [childEnvironment.GH_TOKEN]);
throw new Error(`GitHub CLI failed: ${bound(detail)}`);
}
return completed.stdout.trim();
return decodeGhOutput(completed.stdout, "stdout").trim();
}

function collectEnvironment(repository) {
Expand Down
78 changes: 78 additions & 0 deletions test/production-environment-governance-utf8.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { describe, expect, it } from "vitest";

import {
decodeGhOutput,
runGh,
} from "../scripts/production-environment-governance-audit.mjs";

function failureMessage(action: () => unknown) {
try {
action();
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
throw new Error("expected action to fail");
}

describe("production environment governance GitHub CLI UTF-8 boundary", () => {
it("rejects malformed UTF-8 instead of replacement-decoding production evidence", () => {
expect(() =>
decodeGhOutput(
Uint8Array.from([0x7b, 0x22, 0xff, 0x22, 0x7d]),
"stdout",
),
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
).toThrow("GitHub CLI returned invalid UTF-8 in stdout.");
});

it("decodes valid UTF-8 bytes exactly", () => {
const bytes = new TextEncoder().encode('{"name":"production"}\n');
expect(decodeGhOutput(bytes, "stdout")).toBe('{"name":"production"}\n');
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
});

it("prefers stderr and redacts GH_TOKEN on the real runGh failure path", () => {
const token = "read-only-secret-token";
const message = failureMessage(() =>
runGh(["api", "example"], {
sourceEnvironment: { PATH: "/usr/bin:/bin", GH_TOKEN: token },
spawnSyncImpl: () => ({
status: 1,
stdout: Buffer.from(`stdout exposed ${token}`, "utf8"),
stderr: Buffer.from(`stderr exposed ${token}`, "utf8"),
}),
}),
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
);

expect(message).toBe("GitHub CLI failed: stderr exposed [REDACTED]");

Check failure on line 45 in test/production-environment-governance-utf8.test.ts

View workflow job for this annotation

GitHub Actions / verify

test/production-environment-governance-utf8.test.ts > production environment governance GitHub CLI UTF-8 boundary > prefers stderr and redacts GH_TOKEN on the real runGh failure path

AssertionError: expected '(0 , __vite_ssr_import_1__.runGh) is …' to be 'GitHub CLI failed: stderr exposed [RE…' // Object.is equality Expected: "GitHub CLI failed: stderr exposed [REDACTED]" Received: "(0 , __vite_ssr_import_1__.runGh) is not a function" ❯ test/production-environment-governance-utf8.test.ts:45:21
expect(message).not.toContain(token);
expect(message).not.toContain("stdout exposed");
});

it("falls back to stdout and redacts GH_TOKEN when stderr is empty", () => {
const token = "read-only-secret-token";
const message = failureMessage(() =>
runGh(["api", "example"], {
sourceEnvironment: { PATH: "/usr/bin:/bin", GH_TOKEN: token },
spawnSyncImpl: () => ({
status: 1,
stdout: Buffer.from(`stdout exposed ${token}`, "utf8"),
stderr: Buffer.alloc(0),
}),
}),
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
);

expect(message).toBe("GitHub CLI failed: stdout exposed [REDACTED]");

Check failure on line 63 in test/production-environment-governance-utf8.test.ts

View workflow job for this annotation

GitHub Actions / verify

test/production-environment-governance-utf8.test.ts > production environment governance GitHub CLI UTF-8 boundary > falls back to stdout and redacts GH_TOKEN when stderr is empty

AssertionError: expected '(0 , __vite_ssr_import_1__.runGh) is …' to be 'GitHub CLI failed: stdout exposed [RE…' // Object.is equality Expected: "GitHub CLI failed: stdout exposed [REDACTED]" Received: "(0 , __vite_ssr_import_1__.runGh) is not a function" ❯ test/production-environment-governance-utf8.test.ts:63:21
expect(message).not.toContain(token);
});

it.each([
["stderr", Buffer.from([0xff]), Buffer.from("valid stdout", "utf8")],
["stdout", Buffer.alloc(0), Buffer.from([0xff])],
] as const)("fails closed on malformed %s bytes from runGh", (label, stderr, stdout) => {
expect(() =>
runGh(["api", "example"], {
sourceEnvironment: { PATH: "/usr/bin:/bin", GH_TOKEN: "secret" },
spawnSyncImpl: () => ({ status: 1, stdout, stderr }),
}),
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
).toThrow(`GitHub CLI returned invalid UTF-8 in ${label}.`);

Check failure on line 76 in test/production-environment-governance-utf8.test.ts

View workflow job for this annotation

GitHub Actions / verify

test/production-environment-governance-utf8.test.ts > production environment governance GitHub CLI UTF-8 boundary > fails closed on malformed stdout bytes from runGh

AssertionError: expected [Function] to throw error including 'GitHub CLI returned invalid UTF-8 in …' but got '(0 , __vite_ssr_import_1__.runGh) is …' Expected: "GitHub CLI returned invalid UTF-8 in stdout." Received: "(0 , __vite_ssr_import_1__.runGh) is not a function" ❯ test/production-environment-governance-utf8.test.ts:76:7

Check failure on line 76 in test/production-environment-governance-utf8.test.ts

View workflow job for this annotation

GitHub Actions / verify

test/production-environment-governance-utf8.test.ts > production environment governance GitHub CLI UTF-8 boundary > fails closed on malformed stderr bytes from runGh

AssertionError: expected [Function] to throw error including 'GitHub CLI returned invalid UTF-8 in …' but got '(0 , __vite_ssr_import_1__.runGh) is …' Expected: "GitHub CLI returned invalid UTF-8 in stderr." Received: "(0 , __vite_ssr_import_1__.runGh) is not a function" ❯ test/production-environment-governance-utf8.test.ts:76:7
});
});
Comment thread
seonghobae marked this conversation as resolved.
Resolved
Loading