mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 01:28:27 +00:00
test: add env -S allowlist bypass regressions
This commit is contained in:
@@ -16,7 +16,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Security/Config writes: block reserved prototype keys in account-id normalization and route account config resolution through own-key lookups, hardening `/allowlist` and account-scoped config paths against prototype-chain pollution.
|
- Security/Config writes: block reserved prototype keys in account-id normalization and route account config resolution through own-key lookups, hardening `/allowlist` and account-scoped config paths against prototype-chain pollution.
|
||||||
- Security/Exec: harden `safeBins` long-option validation by rejecting unknown/ambiguous GNU long-option abbreviations and denying sort filesystem-dependent flags (`--random-source`, `--temporary-directory`, `-T`), closing safe-bin denylist bypasses. Thanks @jiseoung.
|
- Security/Exec: harden `safeBins` long-option validation by rejecting unknown/ambiguous GNU long-option abbreviations and denying sort filesystem-dependent flags (`--random-source`, `--temporary-directory`, `-T`), closing safe-bin denylist bypasses. Thanks @jiseoung.
|
||||||
- Security/Channels: unify dangerous name-matching policy checks (`dangerouslyAllowNameMatching`) across core and extension channels, share mutable-allowlist detectors between `openclaw doctor` and `openclaw security audit`, and scan all configured accounts (not only the default account) in channel security audit findings.
|
- Security/Channels: unify dangerous name-matching policy checks (`dangerouslyAllowNameMatching`) across core and extension channels, share mutable-allowlist detectors between `openclaw doctor` and `openclaw security audit`, and scan all configured accounts (not only the default account) in channel security audit findings.
|
||||||
- Security/Exec approvals: enforce canonical wrapper execution plans across allowlist analysis and runtime execution (node host + gateway host), fail closed on semantic `env` wrapper usage, and reject unknown short safe-bin flags to prevent `env -S/--split-string` interpretation-mismatch bypasses. This ships in the next npm release. Thanks @jiseoung for reporting.
|
- Security/Exec approvals: enforce canonical wrapper execution plans across allowlist analysis and runtime execution (node host + gateway host), fail closed on semantic `env` wrapper usage, and reject unknown short safe-bin flags to prevent `env -S/--split-string` interpretation-mismatch bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||||
- Security/Image tool: enforce `tools.fs.workspaceOnly` for sandboxed `image` path resolution so mounted out-of-workspace paths are blocked before media bytes are loaded/sent to vision providers. This ships in the next npm release. Thanks @tdjackey for reporting.
|
- Security/Image tool: enforce `tools.fs.workspaceOnly` for sandboxed `image` path resolution so mounted out-of-workspace paths are blocked before media bytes are loaded/sent to vision providers. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||||
|
|
||||||
## 2026.2.23 (Unreleased)
|
## 2026.2.23 (Unreleased)
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ describe("browser control server", () => {
|
|||||||
cdpUrl: state.cdpBaseUrl,
|
cdpUrl: state.cdpBaseUrl,
|
||||||
targetId: "abcd1234",
|
targetId: "abcd1234",
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
|
ssrfPolicy: {
|
||||||
|
dangerouslyAllowPrivateNetwork: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const click = await postJson<{ ok: boolean }>(`${base}/act`, {
|
const click = await postJson<{ ok: boolean }>(`${base}/act`, {
|
||||||
|
|||||||
@@ -254,6 +254,35 @@ describe("exec approvals command resolution", () => {
|
|||||||
expect(resolution?.rawExecutable).toBe("/usr/bin/env");
|
expect(resolution?.rawExecutable).toBe("/usr/bin/env");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("fails closed for env -S even when env itself is allowlisted", () => {
|
||||||
|
const dir = makeTempDir();
|
||||||
|
const binDir = path.join(dir, "bin");
|
||||||
|
fs.mkdirSync(binDir, { recursive: true });
|
||||||
|
const envName = process.platform === "win32" ? "env.exe" : "env";
|
||||||
|
const envPath = path.join(binDir, envName);
|
||||||
|
fs.writeFileSync(envPath, process.platform === "win32" ? "" : "#!/bin/sh\n");
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
fs.chmodSync(envPath, 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
const analysis = analyzeArgvCommand({
|
||||||
|
argv: [envPath, "-S", 'sh -c "echo pwned"'],
|
||||||
|
cwd: dir,
|
||||||
|
env: makePathEnv(binDir),
|
||||||
|
});
|
||||||
|
const allowlistEval = evaluateExecAllowlist({
|
||||||
|
analysis,
|
||||||
|
allowlist: [{ pattern: envPath }],
|
||||||
|
safeBins: normalizeSafeBins([]),
|
||||||
|
cwd: dir,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(analysis.ok).toBe(true);
|
||||||
|
expect(analysis.segments[0]?.resolution?.policyBlocked).toBe(true);
|
||||||
|
expect(allowlistEval.allowlistSatisfied).toBe(false);
|
||||||
|
expect(allowlistEval.segmentSatisfiedBy).toEqual([null]);
|
||||||
|
});
|
||||||
|
|
||||||
it("unwraps env wrapper with shell inner executable", () => {
|
it("unwraps env wrapper with shell inner executable", () => {
|
||||||
const resolution = resolveCommandResolutionFromArgv(["/usr/bin/env", "bash", "-lc", "echo hi"]);
|
const resolution = resolveCommandResolutionFromArgv(["/usr/bin/env", "bash", "-lc", "echo hi"]);
|
||||||
expect(resolution?.rawExecutable).toBe("bash");
|
expect(resolution?.rawExecutable).toBe("bash");
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("denies ./sh wrapper spoof in allowlist on-miss mode before execution", async () => {
|
it("denies ./sh wrapper spoof in allowlist on-miss mode before execution", async () => {
|
||||||
const marker = path.join(os.tmpdir(), `openclaw-wrapper-spoof-${process.pid}-${Date.now()}`);
|
const marker = path.join(os.tmpdir(), `openclaw-wrapper-spoof-${process.pid}-${Date.now()}`);
|
||||||
const runCommand = vi.fn(async () => {
|
const runCommand = vi.fn(async () => {
|
||||||
@@ -213,4 +212,21 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
|||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("denies env -S shell payloads in allowlist mode", async () => {
|
||||||
|
const { runCommand, sendInvokeResult } = await runSystemInvoke({
|
||||||
|
preferMacAppExecHost: false,
|
||||||
|
security: "allowlist",
|
||||||
|
command: ["env", "-S", 'sh -c "echo pwned"'],
|
||||||
|
});
|
||||||
|
expect(runCommand).not.toHaveBeenCalled();
|
||||||
|
expect(sendInvokeResult).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
ok: false,
|
||||||
|
error: expect.objectContaining({
|
||||||
|
message: expect.stringContaining("allowlist miss"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user