diff --git a/core/tools/policies/fileAccess.ts b/core/tools/policies/fileAccess.ts
index 2d6801f5438..4e3e3af3332 100644
--- a/core/tools/policies/fileAccess.ts
+++ b/core/tools/policies/fileAccess.ts
@@ -16,6 +16,11 @@ export function evaluateFileAccessPolicy(
return "disabled";
}
+ // If tool is in unrestricted mode, skip workspace boundary check
+ if (basePolicy === "allowedUnrestricted") {
+ return "allowedUnrestricted";
+ }
+
// Files within workspace use the base policy (typically "allowedWithoutPermission")
if (isWithinWorkspace) {
return basePolicy;
diff --git a/gui/src/pages/config/components/ToolPolicyItem.tsx b/gui/src/pages/config/components/ToolPolicyItem.tsx
index 5d81c5908f2..98e891b2355 100644
--- a/gui/src/pages/config/components/ToolPolicyItem.tsx
+++ b/gui/src/pages/config/components/ToolPolicyItem.tsx
@@ -141,14 +141,19 @@ export function ToolPolicyItem(props: ToolPolicyItemProps) {
{disabled || policy === "disabled"
? "Excluded"
- : policy === "allowedWithoutPermission"
- ? "Automatic"
- : "Ask First"}
+ : policy === "allowedUnrestricted"
+ ? "Unrestricted"
+ : policy === "allowedWithoutPermission"
+ ? "Automatic"
+ : "Ask First"}
{!disabled && (
+
+ Unrestricted
+
Automatic
diff --git a/gui/src/redux/slices/uiSlice.ts b/gui/src/redux/slices/uiSlice.ts
index 415f927c728..20948a311da 100644
--- a/gui/src/redux/slices/uiSlice.ts
+++ b/gui/src/redux/slices/uiSlice.ts
@@ -97,14 +97,17 @@ export const uiSlice = createSlice({
const setting = state.toolSettings[action.payload];
switch (setting) {
- case "allowedWithPermission":
+ case "allowedUnrestricted":
state.toolSettings[action.payload] = "allowedWithoutPermission";
break;
case "allowedWithoutPermission":
+ state.toolSettings[action.payload] = "allowedWithPermission";
+ break;
+ case "allowedWithPermission":
state.toolSettings[action.payload] = "disabled";
break;
case "disabled":
- state.toolSettings[action.payload] = "allowedWithPermission";
+ state.toolSettings[action.payload] = "allowedUnrestricted";
break;
default:
state.toolSettings[action.payload] = DEFAULT_TOOL_SETTING;
diff --git a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts
index 693eef25f9c..7a05c942396 100644
--- a/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts
+++ b/packages/terminal-security/src/evaluateTerminalCommandSecurity.ts
@@ -27,7 +27,7 @@ type ParsedToken = string | ShellOperator | GlobPattern | CommentToken;
*
* @param basePolicy The base policy configured for the tool
* @param command The command string to evaluate
- * @returns The security policy to apply: 'disabled', 'allowedWithPermission', or 'allowedWithoutPermission'
+ * @returns The security policy to apply: 'disabled', 'allowedWithPermission', 'allowedWithoutPermission', or 'allowedUnrestricted'
*/
export function evaluateTerminalCommandSecurity(
basePolicy: ToolPolicy,
@@ -38,6 +38,11 @@ export function evaluateTerminalCommandSecurity(
return "disabled";
}
+ // If tool is in unrestricted mode, skip all security checks
+ if (basePolicy === "allowedUnrestricted") {
+ return "allowedUnrestricted";
+ }
+
// Handle null/undefined/empty commands
if (!command || typeof command !== "string") {
return basePolicy;
@@ -263,7 +268,10 @@ function evaluateTokens(
}
// Check for obfuscation patterns
- if (hasObfuscationPatterns(originalCommand)) {
+ if (
+ hasObfuscationPatterns(originalCommand) &&
+ mostRestrictivePolicy !== "allowedUnrestricted"
+ ) {
mostRestrictivePolicy = getMostRestrictive(
mostRestrictivePolicy,
"allowedWithPermission",
@@ -283,7 +291,10 @@ function getMostRestrictive(...policies: ToolPolicy[]): ToolPolicy {
if (policies.some((p) => p === "allowedWithPermission")) {
return "allowedWithPermission";
}
- return "allowedWithoutPermission";
+ if (policies.some((p) => p === "allowedWithoutPermission")) {
+ return "allowedWithoutPermission";
+ }
+ return "allowedUnrestricted";
}
/**
diff --git a/packages/terminal-security/src/types.ts b/packages/terminal-security/src/types.ts
index cf3fad0c704..2a830c25cd0 100644
--- a/packages/terminal-security/src/types.ts
+++ b/packages/terminal-security/src/types.ts
@@ -4,4 +4,5 @@
export type ToolPolicy =
| "allowedWithPermission"
| "allowedWithoutPermission"
- | "disabled";
+ | "disabled"
+ | "allowedUnrestricted";
diff --git a/packages/terminal-security/test/terminalCommandSecurity.test.ts b/packages/terminal-security/test/terminalCommandSecurity.test.ts
index 7d2e484765e..169ebd2a5e6 100644
--- a/packages/terminal-security/test/terminalCommandSecurity.test.ts
+++ b/packages/terminal-security/test/terminalCommandSecurity.test.ts
@@ -1,5 +1,5 @@
-import { describe, it, expect } from "vitest";
-import { evaluateTerminalCommandSecurity, ToolPolicy } from "../src/index.js";
+import { describe, expect, it } from "vitest";
+import { evaluateTerminalCommandSecurity } from "../src/index.js";
describe("evaluateTerminalCommandSecurity", () => {
describe("Critical Risk - Always Disabled", () => {
@@ -1864,4 +1864,51 @@ describe("evaluateTerminalCommandSecurity", () => {
});
});
});
+
+ describe("Unrestricted Mode", () => {
+ it("should always return allowedUnrestricted when base policy is allowedUnrestricted", () => {
+ const result = evaluateTerminalCommandSecurity(
+ "allowedUnrestricted",
+ "rm -rf /",
+ );
+ expect(result).toBe("allowedUnrestricted");
+ });
+
+ it("should return allowedUnrestricted for dangerous commands in unrestricted mode", () => {
+ const result = evaluateTerminalCommandSecurity(
+ "allowedUnrestricted",
+ "sudo apt-get update",
+ );
+ expect(result).toBe("allowedUnrestricted");
+ });
+
+ it("should return allowedUnrestricted for command substitution in unrestricted mode", () => {
+ const result = evaluateTerminalCommandSecurity(
+ "allowedUnrestricted",
+ "echo $(rm -rf /)",
+ );
+ expect(result).toBe("allowedUnrestricted");
+ });
+
+ it("should return allowedUnrestricted for obfuscated commands in unrestricted mode", () => {
+ const result = evaluateTerminalCommandSecurity(
+ "allowedUnrestricted",
+ "echo 'cm0gLXJmIC8=' | base64 -d | sh",
+ );
+ expect(result).toBe("allowedUnrestricted");
+ });
+
+ it("should return allowedUnrestricted for multi-line commands in unrestricted mode", () => {
+ const result = evaluateTerminalCommandSecurity(
+ "allowedUnrestricted",
+ "ls\nrm -rf /",
+ );
+ expect(result).toBe("allowedUnrestricted");
+ });
+
+ it("should handle empty command in unrestricted mode", () => {
+ const result = evaluateTerminalCommandSecurity("allowedUnrestricted", "");
+ expect(result).toBe("allowedUnrestricted");
+ });
+ });
});