Hard-deny: PowerShell patterns + drift-proof the label list

Four new compiled-in hard-deny rules covering PowerShell + cmd.exe
catastrophic patterns (mirror of the POSIX 10):

- Remove-Item / del / rd / ri / rm / erase / rmdir targeting C:\
  or user home / appdata
- Format-Volume / Clear-Disk with any flag (= an invocation, not a
  Get-Help lookup)
- iwr | iex pipe form (PowerShell web-to-execute)
- iex (irm ...) parenthesized form

Universal application — no shell-aware scoping yet. PS cmdlet
identifiers are distinctive enough that bash false-positives are
vanishingly unlikely. Shell-aware policy scoping remains a known
follow-up.

Drift-proof the "Always blocked" label list: backend now exposes
hard_deny_rules() via a new mcp_hard_deny_labels Tauri command, and
PolicyTab loads it at mount instead of hardcoding the list. Avoids
the 11→15 manual sync that would have been needed (and that had
already drifted twice this week).

cargo test --lib: 138 passed; 0 failed (118 prior + 20 new fuzz
cases for rules 11-14; hard_deny_rules_count bumped 10 → 14).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-26 17:14:42 +01:00
parent f3ab54252e
commit 5b970f8b48
6 changed files with 264 additions and 16 deletions

View file

@ -1,18 +1,10 @@
import { useEffect, useState, useRef } from "react";
import { mcpPolicyLoad, mcpPolicySave, type McpPolicy } from "../ipc";
const HARD_DENY_LABELS = [
"rm -rf /",
"rm -rf ~",
"rm -rf /*",
"fork bomb",
"mkfs on device",
"dd to raw disk",
"overwrite system auth file",
"pipe to shell from network",
"chmod -R 777 /",
"find / -delete",
];
import {
mcpHardDenyLabels,
mcpPolicyLoad,
mcpPolicySave,
type McpPolicy,
} from "../ipc";
type Bucket = "deny" | "ask" | "allow";
@ -89,12 +81,14 @@ function RuleList({ bucket, rules, onRemove, onAdd }: RuleListProps) {
export default function PolicyTab() {
const [policy, setPolicy] = useState<McpPolicy | null>(null);
const [hardDenyLabels, setHardDenyLabels] = useState<string[]>([]);
const [dirty, setDirty] = useState(false);
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
useEffect(() => {
void mcpPolicyLoad().then(setPolicy);
void mcpHardDenyLabels().then(setHardDenyLabels);
}, []);
function mutate(updater: (p: McpPolicy) => McpPolicy) {
@ -243,7 +237,7 @@ export default function PolicyTab() {
<div className="policy-hard-deny">
<div className="policy-hard-deny-header">Always blocked (built-in)</div>
<ul className="policy-hard-deny-list">
{HARD_DENY_LABELS.map((label) => (
{hardDenyLabels.map((label) => (
<li key={label} className="policy-hard-deny-rule">
<code>{label}</code>
<span className="policy-hard-deny-badge">Cannot be disabled</span>

View file

@ -180,6 +180,11 @@ export const mcpPolicyLoad = (): Promise<McpPolicy> =>
export const mcpPolicySave = (policy: McpPolicy): Promise<void> =>
invoke("mcp_policy_save", { policy });
/** Compiled-in hard-deny rule labels (the patterns the user CANNOT
* override). Loaded once at PolicyTab mount; backend is the SoT. */
export const mcpHardDenyLabels = (): Promise<string[]> =>
invoke("mcp_hard_deny_labels");
/** Subscribe to MCP action requests from the backend. Each request is a
* tool call the frontend must handle (mutate state) and reply to via
* {@link mcpActionReply}. */