[codex] Centralize subprocess env boundaries (#585)

* Centralize subprocess env boundaries

* Remove env boundary static audit test

* Tighten env boundary process launches

* Fix Windows native process launches

* Inline provider env pass-throughs
This commit is contained in:
Mohamed Boudra
2026-04-27 13:24:16 +08:00
committed by GitHub
parent 90032570ec
commit 15df680a64
40 changed files with 760 additions and 304 deletions
+35 -36
View File
@@ -18,10 +18,9 @@ import { parseGitRevParsePath, resolveGitRevParsePath } from "./git-rev-parse-pa
import { runGitCommand } from "./run-git-command.js";
import { isPaseoOwnedWorktreeCwd } from "./worktree.js";
import { readPaseoWorktreeMetadata } from "./worktree-metadata.js";
const READ_ONLY_GIT_ENV: NodeJS.ProcessEnv = {
...process.env,
const READ_ONLY_GIT_ENV = {
GIT_OPTIONAL_LOCKS: "0",
};
} as const;
const DEFAULT_PULL_REQUEST_STATUS_CACHE_TTL_MS = 30_000;
const PULL_REQUEST_STATUS_CACHE_MAX = 1_000;
@@ -178,7 +177,7 @@ async function listGitRefs(cwd: string, refPrefix: string): Promise<GitRef[]> {
"--format=%(refname)%09%(committerdate:unix)",
refPrefix,
],
{ cwd, env: READ_ONLY_GIT_ENV },
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
);
return stdout
.split("\n")
@@ -326,7 +325,7 @@ export async function resolveBranchCheckout(
const localRef = `refs/heads/${normalized}`;
const localResult = await runGitCommand(["rev-parse", "--verify", "--quiet", localRef], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
acceptExitCodes: [0, 1],
});
const hasLocal = localResult.exitCode === 0;
@@ -338,7 +337,7 @@ export async function resolveBranchCheckout(
const remoteRefPath = `refs/remotes/${remoteRef}`;
const remoteResult = await runGitCommand(["rev-parse", "--verify", "--quiet", remoteRefPath], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
acceptExitCodes: [0, 1],
});
const hasRemote = remoteResult.exitCode === 0;
@@ -399,7 +398,7 @@ async function listCheckoutFileChanges(
ignoreWhitespace,
extra: ["--name-status", ...getCheckoutDiffRefArgs(refs)],
}),
{ cwd, env: READ_ONLY_GIT_ENV },
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
);
for (const line of nameStatusOut
.split("\n")
@@ -441,7 +440,7 @@ async function listCheckoutFileChanges(
["ls-files", "--others", "--exclude-standard"],
{
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
},
);
for (const file of untrackedOut
@@ -481,7 +480,7 @@ async function readGitFileContentAtRef(
try {
const { stdout } = await runGitCommand(["show", `${ref}:${path}`], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
return stdout;
} catch {
@@ -493,7 +492,7 @@ async function tryResolveMergeBase(cwd: string, baseRef: string): Promise<string
try {
const { stdout } = await runGitCommand(["merge-base", baseRef, "HEAD"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const sha = stdout.trim();
return sha.length > 0 ? sha : null;
@@ -538,7 +537,7 @@ async function getTrackedNumstatByPath(
}),
{
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
maxOutputBytes: TRACKED_DIFF_NUMSTAT_MAX_BYTES,
acceptExitCodes: [0],
},
@@ -710,7 +709,7 @@ function isGitError(error: unknown): boolean {
async function requireGitRepo(cwd: string): Promise<void> {
try {
await runGitCommand(["rev-parse", "--git-dir"], { cwd, env: READ_ONLY_GIT_ENV });
await runGitCommand(["rev-parse", "--git-dir"], { cwd, envOverlay: READ_ONLY_GIT_ENV });
} catch {
throw new NotGitRepoError(cwd);
}
@@ -720,7 +719,7 @@ export async function getCurrentBranch(cwd: string): Promise<string | null> {
try {
const { stdout } = await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const branch = stdout.trim();
if (branch === "HEAD") {
@@ -739,7 +738,7 @@ async function getRebaseHeadBranch(cwd: string): Promise<string | null> {
try {
const { stdout } = await runGitCommand(["rev-parse", "--git-path", path], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const headName = (await readFile(resolve(cwd, stdout.trim()), "utf8")).trim();
if (headName.startsWith("refs/heads/")) {
@@ -758,7 +757,7 @@ async function getWorktreeRoot(cwd: string): Promise<string | null> {
try {
const { stdout } = await runGitCommand(["rev-parse", "--show-toplevel"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
return parseGitRevParsePath(stdout);
} catch {
@@ -769,7 +768,7 @@ async function getWorktreeRoot(cwd: string): Promise<string | null> {
export async function getMainRepoRoot(cwd: string): Promise<string> {
const { stdout: commonDirOut } = await runGitCommand(["rev-parse", "--git-common-dir"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const commonDir = resolveGitRevParsePath(cwd, commonDirOut);
if (!commonDir) {
@@ -783,7 +782,7 @@ export async function getMainRepoRoot(cwd: string): Promise<string> {
const { stdout: worktreeOut } = await runGitCommand(["worktree", "list", "--porcelain"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const worktrees = parseWorktreeList(worktreeOut);
const nonBareNonPaseo = worktrees.filter((wt) => !wt.isBare && !isPaseoWorktreePath(wt.path));
@@ -849,7 +848,7 @@ async function getWorktreePathForBranch(cwd: string, branchName: string): Promis
try {
const { stdout } = await runGitCommand(["worktree", "list", "--porcelain"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const entries = parseWorktreeList(stdout);
const ref = branchName.startsWith("refs/heads/") ? branchName : `refs/heads/${branchName}`;
@@ -946,7 +945,7 @@ async function resolveBaseRefForCwd(
async function isWorkingTreeDirty(cwd: string): Promise<boolean> {
const { stdout } = await runGitCommand(["status", "--porcelain"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
return stdout.trim().length > 0;
}
@@ -955,7 +954,7 @@ export async function getOriginRemoteUrl(cwd: string): Promise<string | null> {
try {
const { stdout } = await runGitCommand(["config", "--get", "remote.origin.url"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const url = stdout.trim();
return url.length > 0 ? url : null;
@@ -973,7 +972,7 @@ async function getGitConfigValue(cwd: string, key: string): Promise<string | nul
try {
const { stdout } = await runGitCommand(["config", "--get", key], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const value = stdout.trim();
return value.length > 0 ? value : null;
@@ -1019,7 +1018,7 @@ export async function resolveAbsoluteGitDir(cwd: string): Promise<string | null>
try {
const { stdout } = await runGitCommand(["rev-parse", "--absolute-git-dir"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const gitDir = stdout.trim();
return gitDir.length > 0 ? gitDir : null;
@@ -1061,7 +1060,7 @@ export async function resolveRepositoryDefaultBranch(repoRoot: string): Promise<
["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"],
{
cwd: repoRoot,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
},
);
const ref = stdout.trim();
@@ -1075,7 +1074,7 @@ export async function resolveRepositoryDefaultBranch(repoRoot: string): Promise<
try {
await runGitCommand(["show-ref", "--verify", "--quiet", `refs/heads/${localName}`], {
cwd: repoRoot,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
return localName;
} catch {
@@ -1088,7 +1087,7 @@ export async function resolveRepositoryDefaultBranch(repoRoot: string): Promise<
const { stdout } = await runGitCommand(["branch", "--format=%(refname:short)"], {
cwd: repoRoot,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const branches = new Set(
stdout
@@ -1137,7 +1136,7 @@ function normalizeComparisonBaseRefName(input: string): ComparisonBaseRefName {
async function doesGitRefExist(cwd: string, fullRef: string): Promise<boolean> {
const result = await runGitCommand(["show-ref", "--verify", "--quiet", fullRef], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
acceptExitCodes: [0, 1],
});
return result.exitCode === 0;
@@ -1182,7 +1181,7 @@ async function resolveMostAheadBaseRef(cwd: string, normalizedBaseRef: string):
const { stdout } = await runGitCommand(
["rev-list", "--left-right", "--count", `${normalizedBaseRef}...origin/${normalizedBaseRef}`],
{ cwd, env: READ_ONLY_GIT_ENV },
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
);
const [localOnlyRaw, originOnlyRaw] = stdout.trim().split(/\s+/);
const localOnly = Number.parseInt(localOnlyRaw ?? "0", 10);
@@ -1209,7 +1208,7 @@ async function getAheadBehind(
const comparisonBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef);
const { stdout } = await runGitCommand(
["rev-list", "--left-right", "--count", `${comparisonBaseRef}...${currentBranch}`],
{ cwd, env: READ_ONLY_GIT_ENV },
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
);
const [behindRaw, aheadRaw] = stdout.trim().split(/\s+/);
const behind = Number.parseInt(behindRaw ?? "0", 10);
@@ -1227,7 +1226,7 @@ async function getAheadOfOrigin(cwd: string, currentBranch: string): Promise<num
try {
const { stdout } = await runGitCommand(
["rev-list", "--count", `origin/${currentBranch}..${currentBranch}`],
{ cwd, env: READ_ONLY_GIT_ENV },
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
);
const count = Number.parseInt(stdout.trim(), 10);
return Number.isNaN(count) ? null : count;
@@ -1235,7 +1234,7 @@ async function getAheadOfOrigin(cwd: string, currentBranch: string): Promise<num
try {
const { stdout } = await runGitCommand(["rev-list", "--count", currentBranch], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const count = Number.parseInt(stdout.trim(), 10);
return Number.isNaN(count) ? null : count;
@@ -1252,7 +1251,7 @@ async function getBehindOfOrigin(cwd: string, currentBranch: string): Promise<nu
try {
const { stdout } = await runGitCommand(
["rev-list", "--count", `${currentBranch}..origin/${currentBranch}`],
{ cwd, env: READ_ONLY_GIT_ENV },
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
);
const count = Number.parseInt(stdout.trim(), 10);
return Number.isNaN(count) ? null : count;
@@ -1396,7 +1395,7 @@ async function getUntrackedDiffText(
}),
{
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
maxOutputBytes: PER_FILE_DIFF_MAX_BYTES,
acceptExitCodes: [0, 1],
},
@@ -1528,7 +1527,7 @@ async function getCheckoutShortstatUncached(
try {
const { stdout: mergeBaseOut } = await runGitCommand(["merge-base", "HEAD", comparisonRef], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
const mergeBase = mergeBaseOut.trim();
if (!mergeBase) {
@@ -1537,7 +1536,7 @@ async function getCheckoutShortstatUncached(
const { stdout } = await runGitCommand(["diff", "--shortstat", mergeBase], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
return parseCheckoutShortstat(stdout);
} catch {
@@ -1871,7 +1870,7 @@ export async function getCheckoutDiff(
}),
{
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
maxOutputBytes: TOTAL_DIFF_MAX_BYTES,
},
);
@@ -2113,7 +2112,7 @@ export async function mergeFromBase(
if (requireCleanTarget) {
const { stdout } = await runGitCommand(["status", "--porcelain"], {
cwd,
env: READ_ONLY_GIT_ENV,
envOverlay: READ_ONLY_GIT_ENV,
});
if (stdout.trim().length > 0) {
throw new Error("Working directory has uncommitted changes.");