import { useEffect, useState, useRef } from "react"; import { mcpHardDenyLabels, mcpPolicyLoad, mcpPolicySave, type McpPolicy, } from "../ipc"; type Bucket = "deny" | "ask" | "allow"; const BUCKET_LABELS: Record = { deny: "Deny: blocked outright", ask: "Ask: confirm in a modal", allow: "Silently run", }; interface RuleListProps { bucket: Bucket; rules: string[]; onRemove: (bucket: Bucket, index: number) => void; onAdd: (bucket: Bucket, rule: string) => void; } function RuleList({ bucket, rules, onRemove, onAdd }: RuleListProps) { const [draft, setDraft] = useState(""); const inputRef = useRef(null); function handleAdd() { const trimmed = draft.trim(); if (!trimmed) return; onAdd(bucket, trimmed); setDraft(""); inputRef.current?.focus(); } function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Enter") handleAdd(); } return (
{BUCKET_LABELS[bucket]}
    {rules.length === 0 && (
  • )} {rules.map((r, i) => (
  • {r}
  • ))}
setDraft(e.target.value)} onKeyDown={handleKeyDown} placeholder="e.g. write_pane(git push *)" aria-label={`Add ${bucket} rule`} />
); } export default function PolicyTab() { const [policy, setPolicy] = useState(null); const [hardDenyLabels, setHardDenyLabels] = useState([]); const [dirty, setDirty] = useState(false); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(null); useEffect(() => { void mcpPolicyLoad().then(setPolicy); void mcpHardDenyLabels().then(setHardDenyLabels); }, []); function mutate(updater: (p: McpPolicy) => McpPolicy) { setPolicy((prev) => { if (!prev) return prev; const next = updater(prev); setDirty(true); return next; }); } function handleRemove(bucket: Bucket, index: number) { mutate((p) => ({ ...p, permissions: { ...p.permissions, [bucket]: p.permissions[bucket].filter((_, i) => i !== index), }, })); } function handleAdd(bucket: Bucket, rule: string) { mutate((p) => ({ ...p, permissions: { ...p.permissions, [bucket]: [...p.permissions[bucket], rule], }, })); } function setSshSafeguard( key: "allowOpenSsh" | "autoAllowSpawnedSsh" | "allowAddHost", value: boolean, ) { mutate((p) => ({ ...p, sshSafeguards: { ...p.sshSafeguards, [key]: value }, })); } async function handleSave() { if (!policy || !dirty || saving) return; setSaving(true); setSaveError(null); try { await mcpPolicySave(policy); setDirty(false); } catch (e) { setSaveError(String(e)); } finally { setSaving(false); } } if (!policy) { return

Loading policy…

; } return (

Empty policy = every MCP tool call asks for confirmation. Add rules to bypass the prompt for patterns you trust, or to block patterns outright.

{saveError && ( {saveError} )}
SSH safeguards
{(["deny", "ask", "allow"] as Bucket[]).map((bucket) => ( ))}
Always blocked (built-in)
    {hardDenyLabels.map((label) => (
  • {label} Cannot be disabled
  • ))}

These patterns are caught regardless of policy. Best-effort accident prevention, not a sandbox — see README.

); }