From 4d010aa1217effaaf4901e0cc23a015652987b76 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 2 May 2026 00:26:29 +0700 Subject: [PATCH] fix: pass native relay config to tool policies --- .../codex/src/app-server/run-attempt.ts | 3 + src/agents/harness/native-hook-relay.test.ts | 97 +++++++++++++++++++ src/agents/harness/native-hook-relay.ts | 5 + 3 files changed, 105 insertions(+) diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index cfdd72e417b..f534eae91de 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -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, diff --git a/src/agents/harness/native-hook-relay.test.ts b/src/agents/harness/native-hook-relay.test.ts index 16f8583b7b8..df00a3e5ea4 100644 --- a/src/agents/harness/native-hook-relay.test.ts +++ b/src/agents/harness/native-hook-relay.test.ts @@ -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" }, diff --git a/src/agents/harness/native-hook-relay.ts b/src/agents/harness/native-hook-relay.ts index 38de9875cab..dd78198f9d8 100644 --- a/src/agents/harness/native-hook-relay.ts +++ b/src/agents/harness/native-hook-relay.ts @@ -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, }, });