fix: pass native relay config to tool policies

This commit is contained in:
Eva
2026-05-02 00:26:29 +07:00
committed by Josh Lehman
parent 5d1607de06
commit 4d010aa121
3 changed files with 105 additions and 0 deletions

View File

@@ -535,6 +535,7 @@ export async function runCodexAppServerAttempt(
agentId: sessionAgentId,
sessionId: params.sessionId,
sessionKey: sandboxSessionKey,
config: params.config,
runId: params.runId,
signal: runAbortController.signal,
});
@@ -1376,6 +1377,7 @@ function createCodexNativeHookRelay(params: {
agentId: string | undefined;
sessionId: string;
sessionKey: string | undefined;
config: EmbeddedRunAttemptParams["config"];
runId: string;
signal: AbortSignal;
}): NativeHookRelayRegistrationHandle | undefined {
@@ -1392,6 +1394,7 @@ function createCodexNativeHookRelay(params: {
...(params.agentId ? { agentId: params.agentId } : {}),
sessionId: params.sessionId,
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
...(params.config ? { config: params.config } : {}),
runId: params.runId,
allowedEvents: params.options?.events ?? CODEX_NATIVE_HOOK_RELAY_EVENTS,
ttlMs: params.options?.ttlMs,

View File

@@ -1,11 +1,18 @@
import { statSync, writeFileSync } from "node:fs";
import fs from "node:fs/promises";
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { updateSessionStore, type SessionEntry } from "../../config/sessions.js";
import {
initializeGlobalHookRunner,
resetGlobalHookRunner,
} from "../../plugins/hook-runner-global.js";
import { createMockPluginRegistry } from "../../plugins/hooks.test-helpers.js";
import { patchPluginSessionExtension } from "../../plugins/host-hook-state.js";
import { createEmptyPluginRegistry } from "../../plugins/registry-empty.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import {
__testing,
buildNativeHookRelayCommand,
@@ -17,6 +24,7 @@ import {
afterEach(() => {
vi.useRealTimers();
resetGlobalHookRunner();
setActivePluginRegistry(createEmptyPluginRegistry());
__testing.clearNativeHookRelaysForTests();
});
@@ -629,6 +637,95 @@ describe("native hook relay registry", () => {
);
});
it("passes config to trusted policies for native pre-tool session extension reads", async () => {
const stateDir = await fs.mkdtemp(path.join(tmpdir(), "openclaw-native-relay-policy-"));
const storePath = path.join(stateDir, "sessions.json");
const config = { session: { store: storePath } };
const seen: unknown[] = [];
const registry = createEmptyPluginRegistry();
registry.sessionExtensions = [
{
pluginId: "policy-plugin",
pluginName: "Policy Plugin",
source: "test",
extension: {
namespace: "policy",
description: "policy state",
},
},
];
registry.trustedToolPolicies = [
{
pluginId: "policy-plugin",
pluginName: "Policy Plugin",
source: "test",
policy: {
id: "session-extension-policy",
description: "session extension policy",
evaluate(_event, ctx) {
const policyState = ctx.getSessionExtension?.("policy");
seen.push(policyState);
if ((policyState as { block?: boolean } | undefined)?.block) {
return { block: true, blockReason: "blocked by session extension" };
}
return undefined;
},
},
},
];
setActivePluginRegistry(registry);
try {
await updateSessionStore(storePath, (store) => {
store["agent:main:session-1"] = {
sessionId: "session-1",
updatedAt: Date.now(),
} as SessionEntry;
});
await expect(
patchPluginSessionExtension({
cfg: config as never,
sessionKey: "agent:main:session-1",
pluginId: "policy-plugin",
namespace: "policy",
value: { block: true },
}),
).resolves.toMatchObject({ ok: true });
const relay = registerNativeHookRelay({
provider: "codex",
agentId: "agent-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
config: config as never,
runId: "run-1",
allowedEvents: ["pre_tool_use"],
});
const response = await invokeNativeHookRelay({
provider: "codex",
relayId: relay.relayId,
event: "pre_tool_use",
rawPayload: {
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_use_id: "native-policy-call-1",
tool_input: { command: "rm -rf dist" },
},
});
expect(JSON.parse(response.stdout)).toEqual({
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "blocked by session extension",
},
});
expect(seen).toEqual([{ block: true }]);
} finally {
await fs.rm(stateDir, { recursive: true, force: true });
}
});
it("does not rewrite Codex native tool input when before_tool_call adjusts params", async () => {
const beforeToolCall = vi.fn(async () => ({
params: { command: "echo replaced" },

View File

@@ -18,6 +18,7 @@ import {
} from "node:http";
import { tmpdir } from "node:os";
import path from "node:path";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { resolveOpenClawPackageRootSync } from "../../infra/openclaw-root.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { PluginApprovalResolutions } from "../../plugins/types.js";
@@ -81,6 +82,7 @@ export type NativeHookRelayRegistration = {
agentId?: string;
sessionId: string;
sessionKey?: string;
config?: OpenClawConfig;
runId: string;
allowedEvents: readonly NativeHookRelayEvent[];
expiresAtMs: number;
@@ -98,6 +100,7 @@ export type RegisterNativeHookRelayParams = {
agentId?: string;
sessionId: string;
sessionKey?: string;
config?: OpenClawConfig;
runId: string;
allowedEvents?: readonly NativeHookRelayEvent[];
ttlMs?: number;
@@ -299,6 +302,7 @@ export function registerNativeHookRelay(
...(params.agentId ? { agentId: params.agentId } : {}),
sessionId: params.sessionId,
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
...(params.config ? { config: params.config } : {}),
runId: params.runId,
allowedEvents,
expiresAtMs: Date.now() + normalizePositiveInteger(params.ttlMs, DEFAULT_RELAY_TTL_MS),
@@ -878,6 +882,7 @@ async function runNativeHookRelayPreToolUse(params: {
...(params.registration.agentId ? { agentId: params.registration.agentId } : {}),
sessionId: params.registration.sessionId,
...(params.registration.sessionKey ? { sessionKey: params.registration.sessionKey } : {}),
...(params.registration.config ? { config: params.registration.config } : {}),
runId: params.registration.runId,
},
});