mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 07:10:23 +00:00
refactor(exec): centralize safe-bin policy checks
This commit is contained in:
@@ -296,6 +296,70 @@ describe("security audit", () => {
|
||||
expect(hasFinding(res, "tools.exec.host_sandbox_no_sandbox_agents", "warn")).toBe(true);
|
||||
});
|
||||
|
||||
it("warns for interpreter safeBins entries without explicit profiles", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: {
|
||||
exec: {
|
||||
safeBins: ["python3"],
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "ops",
|
||||
tools: {
|
||||
exec: {
|
||||
safeBins: ["node"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const res = await audit(cfg);
|
||||
|
||||
expect(hasFinding(res, "tools.exec.safe_bins_interpreter_unprofiled", "warn")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not warn for interpreter safeBins when explicit profiles are present", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: {
|
||||
exec: {
|
||||
safeBins: ["python3"],
|
||||
safeBinProfiles: {
|
||||
python3: {
|
||||
maxPositional: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "ops",
|
||||
tools: {
|
||||
exec: {
|
||||
safeBins: ["node"],
|
||||
safeBinProfiles: {
|
||||
node: {
|
||||
maxPositional: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const res = await audit(cfg);
|
||||
|
||||
expect(
|
||||
res.findings.some((f) => f.checkId === "tools.exec.safe_bins_interpreter_unprofiled"),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("warns when loopback control UI lacks trusted proxies", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
gateway: {
|
||||
|
||||
@@ -11,6 +11,10 @@ import { resolveGatewayAuth } from "../gateway/auth.js";
|
||||
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
||||
import { resolveGatewayProbeAuth } from "../gateway/probe-auth.js";
|
||||
import { probeGateway } from "../gateway/probe.js";
|
||||
import {
|
||||
listInterpreterLikeSafeBins,
|
||||
resolveMergedSafeBinProfileFixtures,
|
||||
} from "../infra/exec-safe-bin-runtime-policy.js";
|
||||
import { collectChannelSecurityFindings } from "./audit-channel.js";
|
||||
import {
|
||||
collectAttackSurfaceSummaryFindings,
|
||||
@@ -695,6 +699,65 @@ function collectExecRuntimeFindings(cfg: OpenClawConfig): SecurityAuditFinding[]
|
||||
});
|
||||
}
|
||||
|
||||
const normalizeConfiguredSafeBins = (entries: unknown): string[] => {
|
||||
if (!Array.isArray(entries)) {
|
||||
return [];
|
||||
}
|
||||
return Array.from(
|
||||
new Set(
|
||||
entries
|
||||
.map((entry) => (typeof entry === "string" ? entry.trim().toLowerCase() : ""))
|
||||
.filter((entry) => entry.length > 0),
|
||||
),
|
||||
).toSorted();
|
||||
};
|
||||
const interpreterHits: string[] = [];
|
||||
const globalExec = cfg.tools?.exec;
|
||||
const globalSafeBins = normalizeConfiguredSafeBins(globalExec?.safeBins);
|
||||
if (globalSafeBins.length > 0) {
|
||||
const merged = resolveMergedSafeBinProfileFixtures({ global: globalExec }) ?? {};
|
||||
const interpreters = listInterpreterLikeSafeBins(globalSafeBins).filter((bin) => !merged[bin]);
|
||||
if (interpreters.length > 0) {
|
||||
interpreterHits.push(`- tools.exec.safeBins: ${interpreters.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const entry of agents) {
|
||||
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
|
||||
continue;
|
||||
}
|
||||
const agentExec = entry.tools?.exec;
|
||||
const agentSafeBins = normalizeConfiguredSafeBins(agentExec?.safeBins);
|
||||
if (agentSafeBins.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const merged =
|
||||
resolveMergedSafeBinProfileFixtures({
|
||||
global: globalExec,
|
||||
local: agentExec,
|
||||
}) ?? {};
|
||||
const interpreters = listInterpreterLikeSafeBins(agentSafeBins).filter((bin) => !merged[bin]);
|
||||
if (interpreters.length === 0) {
|
||||
continue;
|
||||
}
|
||||
interpreterHits.push(
|
||||
`- agents.list.${entry.id}.tools.exec.safeBins: ${interpreters.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (interpreterHits.length > 0) {
|
||||
findings.push({
|
||||
checkId: "tools.exec.safe_bins_interpreter_unprofiled",
|
||||
severity: "warn",
|
||||
title: "safeBins includes interpreter/runtime binaries without explicit profiles",
|
||||
detail:
|
||||
`Detected interpreter-like safeBins entries missing explicit profiles:\n${interpreterHits.join("\n")}\n` +
|
||||
"These entries can turn safeBins into a broad execution surface when used with permissive argv profiles.",
|
||||
remediation:
|
||||
"Remove interpreter/runtime bins from safeBins (prefer allowlist entries) or define hardened tools.exec.safeBinProfiles.<bin> rules.",
|
||||
});
|
||||
}
|
||||
|
||||
return findings;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user