diff --git a/src/scripts/__tests__/codex-native-hook.test.ts b/src/scripts/__tests__/codex-native-hook.test.ts
index 05f96ed2..c38c0c9f 100644
--- a/src/scripts/__tests__/codex-native-hook.test.ts
+++ b/src/scripts/__tests__/codex-native-hook.test.ts
@@ -756,21 +756,59 @@ const priorTeamEnv = new Map<
string | undefined
>();
-beforeEach(() => {
+const EXECUTABLE_TRUST_TEST_NAMES = new Set([
+ "issue #3239 permits command-specific safe operations during Autopilot deep-interview without relaxing mutation guards",
+ "allows canonical leader deep-interview artifact and state writes while blocking implementation Bash writes",
+ "allows Ralplan read-only inspection and planning artifact writes while blocking implementation targets",
+ "blocks operator-adjacent transport bypass forms while still allowing a safe direct-wrapper state write",
+ "fail-closed: escaped-quote and ANSI-C quoted redirects to source stay blocked while legit quoted data and nested shells behave (#3119)",
+ "uses hook-native agent_id as child provenance without borrowing Team or legacy identity",
+ "blocks common Bash file mutations in Main-root conductor states unless they target workflow metadata",
+ "allows conductor sed/perl metadata edits while blocking non-metadata targets",
+]);
+const CONTROLLED_EXECUTABLE_NAMES = [
+ "bash", "cat", "chmod", "cp", "curl", "cut", "env", "find", "gh", "git", "grep",
+ "head", "install", "ln", "ls", "mkdir", "mv", "node", "npm", "perl", "python",
+ "python3", "readlink", "realpath", "rm", "rsync", "sed", "sh", "sudo", "tail", "tee",
+ "time", "touch", "truncate", "wget", "xargs", "zsh",
+];
+let priorExecutableTrustPath: string | undefined;
+let executableTrustFixtureDir: string | undefined;
+
+beforeEach(async (context) => {
priorTeamEnv.clear();
for (const key of TEAM_ENV_KEYS) {
priorTeamEnv.set(key, process.env[key]);
delete process.env[key];
}
+ priorExecutableTrustPath = undefined;
+ executableTrustFixtureDir = undefined;
+ if (EXECUTABLE_TRUST_TEST_NAMES.has(context.name)) {
+ priorExecutableTrustPath = process.env.PATH;
+ executableTrustFixtureDir = await mkdtemp(join(tmpdir(), "omx-native-hook-executable-trust-"));
+ for (const name of CONTROLLED_EXECUTABLE_NAMES) {
+ const executable = join(executableTrustFixtureDir, name);
+ await writeFile(executable, "controlled executable identity fixture\n", "utf-8");
+ await chmod(executable, 0o755);
+ }
+ process.env.PATH = `${executableTrustFixtureDir}:/usr/bin:/bin`;
+ }
});
-afterEach(() => {
+afterEach(async (context) => {
for (const key of TEAM_ENV_KEYS) {
const value = priorTeamEnv.get(key);
if (typeof value === "string") process.env[key] = value;
else delete process.env[key];
}
priorTeamEnv.clear();
+ if (EXECUTABLE_TRUST_TEST_NAMES.has(context.name)) {
+ if (typeof priorExecutableTrustPath === "string") process.env.PATH = priorExecutableTrustPath;
+ else delete process.env.PATH;
+ if (executableTrustFixtureDir) await rm(executableTrustFixtureDir, { recursive: true, force: true });
+ }
+ priorExecutableTrustPath = undefined;
+ executableTrustFixtureDir = undefined;
});
describe("codex native hook config", () => {
diff --git a/src/scripts/codex-native-hook.ts b/src/scripts/codex-native-hook.ts
index 9a13f337..90e7c5b7 100644
--- a/src/scripts/codex-native-hook.ts
+++ b/src/scripts/codex-native-hook.ts
@@ -16494,6 +16494,7 @@ function conductorExecutableHasTrustedIdentity(
seen = new Set<string>(),
): boolean {
if (conductorExecutableHasTrustedCurrentNodeRuntimeIdentity(commandName, commandPath)) return true;
+ if (conductorExecutableHasTrustedInheritedPathIdentity(commandName, commandPath, rootCwd)) return true;
if (!conductorExecutableHasTrustedSystemIdentity(commandPath, rootCwd)) return false;
if (depth >= CONDUCTOR_BASH_MAX_NESTING_DEPTH) return false;
let canonical: string;
@@ -16507,6 +16508,38 @@ function conductorExecutableHasTrustedIdentity(
return conductorTrustedScriptInterpreterIsSafe(canonical, state, rootCwd, depth, seen);
}
+function conductorExecutableHasTrustedInheritedPathIdentity(
+ commandName: string,
+ commandPath: string,
+ rootCwd: string,
+): boolean {
+ const inheritedPath = process.env.PATH;
+ if (!inheritedPath || !commandName) return false;
+ try {
+ const root = realpathSync(resolve(rootCwd));
+ const lexicalCommandPath = resolve(commandPath);
+ if (lexicalCommandPath === root || lexicalCommandPath.startsWith(`${root}/`)) return false;
+
+ for (const entry of inheritedPath.split(":")) {
+ if (!entry || !isAbsolute(entry)) return false;
+ const candidate = join(entry, commandName);
+ try {
+ accessSync(candidate, fsConstants.X_OK);
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
+ return false;
+ }
+ if (resolve(candidate) !== lexicalCommandPath) return false;
+ const canonicalCandidate = realpathSync(candidate);
+ if (canonicalCandidate === root || canonicalCandidate.startsWith(`${root}/`)) return false;
+ return statSync(canonicalCandidate).isFile();
+ }
+ } catch {
+ return false;
+ }
+ return false;
+}
+
function conductorExecutableHasTrustedSystemIdentity(commandPath: string, rootCwd: string): boolean {
try {
const lexical = resolve(commandPath);
Created
July 27, 2026 22:35
-
-
Save arikon/b827b16ad5e6aea2d15a4e9f9077b703 to your computer and use it in GitHub Desktop.
Patch for Yeachan-Heo/oh-my-codex#3322
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment