fix(gateway): surface env override keys in exec approvals

This commit is contained in:
Peter Steinberger 2026-03-16 23:24:25 -07:00
parent 38a6415a70
commit 57204b4fa9
No known key found for this signature in database
5 changed files with 39 additions and 3 deletions

View File

@ -113,6 +113,7 @@ Docs: https://docs.openclaw.ai
- Agents/compaction: rerun transcript repair after `session.compact()` so orphaned `tool_result` blocks cannot survive compaction and break later Anthropic requests. (#16095) thanks @claw-sylphx.
- Agents/compaction: trigger overflow recovery from the tool-result guard once post-compaction context still exceeds the safe threshold, so long tool loops compact before the next model call hard-fails. (#29371) thanks @keshav55.
- macOS/exec approvals: harden exec-host request HMAC verification to use a timing-safe compare and keep malformed or truncated signatures fail-closed in focused IPC auth coverage.
- Gateway/exec approvals: surface requested env override keys in gateway-host approval prompts so operators can review surviving env context without inheriting noisy base host env.
## 2026.3.13

View File

@ -40,6 +40,7 @@ export type ProcessGatewayAllowlistParams = {
command: string;
workdir: string;
env: Record<string, string>;
requestedEnv?: Record<string, string>;
pty: boolean;
timeoutSec?: number;
defaultTimeoutSec: number;
@ -152,6 +153,7 @@ export async function processGatewayAllowlist(
await registerExecApprovalRequestForHostOrThrow({
approvalId,
command: params.command,
env: params.requestedEnv,
workdir: params.workdir,
host: "gateway",
security: hostSecurity,

View File

@ -429,6 +429,7 @@ export function createExecTool(
command: params.command,
workdir,
env,
requestedEnv: params.env,
pty: params.pty === true && !sandbox,
timeoutSec: params.timeout,
defaultTimeoutSec,

View File

@ -4,7 +4,10 @@ import {
DEFAULT_EXEC_APPROVAL_TIMEOUT_MS,
type ExecApprovalDecision,
} from "../../infra/exec-approvals.js";
import { buildSystemRunApprovalBinding } from "../../infra/system-run-approval-binding.js";
import {
buildSystemRunApprovalBinding,
buildSystemRunApprovalEnvBinding,
} from "../../infra/system-run-approval-binding.js";
import { resolveSystemRunApprovalRequestContext } from "../../infra/system-run-approval-context.js";
import type { ExecApprovalManager } from "../exec-approval-manager.js";
import {
@ -107,6 +110,7 @@ export function createExecApprovalHandlers(
);
return;
}
const envBinding = buildSystemRunApprovalEnvBinding(p.env);
const systemRunBinding =
host === "node"
? buildSystemRunApprovalBinding({
@ -132,7 +136,7 @@ export function createExecApprovalHandlers(
? undefined
: sanitizeExecApprovalDisplayText(approvalContext.commandPreview),
commandArgv: host === "node" ? undefined : effectiveCommandArgv,
envKeys: systemRunBinding?.envKeys?.length ? systemRunBinding.envKeys : undefined,
envKeys: envBinding.envKeys.length > 0 ? envBinding.envKeys : undefined,
systemRunBinding: systemRunBinding?.binding ?? null,
systemRunPlan: approvalContext.plan,
cwd: effectiveCwd ?? null,

View File

@ -6,7 +6,10 @@ import { fileURLToPath } from "node:url";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { emitAgentEvent } from "../../infra/agent-events.js";
import { formatZonedTimestamp } from "../../infra/format-time/format-datetime.js";
import { buildSystemRunApprovalBinding } from "../../infra/system-run-approval-binding.js";
import {
buildSystemRunApprovalBinding,
buildSystemRunApprovalEnvBinding,
} from "../../infra/system-run-approval-binding.js";
import { resetLogger, setLoggerOverride } from "../../logging.js";
import { ExecApprovalManager } from "../exec-approval-manager.js";
import { validateExecApprovalRequestParams } from "../protocol/index.js";
@ -583,6 +586,31 @@ describe("exec approval handlers", () => {
);
});
it("stores sorted env keys for gateway approvals without node-only binding", async () => {
const { handlers, broadcasts, respond, context } = createExecApprovalFixture();
await requestExecApproval({
handlers,
respond,
context,
params: {
host: "gateway",
nodeId: undefined,
systemRunPlan: undefined,
env: {
Z_VAR: "z",
A_VAR: "a",
},
},
});
const requested = broadcasts.find((entry) => entry.event === "exec.approval.requested");
expect(requested).toBeTruthy();
const request = (requested?.payload as { request?: Record<string, unknown> })?.request ?? {};
expect(request["envKeys"]).toEqual(
buildSystemRunApprovalEnvBinding({ A_VAR: "a", Z_VAR: "z" }).envKeys,
);
expect(request["systemRunBinding"]).toBeNull();
});
it("prefers systemRunPlan canonical command/cwd when present", async () => {
const { handlers, broadcasts, respond, context } = createExecApprovalFixture();
await requestExecApproval({