mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-16 18:34:18 +00:00
test: remove exec approval file fixtures
This commit is contained in:
@@ -425,7 +425,7 @@ If a macOS node is paired, the Gateway can invoke `system.run` on that node. Thi
|
||||
- Gateway node pairing is not a per-command approval surface. It establishes node identity/trust and token issuance.
|
||||
- The Gateway applies a coarse global node command policy via `gateway.nodes.allowCommands` / `denyCommands`.
|
||||
- Controlled on the Mac via **Settings → Exec approvals** (security + ask + allowlist).
|
||||
- The per-node `system.run` policy is the node's own exec approvals file (`exec.approvals.node.*`), which can be stricter or looser than the gateway's global command-ID policy.
|
||||
- The per-node `system.run` policy is the node's own SQLite exec approvals state (`exec.approvals.node.*`), which can be stricter or looser than the gateway's global command-ID policy.
|
||||
- A node running with `security="full"` and `ask="off"` is following the default trusted-operator model. Treat that as expected behavior unless your deployment explicitly requires a tighter approval or allowlist stance.
|
||||
- Approval mode binds exact request context and, when possible, one concrete local script/file operand. If OpenClaw cannot identify exactly one direct local file for an interpreter/runtime command, approval-backed execution is denied rather than promising full semantic coverage.
|
||||
- For `host=node`, approval-backed runs also store a canonical prepared
|
||||
|
||||
@@ -76,7 +76,7 @@ If pairing is missing, approve the node device first.
|
||||
If `nodes describe` is missing a command, check the gateway node command policy and whether the node actually declared that command on connect.
|
||||
If pairing is fine but `system.run` fails, fix exec approvals/allowlist on that node.
|
||||
|
||||
Node pairing is an identity/trust gate, not a per-command approval surface. For `system.run`, the per-node policy lives in that node's exec approvals file (`openclaw approvals get --node ...`), not in the gateway pairing record.
|
||||
Node pairing is an identity/trust gate, not a per-command approval surface. For `system.run`, the per-node policy lives in that node's SQLite exec approvals state (`openclaw approvals get --node ...`), not in the gateway pairing record.
|
||||
|
||||
For approval-backed `host=node` runs, the gateway also binds execution to the
|
||||
prepared canonical `systemRunPlan`. If a later caller mutates command/cwd or
|
||||
|
||||
@@ -109,7 +109,7 @@ describe("sendExecApprovalFollowupResult", () => {
|
||||
});
|
||||
|
||||
describe("resolveExecHostApprovalContext", () => {
|
||||
it("does not let exec-approvals.json broaden security beyond the requested policy", () => {
|
||||
it("does not let host exec approvals broaden security beyond the requested policy", () => {
|
||||
mocks.resolveExecApprovals.mockReturnValue({
|
||||
defaults: {
|
||||
security: "allowlist",
|
||||
|
||||
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { readExecApprovalsSnapshot, saveExecApprovals } from "../infra/exec-approvals.js";
|
||||
import { sendMessage } from "../infra/outbound/message.js";
|
||||
import { buildSystemRunPreparePayload } from "../test-utils/system-run-prepare-payload.js";
|
||||
import { createExecTool } from "./bash-tools.exec.js";
|
||||
@@ -183,10 +184,8 @@ function buildPreparedSystemRunPayload(rawInvokeParams: unknown) {
|
||||
return buildSystemRunPreparePayload(params);
|
||||
}
|
||||
|
||||
async function writeExecApprovalsConfig(config: Record<string, unknown>) {
|
||||
const approvalsPath = path.join(process.env.HOME ?? "", ".openclaw", "exec-approvals.json");
|
||||
await fs.mkdir(path.dirname(approvalsPath), { recursive: true });
|
||||
await fs.writeFile(approvalsPath, JSON.stringify(config, null, 2));
|
||||
async function writeExecApprovalsConfig(config: Parameters<typeof saveExecApprovals>[0]) {
|
||||
saveExecApprovals(config);
|
||||
}
|
||||
|
||||
function acceptedApprovalResponse(params: unknown) {
|
||||
@@ -774,22 +773,14 @@ describe("exec approvals", () => {
|
||||
expect(calls).toContain("exec.approval.request");
|
||||
expect(calls).toContain("exec.approval.waitDecision");
|
||||
|
||||
const approvalsPath = path.join(process.env.HOME ?? "", ".openclaw", "exec-approvals.json");
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const raw = await fs.readFile(approvalsPath, "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
agents?: { main?: { allowlist?: Array<{ source?: string }> } };
|
||||
};
|
||||
return (
|
||||
parsed.agents?.main?.allowlist?.some((entry) => entry.source === "allow-always") ===
|
||||
true
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
const parsed = readExecApprovalsSnapshot().file;
|
||||
return (
|
||||
parsed.agents?.main?.allowlist?.some((entry) => entry.source === "allow-always") ===
|
||||
true
|
||||
);
|
||||
},
|
||||
{ timeout: 2000, interval: 1 },
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ let createExecTool: typeof import("./bash-tools.exec.js").createExecTool;
|
||||
|
||||
function createExecApprovals(): ExecApprovalsResolved {
|
||||
return {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
socketPath: "/tmp/exec-approvals.sock",
|
||||
token: "token",
|
||||
defaults: {
|
||||
|
||||
@@ -12,7 +12,7 @@ let createOpenClawCodingTools: typeof import("./pi-tools.js").createOpenClawCodi
|
||||
|
||||
const { mockExecApprovals, supervisorSpawnMock } = vi.hoisted(() => {
|
||||
const execApprovals = {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
socketPath: "/tmp/exec-approvals.sock",
|
||||
token: "token",
|
||||
defaults: {
|
||||
|
||||
@@ -39,7 +39,7 @@ const mocks = vi.hoisted(() => {
|
||||
};
|
||||
}
|
||||
return {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
hash: "hash-1",
|
||||
file: { version: 1, agents: {} },
|
||||
@@ -56,7 +56,7 @@ const mocks = vi.hoisted(() => {
|
||||
const { callGatewayFromCli, defaultRuntime, readBestEffortConfig, runtimeErrors } = mocks;
|
||||
|
||||
const localSnapshot = {
|
||||
path: "/tmp/local-exec-approvals.json",
|
||||
path: "/tmp/local-openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
raw: "{}",
|
||||
hash: "hash-local",
|
||||
@@ -249,13 +249,14 @@ describe("exec approvals CLI", () => {
|
||||
expect.objectContaining({
|
||||
scopeLabel: "agent:runner",
|
||||
security: expect.objectContaining({
|
||||
hostSource: "/tmp/local-exec-approvals.json agents.*.security",
|
||||
hostSource:
|
||||
"/tmp/local-openclaw.sqlite#kv/exec.approvals/current agents.*.security",
|
||||
}),
|
||||
ask: expect.objectContaining({
|
||||
hostSource: "/tmp/local-exec-approvals.json agents.*.ask",
|
||||
hostSource: "/tmp/local-openclaw.sqlite#kv/exec.approvals/current agents.*.ask",
|
||||
}),
|
||||
askFallback: expect.objectContaining({
|
||||
source: "/tmp/local-exec-approvals.json agents.*.askFallback",
|
||||
source: "/tmp/local-openclaw.sqlite#kv/exec.approvals/current agents.*.askFallback",
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
@@ -282,7 +283,7 @@ describe("exec approvals CLI", () => {
|
||||
}
|
||||
if (method === "exec.approvals.node.get") {
|
||||
return {
|
||||
path: "/tmp/node-exec-approvals.json",
|
||||
path: "/tmp/node-openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
hash: "hash-node-1",
|
||||
file: {
|
||||
@@ -317,7 +318,7 @@ describe("exec approvals CLI", () => {
|
||||
}),
|
||||
askFallback: expect.objectContaining({
|
||||
effective: "deny",
|
||||
source: "/tmp/node-exec-approvals.json defaults.askFallback",
|
||||
source: "/tmp/node-openclaw.sqlite#kv/exec.approvals/current defaults.askFallback",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
@@ -335,7 +336,7 @@ describe("exec approvals CLI", () => {
|
||||
}
|
||||
if (method === "exec.approvals.get") {
|
||||
return {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
hash: "hash-1",
|
||||
file: { version: 1, agents: {} },
|
||||
@@ -367,7 +368,7 @@ describe("exec approvals CLI", () => {
|
||||
}
|
||||
if (method === "exec.approvals.get") {
|
||||
return {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
hash: "hash-1",
|
||||
file: { version: 1, agents: {} },
|
||||
@@ -399,7 +400,7 @@ describe("exec approvals CLI", () => {
|
||||
}
|
||||
if (method === "exec.approvals.node.get") {
|
||||
return {
|
||||
path: "/tmp/node-exec-approvals.json",
|
||||
path: "/tmp/node-openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
hash: "hash-node-1",
|
||||
file: { version: 1, agents: {} },
|
||||
|
||||
@@ -104,7 +104,7 @@ const mocks = vi.hoisted(() => {
|
||||
config: configState,
|
||||
})),
|
||||
readExecApprovalsSnapshot: vi.fn<() => ExecApprovalsSnapshot>(() => ({
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
raw: "{}",
|
||||
hash: "approvals-hash",
|
||||
@@ -218,7 +218,7 @@ describe("exec-policy CLI", () => {
|
||||
}));
|
||||
mocks.readExecApprovalsSnapshot.mockReset();
|
||||
mocks.readExecApprovalsSnapshot.mockImplementation(() => ({
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
raw: "{}",
|
||||
hash: "approvals-hash",
|
||||
@@ -238,7 +238,7 @@ describe("exec-policy CLI", () => {
|
||||
expect(mocks.defaultRuntime.writeJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
configPath: "/tmp/openclaw.json",
|
||||
approvalsPath: "/tmp/exec-approvals.json",
|
||||
approvalsStore: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
effectivePolicy: expect.objectContaining({
|
||||
scopes: [
|
||||
expect.objectContaining({
|
||||
@@ -378,7 +378,7 @@ describe("exec-policy CLI", () => {
|
||||
config: mocks.getConfig(),
|
||||
}));
|
||||
mocks.readExecApprovalsSnapshot.mockImplementationOnce(() => ({
|
||||
path: "/tmp/exec-approvals.json\u0007\nforged",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current\u0007\nforged",
|
||||
exists: true,
|
||||
raw: "{}",
|
||||
hash: "approvals-hash",
|
||||
@@ -405,7 +405,7 @@ describe("exec-policy CLI", () => {
|
||||
mocks.defaultRuntime.log.mock.calls.map((call) => String(call[0] ?? "")).join("\n"),
|
||||
);
|
||||
expect(output).toContain("/tmp/openclaw.json");
|
||||
expect(output).toContain("/tmp/exec-approvals.json");
|
||||
expect(output).toContain("/tmp/openclaw.sqlite#kv/exec.approvals/current");
|
||||
expect(output).toContain("scope\\u{200B}name");
|
||||
expect(output).toContain("host=auto");
|
||||
expect(output).toContain("tools.exec.");
|
||||
@@ -464,7 +464,7 @@ describe("exec-policy CLI", () => {
|
||||
const originalApprovals = structuredClone(mocks.getApprovals());
|
||||
const originalRaw = JSON.stringify(originalApprovals, null, 2);
|
||||
const originalSnapshot: ExecApprovalsSnapshot = {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
raw: originalRaw,
|
||||
hash: "approvals-hash",
|
||||
@@ -484,9 +484,9 @@ describe("exec-policy CLI", () => {
|
||||
expect(mocks.runtimeErrors).toEqual(["config write failed"]);
|
||||
});
|
||||
|
||||
it("removes a newly-written approvals file when config replacement fails and the original file was missing", async () => {
|
||||
it("removes newly-written approvals state when config replacement fails and the original state was missing", async () => {
|
||||
const missingSnapshot: ExecApprovalsSnapshot = {
|
||||
path: "/tmp/missing-exec-approvals.json",
|
||||
path: "/tmp/missing-openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: false,
|
||||
raw: null,
|
||||
hash: "approvals-hash",
|
||||
@@ -508,7 +508,7 @@ describe("exec-policy CLI", () => {
|
||||
const originalApprovals = structuredClone(mocks.getApprovals());
|
||||
const originalRaw = JSON.stringify(originalApprovals, null, 2);
|
||||
const originalSnapshot = {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
raw: originalRaw,
|
||||
hash: "original-hash",
|
||||
@@ -524,7 +524,7 @@ describe("exec-policy CLI", () => {
|
||||
agents: {},
|
||||
};
|
||||
const concurrentSnapshot: ExecApprovalsSnapshot = {
|
||||
path: "/tmp/exec-approvals.json",
|
||||
path: "/tmp/openclaw.sqlite#kv/exec.approvals/current",
|
||||
exists: true,
|
||||
raw: JSON.stringify(concurrentFile, null, 2),
|
||||
hash: "concurrent-write-hash",
|
||||
|
||||
@@ -57,7 +57,7 @@ const EXEC_POLICY_PRESETS: Record<ExecPolicyPresetName, Required<ExecPolicyResol
|
||||
|
||||
type ExecPolicyShowPayload = {
|
||||
configPath: string;
|
||||
approvalsPath: string;
|
||||
approvalsStore: string;
|
||||
approvalsExists: boolean;
|
||||
effectivePolicy: {
|
||||
note: string;
|
||||
@@ -72,7 +72,7 @@ type ExecPolicyShowScope = Omit<
|
||||
ExecPolicyScopeSnapshot,
|
||||
"security" | "ask" | "askFallback" | "allowedDecisions"
|
||||
> & {
|
||||
runtimeApprovalsSource: "local-file" | "node-runtime";
|
||||
runtimeApprovalsSource: "local-state" | "node-runtime";
|
||||
security: {
|
||||
requested: ExecSecurity;
|
||||
requestedSource: string;
|
||||
@@ -234,7 +234,7 @@ async function buildLocalExecPolicyShowPayload(): Promise<ExecPolicyShowPayload>
|
||||
);
|
||||
return {
|
||||
configPath: configSnapshot.path,
|
||||
approvalsPath: approvalsSnapshot.path,
|
||||
approvalsStore: approvalsSnapshot.path,
|
||||
approvalsExists: approvalsSnapshot.exists,
|
||||
effectivePolicy: {
|
||||
note: hasNodeRuntimeScope
|
||||
@@ -250,7 +250,7 @@ function buildExecPolicyShowScope(snapshot: ExecPolicyScopeSnapshot): ExecPolicy
|
||||
if (snapshot.host.requested !== "node") {
|
||||
return {
|
||||
...baseScope,
|
||||
runtimeApprovalsSource: "local-file",
|
||||
runtimeApprovalsSource: "local-state",
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -293,9 +293,9 @@ function renderExecPolicyShow(payload: ExecPolicyShowPayload): void {
|
||||
],
|
||||
rows: [
|
||||
{ Field: "Config", Value: sanitizeExecPolicyTableCell(payload.configPath) },
|
||||
{ Field: "Approvals", Value: sanitizeExecPolicyTableCell(payload.approvalsPath) },
|
||||
{ Field: "Approvals", Value: sanitizeExecPolicyTableCell(payload.approvalsStore) },
|
||||
{
|
||||
Field: "Approvals File",
|
||||
Field: "Approvals State",
|
||||
Value: sanitizeExecPolicyTableCell(payload.approvalsExists ? "present" : "missing"),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -118,7 +118,7 @@ export const execApprovalsHandlers: GatewayRequestHandlers = {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, "exec approvals file is required"),
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, "exec approvals document is required"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,14 +308,18 @@ describe("exec approvals policy helpers", () => {
|
||||
},
|
||||
configPath: "tools.exec",
|
||||
scopeLabel: "tools.exec",
|
||||
hostPath: "/tmp/node-exec-approvals.json",
|
||||
hostPath: "/tmp/node-openclaw.sqlite#kv/exec.approvals/current",
|
||||
});
|
||||
|
||||
expect(summary.security.hostSource).toBe("/tmp/node-exec-approvals.json defaults.security");
|
||||
expect(summary.ask.hostSource).toBe("/tmp/node-exec-approvals.json defaults.ask");
|
||||
expect(summary.security.hostSource).toBe(
|
||||
"/tmp/node-openclaw.sqlite#kv/exec.approvals/current defaults.security",
|
||||
);
|
||||
expect(summary.ask.hostSource).toBe(
|
||||
"/tmp/node-openclaw.sqlite#kv/exec.approvals/current defaults.ask",
|
||||
);
|
||||
expect(summary.askFallback).toEqual({
|
||||
effective: "deny",
|
||||
source: "/tmp/node-exec-approvals.json defaults.askFallback",
|
||||
source: "/tmp/node-openclaw.sqlite#kv/exec.approvals/current defaults.askFallback",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ export async function handleInvoke(
|
||||
try {
|
||||
const params = decodeParams<SystemExecApprovalsSetParams>(frame.paramsJSON);
|
||||
if (!params.file || typeof params.file !== "object") {
|
||||
throw new Error("INVALID_REQUEST: exec approvals file required");
|
||||
throw new Error("INVALID_REQUEST: exec approvals document required");
|
||||
}
|
||||
ensureExecApprovals();
|
||||
const snapshot = readExecApprovalsSnapshot();
|
||||
|
||||
Reference in New Issue
Block a user