mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(anthropic): stop forcing claude permission bypass
This commit is contained in:
@@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Anthropic/CLI security: stop Claude CLI backend defaults from forcing `bypassPermissions`, and strip malformed permission-mode overrides instead of silently falling back to a bypass. (#70723) Thanks @vincentkoc.
|
||||
- Android/security: require loopback-only cleartext gateway connections on Android manual and scanned routes, so private-LAN and link-local `ws://` endpoints now fail closed unless TLS is enabled. (#70722) Thanks @vincentkoc.
|
||||
- Pairing/security: require private-IP or loopback hosts for cleartext mobile pairing, and stop treating `.local` or dotless hostnames as safe cleartext endpoints. (#70721) Thanks @vincentkoc.
|
||||
- Approvals/security: require explicit chat exec-approval enablement instead of auto-enabling approval clients just because approvers resolve from config or owner allowlists. (#70715) Thanks @vincentkoc.
|
||||
|
||||
@@ -36,8 +36,6 @@ export function buildAnthropicCliBackend(): CliBackendPlugin {
|
||||
"--verbose",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
],
|
||||
resumeArgs: [
|
||||
"-p",
|
||||
@@ -47,8 +45,6 @@ export function buildAnthropicCliBackend(): CliBackendPlugin {
|
||||
"--verbose",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
"--resume",
|
||||
"{sessionId}",
|
||||
],
|
||||
|
||||
@@ -8,23 +8,16 @@ import {
|
||||
} from "./cli-shared.js";
|
||||
|
||||
describe("normalizeClaudePermissionArgs", () => {
|
||||
it("injects bypassPermissions when args omit permission flags", () => {
|
||||
it("leaves args alone when they omit permission flags", () => {
|
||||
expect(
|
||||
normalizeClaudePermissionArgs(["-p", "--output-format", "stream-json", "--verbose"]),
|
||||
).toEqual([
|
||||
"-p",
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
"--verbose",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
]);
|
||||
).toEqual(["-p", "--output-format", "stream-json", "--verbose"]);
|
||||
});
|
||||
|
||||
it("removes legacy skip-permissions and injects bypassPermissions", () => {
|
||||
it("removes legacy skip-permissions without adding bypassPermissions", () => {
|
||||
expect(
|
||||
normalizeClaudePermissionArgs(["-p", "--dangerously-skip-permissions", "--verbose"]),
|
||||
).toEqual(["-p", "--verbose", "--permission-mode", "bypassPermissions"]);
|
||||
).toEqual(["-p", "--verbose"]);
|
||||
});
|
||||
|
||||
it("keeps explicit permission-mode overrides", () => {
|
||||
@@ -39,10 +32,14 @@ describe("normalizeClaudePermissionArgs", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("treats a bare permission-mode flag as malformed and falls back to bypassPermissions", () => {
|
||||
it("drops malformed permission-mode flags in both split and equals forms", () => {
|
||||
expect(
|
||||
normalizeClaudePermissionArgs(["-p", "--permission-mode", "--output-format", "stream-json"]),
|
||||
).toEqual(["-p", "--output-format", "stream-json", "--permission-mode", "bypassPermissions"]);
|
||||
).toEqual(["-p", "--output-format", "stream-json"]);
|
||||
expect(normalizeClaudePermissionArgs(["-p", "--permission-mode="])).toEqual(["-p"]);
|
||||
expect(normalizeClaudePermissionArgs(["-p", "--permission-mode=--output-format"])).toEqual([
|
||||
"-p",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,8 +89,6 @@ describe("normalizeClaudeBackendConfig", () => {
|
||||
"--verbose",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
]);
|
||||
expect(normalized.resumeArgs).toEqual([
|
||||
"-p",
|
||||
@@ -104,8 +99,6 @@ describe("normalizeClaudeBackendConfig", () => {
|
||||
"{sessionId}",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
]);
|
||||
expect(normalized.output).toBe("jsonl");
|
||||
expect(normalized.liveSession).toBe("claude-stdio");
|
||||
@@ -136,12 +129,8 @@ describe("normalizeClaudeBackendConfig", () => {
|
||||
resumeArgs: ["-p", "--output-format", "stream-json", "--verbose", "--resume", "{sessionId}"],
|
||||
});
|
||||
|
||||
expect(normalized?.args).toContain("--permission-mode");
|
||||
expect(normalized?.args).toContain("bypassPermissions");
|
||||
expect(normalized?.args).toContain("--setting-sources");
|
||||
expect(normalized?.args).toContain("user");
|
||||
expect(normalized?.resumeArgs).toContain("--permission-mode");
|
||||
expect(normalized?.resumeArgs).toContain("bypassPermissions");
|
||||
expect(normalized?.resumeArgs).toContain("--setting-sources");
|
||||
expect(normalized?.resumeArgs).toContain("user");
|
||||
expect(normalized?.liveSession).toBe("claude-stdio");
|
||||
|
||||
@@ -56,7 +56,6 @@ export const CLAUDE_CLI_CLEAR_ENV = [
|
||||
|
||||
const CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG = "--dangerously-skip-permissions";
|
||||
const CLAUDE_PERMISSION_MODE_ARG = "--permission-mode";
|
||||
const CLAUDE_BYPASS_PERMISSIONS_MODE = "bypassPermissions";
|
||||
const CLAUDE_SETTING_SOURCES_ARG = "--setting-sources";
|
||||
const CLAUDE_SAFE_SETTING_SOURCES = "user";
|
||||
|
||||
@@ -69,7 +68,6 @@ export function normalizeClaudePermissionArgs(args?: string[]): string[] | undef
|
||||
return args;
|
||||
}
|
||||
const normalized: string[] = [];
|
||||
let hasPermissionMode = false;
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i];
|
||||
if (arg === CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG) {
|
||||
@@ -82,7 +80,6 @@ export function normalizeClaudePermissionArgs(args?: string[]): string[] | undef
|
||||
maybeValue.trim().length > 0 &&
|
||||
!maybeValue.startsWith("-")
|
||||
) {
|
||||
hasPermissionMode = true;
|
||||
normalized.push(arg);
|
||||
normalized.push(maybeValue);
|
||||
i += 1;
|
||||
@@ -90,13 +87,14 @@ export function normalizeClaudePermissionArgs(args?: string[]): string[] | undef
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith(`${CLAUDE_PERMISSION_MODE_ARG}=`)) {
|
||||
hasPermissionMode = true;
|
||||
const maybeValue = arg.slice(`${CLAUDE_PERMISSION_MODE_ARG}=`.length).trim();
|
||||
if (maybeValue.length > 0 && !maybeValue.startsWith("-")) {
|
||||
normalized.push(`${CLAUDE_PERMISSION_MODE_ARG}=${maybeValue}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
normalized.push(arg);
|
||||
}
|
||||
if (!hasPermissionMode) {
|
||||
normalized.push(CLAUDE_PERMISSION_MODE_ARG, CLAUDE_BYPASS_PERMISSIONS_MODE);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,8 +101,6 @@ const NORMALIZED_CLAUDE_FALLBACK_ARGS = [
|
||||
"stream-json",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
];
|
||||
|
||||
const NORMALIZED_CLAUDE_FALLBACK_RESUME_ARGS = [
|
||||
@@ -111,8 +109,6 @@ const NORMALIZED_CLAUDE_FALLBACK_RESUME_ARGS = [
|
||||
"{sessionId}",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
];
|
||||
|
||||
function normalizeTestClaudeArgs(args?: string[]): string[] | undefined {
|
||||
@@ -121,7 +117,6 @@ function normalizeTestClaudeArgs(args?: string[]): string[] | undefined {
|
||||
}
|
||||
const normalized: string[] = [];
|
||||
let hasSettingSources = false;
|
||||
let hasPermissionMode = false;
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i];
|
||||
if (arg === "--dangerously-skip-permissions") {
|
||||
@@ -144,23 +139,23 @@ function normalizeTestClaudeArgs(args?: string[]): string[] | undefined {
|
||||
if (arg === "--permission-mode") {
|
||||
const maybeValue = args[i + 1];
|
||||
if (maybeValue && !maybeValue.startsWith("-")) {
|
||||
hasPermissionMode = true;
|
||||
normalized.push(arg, maybeValue);
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("--permission-mode=")) {
|
||||
hasPermissionMode = true;
|
||||
const maybeValue = arg.slice("--permission-mode=".length).trim();
|
||||
if (maybeValue.length > 0 && !maybeValue.startsWith("-")) {
|
||||
normalized.push(`--permission-mode=${maybeValue}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
normalized.push(arg);
|
||||
}
|
||||
if (!hasSettingSources) {
|
||||
normalized.push("--setting-sources", "user");
|
||||
}
|
||||
if (!hasPermissionMode) {
|
||||
normalized.push("--permission-mode", "bypassPermissions");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -191,8 +186,6 @@ beforeEach(() => {
|
||||
"--verbose",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
],
|
||||
resumeArgs: [
|
||||
"stream-json",
|
||||
@@ -200,8 +193,6 @@ beforeEach(() => {
|
||||
"--verbose",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
"--resume",
|
||||
"{sessionId}",
|
||||
],
|
||||
@@ -414,7 +405,7 @@ describe("resolveCliBackendLiveTest", () => {
|
||||
});
|
||||
|
||||
describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
it("uses non-interactive permission-mode defaults for fresh and resume args", () => {
|
||||
it("keeps user-only setting sources without forcing a permission-mode default", () => {
|
||||
const resolved = resolveCliBackendConfig("claude-cli");
|
||||
|
||||
expect(resolved).not.toBeNull();
|
||||
@@ -426,8 +417,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
expect(resolved?.config.args).toContain("--verbose");
|
||||
expect(resolved?.config.args).toContain("--setting-sources");
|
||||
expect(resolved?.config.args).toContain("user");
|
||||
expect(resolved?.config.args).toContain("--permission-mode");
|
||||
expect(resolved?.config.args).toContain("bypassPermissions");
|
||||
expect(resolved?.config.args).not.toContain("--permission-mode");
|
||||
expect(resolved?.config.args).not.toContain("--dangerously-skip-permissions");
|
||||
expect(resolved?.config.input).toBe("stdin");
|
||||
expect(resolved?.config.resumeArgs).toContain("stream-json");
|
||||
@@ -435,8 +425,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
expect(resolved?.config.resumeArgs).toContain("--verbose");
|
||||
expect(resolved?.config.resumeArgs).toContain("--setting-sources");
|
||||
expect(resolved?.config.resumeArgs).toContain("user");
|
||||
expect(resolved?.config.resumeArgs).toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).toContain("bypassPermissions");
|
||||
expect(resolved?.config.resumeArgs).not.toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).not.toContain("--dangerously-skip-permissions");
|
||||
});
|
||||
|
||||
@@ -459,12 +448,10 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
expect(resolved?.config.command).toBe("/usr/local/bin/claude");
|
||||
expect(resolved?.config.args).toContain("--setting-sources");
|
||||
expect(resolved?.config.args).toContain("user");
|
||||
expect(resolved?.config.args).toContain("--permission-mode");
|
||||
expect(resolved?.config.args).toContain("bypassPermissions");
|
||||
expect(resolved?.config.args).not.toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).toContain("--setting-sources");
|
||||
expect(resolved?.config.resumeArgs).toContain("user");
|
||||
expect(resolved?.config.resumeArgs).toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).toContain("bypassPermissions");
|
||||
expect(resolved?.config.resumeArgs).not.toContain("--permission-mode");
|
||||
expect(resolved?.config.env).not.toHaveProperty("CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST");
|
||||
expect(resolved?.config.clearEnv).toContain("ANTHROPIC_API_TOKEN");
|
||||
expect(resolved?.config.clearEnv).toContain("ANTHROPIC_BASE_URL");
|
||||
@@ -478,7 +465,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
expect(resolved?.config.clearEnv).toContain("CLAUDE_CODE_USE_COWORK_PLUGINS");
|
||||
});
|
||||
|
||||
it("normalizes legacy skip-permissions overrides to permission-mode bypassPermissions", () => {
|
||||
it("drops legacy skip-permissions overrides without inventing bypassPermissions", () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
@@ -504,11 +491,9 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
|
||||
expect(resolved).not.toBeNull();
|
||||
expect(resolved?.config.args).not.toContain("--dangerously-skip-permissions");
|
||||
expect(resolved?.config.args).toContain("--permission-mode");
|
||||
expect(resolved?.config.args).toContain("bypassPermissions");
|
||||
expect(resolved?.config.args).not.toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).not.toContain("--dangerously-skip-permissions");
|
||||
expect(resolved?.config.resumeArgs).toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).toContain("bypassPermissions");
|
||||
expect(resolved?.config.resumeArgs).not.toContain("--permission-mode");
|
||||
});
|
||||
|
||||
it("keeps explicit permission-mode overrides while removing legacy skip flag", () => {
|
||||
@@ -610,11 +595,11 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
expect(resolved?.config.resumeArgs).toEqual(NORMALIZED_CLAUDE_FALLBACK_RESUME_ARGS);
|
||||
});
|
||||
|
||||
it("falls back to bypassPermissions when a custom override leaves permission-mode without a value", () => {
|
||||
it("drops malformed permission-mode overrides without adding bypassPermissions", () => {
|
||||
const cfg = createClaudeCliOverrideConfig({
|
||||
command: "claude",
|
||||
args: ["-p", "--permission-mode", "--output-format", "stream-json"],
|
||||
resumeArgs: ["-p", "--permission-mode", "--resume", "{sessionId}"],
|
||||
resumeArgs: ["-p", "--permission-mode=--resume", "--resume", "{sessionId}"],
|
||||
});
|
||||
|
||||
const resolved = resolveCliBackendConfig("claude-cli", cfg);
|
||||
@@ -624,7 +609,7 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
expect(resolved?.config.resumeArgs).toEqual(NORMALIZED_CLAUDE_FALLBACK_RESUME_ARGS);
|
||||
});
|
||||
|
||||
it("injects bypassPermissions when custom args omit any permission flag", () => {
|
||||
it("leaves permission-mode unset when custom args omit it", () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
@@ -651,12 +636,10 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
expect(resolved).not.toBeNull();
|
||||
expect(resolved?.config.args).toContain("--setting-sources");
|
||||
expect(resolved?.config.args).toContain("user");
|
||||
expect(resolved?.config.args).toContain("--permission-mode");
|
||||
expect(resolved?.config.args).toContain("bypassPermissions");
|
||||
expect(resolved?.config.args).not.toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).toContain("--setting-sources");
|
||||
expect(resolved?.config.resumeArgs).toContain("user");
|
||||
expect(resolved?.config.resumeArgs).toContain("--permission-mode");
|
||||
expect(resolved?.config.resumeArgs).toContain("bypassPermissions");
|
||||
expect(resolved?.config.resumeArgs).not.toContain("--permission-mode");
|
||||
});
|
||||
|
||||
it("keeps hardened clearEnv defaults when custom claude env overrides are merged", () => {
|
||||
@@ -723,8 +706,6 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
"json",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
]);
|
||||
expect(resolved?.config.resumeArgs).toEqual([
|
||||
"-p",
|
||||
@@ -734,8 +715,6 @@ describe("resolveCliBackendConfig claude-cli defaults", () => {
|
||||
"{sessionId}",
|
||||
"--setting-sources",
|
||||
"user",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
]);
|
||||
expect(resolved?.config.systemPromptArg).toBe("--append-system-prompt");
|
||||
expect(resolved?.config.systemPromptWhen).toBe("first");
|
||||
|
||||
Reference in New Issue
Block a user