Skip to content

Commit b4cdd92

Browse files
authored
fix(codex): avoid guardian review for local models (#88630)
* fix(codex): avoid guardian review for local models * fix(codex): route app-server auto exec review * fix(codex): make guardian requirements provider-aware * fix(codex): block unrouted bound approvals * fix(channels): satisfy ingress queue lint * fix(codex): use local-model policy for side forks * fix(extensions): satisfy ingress lint * fix(codex): require trusted exec reviewer model * fix(exec): share control command approval guards * fix(codex): fail closed for unknown guardian model provider * fix(codex): reject custom exec reviewer endpoints * fix(codex): preserve bound providers on app-server reuse * fix(codex): prefer qualified app-server model providers * fix(codex): preserve guardian on model control switches * fix(codex): retain local providers across model switches * fix(codex): distrust aliased reviewer model refs * fix(codex): preserve providers after thread rotation * fix(codex): clear stale providers on qualified model switches * fix(codex): prefer qualified models over legacy providers * fix(codex): validate reviewer trust before auto approvals * fix(codex): recompute reviewer policy after binding rotation * fix(codex): normalize reviewer aliases before trust checks * fix(codex): retain bound providers for slashed local models * fix(codex): normalize provider trust checks for exec review * fix(codex): ignore stale bindings for explicit providers * fix(codex): share trusted reviewer endpoint policy * fix(codex): keep network approvals on plugin path * fix(codex): route provider-qualified model refs * fix(codex): reject blank masked OpenAI base overrides * fix(codex): scope exec reviewer alias trust * fix(codex): distrust exec reviewer transport overrides
1 parent 5a0b952 commit b4cdd92

26 files changed

Lines changed: 3864 additions & 256 deletions

extensions/codex/src/app-server/app-server-policy.test.ts

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Codex tests cover app server policy plugin behavior.
22
import { describe, expect, it } from "vitest";
3-
import { resolveCodexAppServerForOpenClawToolPolicy } from "./app-server-policy.js";
3+
import {
4+
resolveCodexAppServerForModelProvider,
5+
resolveCodexAppServerForOpenClawToolPolicy,
6+
} from "./app-server-policy.js";
47
import { readCodexPluginConfig, resolveCodexAppServerRuntimeOptions } from "./config.js";
58

69
describe("Codex app-server policy", () => {
@@ -66,4 +69,143 @@ describe("Codex app-server policy", () => {
6669
expect(explicitEnv.approvalPolicy).toBe("never");
6770
expect(explicitRequirements.approvalPolicy).toBe("never");
6871
});
72+
73+
it("keeps model-backed reviewers for explicit OpenAI model providers", () => {
74+
const appServer = resolveCodexAppServerRuntimeOptions({
75+
env: {},
76+
requirementsToml: null,
77+
execMode: "auto",
78+
modelProvider: "openai",
79+
});
80+
81+
expect(
82+
resolveCodexAppServerForModelProvider({
83+
appServer,
84+
provider: "codex",
85+
model: "openai/gpt-5.5",
86+
}).approvalsReviewer,
87+
).toBe("auto_review");
88+
expect(
89+
resolveCodexAppServerForModelProvider({
90+
appServer,
91+
provider: "codex",
92+
model: "gpt-5.5",
93+
}).approvalsReviewer,
94+
).toBe("user");
95+
expect(
96+
resolveCodexAppServerForModelProvider({ appServer, provider: "openai" }).approvalsReviewer,
97+
).toBe("auto_review");
98+
});
99+
100+
it("uses human approval for OpenAI-compatible custom endpoints", () => {
101+
const appServer = resolveCodexAppServerRuntimeOptions({
102+
env: {},
103+
requirementsToml: null,
104+
execMode: "auto",
105+
modelProvider: "openai",
106+
model: "gpt-5.5",
107+
config: {
108+
models: {
109+
providers: {
110+
openai: {
111+
baseUrl: "http://localhost:8080/v1",
112+
models: [],
113+
},
114+
},
115+
},
116+
},
117+
});
118+
119+
expect(appServer.approvalsReviewer).toBe("user");
120+
expect(
121+
resolveCodexAppServerForModelProvider({
122+
appServer,
123+
provider: "openai",
124+
model: "gpt-5.5",
125+
config: {
126+
models: {
127+
providers: {
128+
openai: {
129+
baseUrl: "http://localhost:8080/v1",
130+
models: [],
131+
},
132+
},
133+
},
134+
},
135+
}).approvalsReviewer,
136+
).toBe("user");
137+
});
138+
139+
it("uses human approval instead of Codex Guardian for custom model providers", () => {
140+
const appServer = resolveCodexAppServerRuntimeOptions({
141+
env: {},
142+
requirementsToml: null,
143+
execMode: "auto",
144+
modelProvider: "openai",
145+
});
146+
147+
const resolved = resolveCodexAppServerForModelProvider({
148+
appServer,
149+
provider: "lmstudio",
150+
});
151+
const vendorPrefixedModel = resolveCodexAppServerForModelProvider({
152+
appServer,
153+
provider: "openrouter",
154+
model: "openai/gpt-5.5",
155+
});
156+
157+
expect(appServer.approvalsReviewer).toBe("auto_review");
158+
expect(resolved.approvalPolicy).toBe("on-request");
159+
expect(resolved.sandbox).toBe("workspace-write");
160+
expect(resolved.approvalsReviewer).toBe("user");
161+
expect(vendorPrefixedModel.approvalsReviewer).toBe("user");
162+
});
163+
164+
it("infers custom providers from provider-qualified model refs", () => {
165+
const appServer = resolveCodexAppServerRuntimeOptions({
166+
env: {},
167+
requirementsToml: null,
168+
execMode: "auto",
169+
});
170+
171+
expect(
172+
resolveCodexAppServerForModelProvider({
173+
appServer,
174+
model: "lmstudio/local-model",
175+
}).approvalsReviewer,
176+
).toBe("user");
177+
});
178+
179+
it("uses provider-qualified model refs to override broad native provider wrappers", () => {
180+
const appServer = resolveCodexAppServerRuntimeOptions({
181+
env: {},
182+
requirementsToml: null,
183+
execMode: "auto",
184+
});
185+
186+
expect(
187+
resolveCodexAppServerForModelProvider({
188+
appServer,
189+
provider: "codex",
190+
model: "lmstudio/local-model",
191+
}).approvalsReviewer,
192+
).toBe("user");
193+
});
194+
195+
it("downgrades legacy guardian_subagent for custom model providers", () => {
196+
const appServer = resolveCodexAppServerRuntimeOptions({
197+
env: {},
198+
requirementsToml: null,
199+
pluginConfig: {
200+
appServer: {
201+
mode: "guardian",
202+
approvalsReviewer: "guardian_subagent",
203+
},
204+
},
205+
});
206+
207+
expect(
208+
resolveCodexAppServerForModelProvider({ appServer, provider: "local" }).approvalsReviewer,
209+
).toBe("user");
210+
});
69211
});

extensions/codex/src/app-server/app-server-policy.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
* Policy promotion for Codex app-server runs that can safely use OpenClaw tool
33
* approvals.
44
*/
5-
import type {
6-
CodexAppServerRuntimeOptions,
7-
CodexPluginConfig,
8-
OpenClawExecPolicyForCodexAppServer,
5+
import {
6+
canUseCodexModelBackedApprovalsReviewerForModel,
7+
type CodexAppServerRuntimeOptions,
8+
type CodexPluginConfig,
9+
type OpenClawExecPolicyForCodexAppServer,
910
} from "./config.js";
1011

1112
/**
@@ -44,6 +45,35 @@ export function resolveCodexAppServerForOpenClawToolPolicy(params: {
4445
};
4546
}
4647

48+
export function resolveCodexAppServerForModelProvider(params: {
49+
appServer: CodexAppServerRuntimeOptions;
50+
provider?: string;
51+
model?: string;
52+
config?: Parameters<typeof canUseCodexModelBackedApprovalsReviewerForModel>[0]["config"];
53+
env?: NodeJS.ProcessEnv;
54+
agentDir?: string;
55+
codexConfigToml?: string | null;
56+
}): CodexAppServerRuntimeOptions {
57+
const explicitProvider = normalizeModelBackedReviewerProvider(params.provider);
58+
if (
59+
!isCodexModelBackedApprovalsReviewer(params.appServer.approvalsReviewer) ||
60+
canUseCodexModelBackedApprovalsReviewerForModel({
61+
modelProvider: explicitProvider,
62+
model: params.model,
63+
config: params.config,
64+
env: params.env,
65+
agentDir: params.agentDir,
66+
codexConfigToml: params.codexConfigToml,
67+
})
68+
) {
69+
return params.appServer;
70+
}
71+
return {
72+
...params.appServer,
73+
approvalsReviewer: "user",
74+
};
75+
}
76+
4777
function isCodexAppServerPolicyMode(value: unknown): boolean {
4878
return value === "guardian" || value === "yolo";
4979
}
@@ -53,3 +83,12 @@ function isCodexAppServerApprovalPolicy(value: unknown): boolean {
5383
value === "never" || value === "on-request" || value === "on-failure" || value === "untrusted"
5484
);
5585
}
86+
87+
function isCodexModelBackedApprovalsReviewer(value: string): boolean {
88+
return value === "auto_review" || value === "guardian_subagent";
89+
}
90+
91+
function normalizeModelBackedReviewerProvider(provider: string | undefined): string | undefined {
92+
const normalized = provider?.trim().toLowerCase();
93+
return normalized || undefined;
94+
}

0 commit comments

Comments
 (0)