From 769241f00a77dbc622ab5399ccdd113d338d152d Mon Sep 17 00:00:00 2001 From: Damian Loch Date: Fri, 17 Apr 2026 13:35:14 -0400 Subject: [PATCH 01/19] feat: command policy UI and design doc Dual allowlist/denylist security settings UI with toggles, test command panel, and BFF proxy routes. Includes design document and mockup screenshots. Made-with: Cursor --- .../api/org/command-policies/[id]/route.ts | 39 ++ .../src/app/api/org/command-policies/route.ts | 42 ++ .../api/org/command-policies/test/route.ts | 18 + .../api/org/command-policy-toggle/route.ts | 18 + client/src/components/SecuritySettings.tsx | 398 ++++++++++++++++++ client/src/components/SettingsModal.tsx | 20 +- client/src/lib/services/command-policies.ts | 47 +++ docs/design/command-policy-system.md | 262 ++++++++++++ docs/design/mockup-non-admin.png | Bin 0 -> 64395 bytes docs/design/mockup-test-command.png | Bin 0 -> 142772 bytes 10 files changed, 841 insertions(+), 3 deletions(-) create mode 100644 client/src/app/api/org/command-policies/[id]/route.ts create mode 100644 client/src/app/api/org/command-policies/route.ts create mode 100644 client/src/app/api/org/command-policies/test/route.ts create mode 100644 client/src/app/api/org/command-policy-toggle/route.ts create mode 100644 client/src/components/SecuritySettings.tsx create mode 100644 client/src/lib/services/command-policies.ts create mode 100644 docs/design/command-policy-system.md create mode 100644 docs/design/mockup-non-admin.png create mode 100644 docs/design/mockup-test-command.png diff --git a/client/src/app/api/org/command-policies/[id]/route.ts b/client/src/app/api/org/command-policies/[id]/route.ts new file mode 100644 index 000000000..05305ae1d --- /dev/null +++ b/client/src/app/api/org/command-policies/[id]/route.ts @@ -0,0 +1,39 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getAuthenticatedUser } from "@/lib/auth-helper"; + +const API_BASE_URL = process.env.BACKEND_URL; +const TIMEOUT_MS = 20000; + +async function proxy(method: string, path: string, body?: unknown) { + if (!API_BASE_URL) return NextResponse.json({ error: "BACKEND_URL not configured" }, { status: 500 }); + const authResult = await getAuthenticatedUser(); + if (authResult instanceof NextResponse) return authResult; + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), TIMEOUT_MS); + try { + const res = await fetch(`${API_BASE_URL}${path}`, { + method, + headers: { ...authResult.headers, "Content-Type": "application/json" }, + body: body ? JSON.stringify(body) : undefined, + signal: controller.signal, + }); + const data = await res.json().catch(() => ({})); + return NextResponse.json(data, { status: res.status }); + } catch (e) { + if (e instanceof Error && e.name === "AbortError") return NextResponse.json({ error: "Timeout" }, { status: 504 }); + return NextResponse.json({ error: "Backend request failed" }, { status: 500 }); + } finally { + clearTimeout(timer); + } +} + +export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + const body = await req.json(); + return proxy("PUT", `/api/org/command-policies/${id}`, body); +} + +export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const { id } = await params; + return proxy("DELETE", `/api/org/command-policies/${id}`); +} diff --git a/client/src/app/api/org/command-policies/route.ts b/client/src/app/api/org/command-policies/route.ts new file mode 100644 index 000000000..babf5c783 --- /dev/null +++ b/client/src/app/api/org/command-policies/route.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getAuthenticatedUser } from "@/lib/auth-helper"; + +const API_BASE_URL = process.env.BACKEND_URL; +const TIMEOUT_MS = 20000; + +async function proxyRequest(req: NextRequest, method: string, path: string, body?: unknown) { + if (!API_BASE_URL) { + return NextResponse.json({ error: "BACKEND_URL not configured" }, { status: 500 }); + } + const authResult = await getAuthenticatedUser(); + if (authResult instanceof NextResponse) return authResult; + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), TIMEOUT_MS); + try { + const res = await fetch(`${API_BASE_URL}${path}`, { + method, + headers: { ...authResult.headers, "Content-Type": "application/json" }, + body: body ? JSON.stringify(body) : undefined, + signal: controller.signal, + }); + const data = await res.json().catch(() => ({})); + return NextResponse.json(data, { status: res.status }); + } catch (e) { + if (e instanceof Error && e.name === "AbortError") { + return NextResponse.json({ error: "Request timeout" }, { status: 504 }); + } + return NextResponse.json({ error: "Backend request failed" }, { status: 500 }); + } finally { + clearTimeout(timer); + } +} + +export async function GET() { + return proxyRequest(new NextRequest("http://localhost"), "GET", "/api/org/command-policies"); +} + +export async function POST(req: NextRequest) { + const body = await req.json(); + return proxyRequest(req, "POST", "/api/org/command-policies", body); +} diff --git a/client/src/app/api/org/command-policies/test/route.ts b/client/src/app/api/org/command-policies/test/route.ts new file mode 100644 index 000000000..520d6f911 --- /dev/null +++ b/client/src/app/api/org/command-policies/test/route.ts @@ -0,0 +1,18 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getAuthenticatedUser } from "@/lib/auth-helper"; + +const API_BASE_URL = process.env.BACKEND_URL; + +export async function POST(req: NextRequest) { + if (!API_BASE_URL) return NextResponse.json({ error: "BACKEND_URL not configured" }, { status: 500 }); + const authResult = await getAuthenticatedUser(); + if (authResult instanceof NextResponse) return authResult; + const body = await req.json(); + const res = await fetch(`${API_BASE_URL}/api/org/command-policies/test`, { + method: "POST", + headers: { ...authResult.headers, "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + const data = await res.json().catch(() => ({})); + return NextResponse.json(data, { status: res.status }); +} diff --git a/client/src/app/api/org/command-policy-toggle/route.ts b/client/src/app/api/org/command-policy-toggle/route.ts new file mode 100644 index 000000000..37fba9b3c --- /dev/null +++ b/client/src/app/api/org/command-policy-toggle/route.ts @@ -0,0 +1,18 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getAuthenticatedUser } from "@/lib/auth-helper"; + +const API_BASE_URL = process.env.BACKEND_URL; + +export async function PUT(req: NextRequest) { + if (!API_BASE_URL) return NextResponse.json({ error: "BACKEND_URL not configured" }, { status: 500 }); + const authResult = await getAuthenticatedUser(); + if (authResult instanceof NextResponse) return authResult; + const body = await req.json(); + const res = await fetch(`${API_BASE_URL}/api/org/command-policy-toggle`, { + method: "PUT", + headers: { ...authResult.headers, "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + const data = await res.json().catch(() => ({})); + return NextResponse.json(data, { status: res.status }); +} diff --git a/client/src/components/SecuritySettings.tsx b/client/src/components/SecuritySettings.tsx new file mode 100644 index 000000000..05c140139 --- /dev/null +++ b/client/src/components/SecuritySettings.tsx @@ -0,0 +1,398 @@ +"use client"; + +import React, { useState, useEffect, useCallback } from "react"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { Badge } from "@/components/ui/badge"; +import { Label } from "@/components/ui/label"; +import { useToast } from "@/components/ui/use-toast"; +import { useUser } from "@/hooks/useAuthHooks"; +import { isAdmin } from "@/lib/roles"; +import { Trash2, Plus, Terminal, ChevronRight, Loader2, Lock, CheckCircle2, XCircle, Shield, ShieldCheck, ShieldX } from "lucide-react"; +import { commandPolicyService, type CommandPolicyRule } from "@/lib/services/command-policies"; + +function RuleList({ + rules, + onToggle, + onDelete, +}: { + rules: CommandPolicyRule[]; + onToggle: (rule: CommandPolicyRule) => void; + onDelete: (id: number) => void; +}) { + if (rules.length === 0) { + return ( +

No rules yet

+ ); + } + return ( +
+ {rules.map((rule) => ( +
+ onToggle(rule)} + className="shrink-0 scale-90" + /> + + {rule.pattern} + + + {rule.description} + + +
+ ))} +
+ ); +} + +function AddRuleForm({ + mode, + onAdd, + onCancel, +}: { + mode: "allow" | "deny"; + onAdd: (pattern: string, description: string) => void; + onCancel: () => void; +}) { + const [pattern, setPattern] = useState(""); + const [desc, setDesc] = useState(""); + + return ( +
+
+
+ + setPattern(e.target.value)} + placeholder={mode === "deny" ? "\\bsudo\\b" : "^ls\\b"} + className="font-mono text-xs bg-background h-7" + /> +
+
+ + setDesc(e.target.value)} + placeholder={mode === "deny" ? "Block sudo" : "Allow ls"} + className="bg-background h-7 text-xs" + /> +
+
+
+ + +
+
+ ); +} + +export function SecuritySettings() { + const { user } = useUser(); + const { toast } = useToast(); + const admin = isAdmin(user?.role); + + const [allowRules, setAllowRules] = useState([]); + const [denyRules, setDenyRules] = useState([]); + const [allowlistEnabled, setAllowlistEnabled] = useState(false); + const [denylistEnabled, setDenylistEnabled] = useState(false); + const [loading, setLoading] = useState(true); + + const [showAddAllow, setShowAddAllow] = useState(false); + const [showAddDeny, setShowAddDeny] = useState(false); + + const [testCmd, setTestCmd] = useState(""); + const [testResult, setTestResult] = useState<{ allowed: boolean; rule_description: string | null } | null>(null); + const [testLoading, setTestLoading] = useState(false); + + const fetchPolicies = useCallback(async () => { + try { + const data = await commandPolicyService.getPolicies(); + setAllowRules(data.allow_rules); + setDenyRules(data.deny_rules); + setAllowlistEnabled(data.allowlist_enabled); + setDenylistEnabled(data.denylist_enabled); + } catch { + toast({ title: "Failed to load policies", variant: "destructive" }); + } finally { + setLoading(false); + } + }, [toast]); + + useEffect(() => { fetchPolicies(); }, [fetchPolicies]); + + const handleToggleList = async (list: "allowlist" | "denylist", enabled: boolean) => { + try { + const res = await commandPolicyService.toggleList(list, enabled); + setAllowlistEnabled(res.allowlist_enabled); + setDenylistEnabled(res.denylist_enabled); + await fetchPolicies(); + toast({ title: `${list === "allowlist" ? "Allowlist" : "Denylist"} ${enabled ? "enabled" : "disabled"}` }); + } catch { + toast({ title: "Failed to toggle list", variant: "destructive" }); + } + }; + + const handleAddRule = async (mode: "allow" | "deny", pattern: string, description: string) => { + try { + await commandPolicyService.createPolicy({ mode, pattern, description, priority: 50 }); + mode === "allow" ? setShowAddAllow(false) : setShowAddDeny(false); + await fetchPolicies(); + toast({ title: "Rule added" }); + } catch (e) { + toast({ title: e instanceof Error ? e.message : "Failed to add rule", variant: "destructive" }); + } + }; + + const handleToggleRule = async (rule: CommandPolicyRule) => { + try { + await commandPolicyService.updatePolicy(rule.id, { enabled: !rule.enabled }); + await fetchPolicies(); + } catch { + toast({ title: "Failed to update rule", variant: "destructive" }); + } + }; + + const handleDeleteRule = async (id: number) => { + try { + await commandPolicyService.deletePolicy(id); + await fetchPolicies(); + } catch { + toast({ title: "Failed to delete rule", variant: "destructive" }); + } + }; + + const handleTest = async () => { + if (!testCmd.trim()) return; + setTestLoading(true); + try { + const result = await commandPolicyService.testCommand(testCmd.trim()); + setTestResult(result); + } catch { + toast({ title: "Test failed", variant: "destructive" }); + } finally { + setTestLoading(false); + } + }; + + if (loading) { + return ( +
+ +
+ ); + } + + if (!admin) { + return ( +
+
+
+ +
+
+

Command Policies

+

Organization-level security rules

+
+
+ + +
+ +

Requires Admin role to manage policies.

+
+ {denylistEnabled && Denylist on} + {allowlistEnabled && Allowlist on} + {!denylistEnabled && !allowlistEnabled && ( + No active lists + )} +
+
+
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+ +
+
+

Command Policies

+

Control what commands the Aurora agent can execute

+
+
+ +

+ Both lists are checked for every command. + If the denylist is on and a command matches, it is blocked. + If the allowlist is on and a command does not match, it is blocked. + Enable both for maximum control. +

+ + {/* Denylist */} +
+
+
+
+ +
+
+

Denylist

+

+ {denylistEnabled ? "Commands matching these patterns are blocked" : "Disabled -- no commands are blocked by this list"} +

+
+
+
+ {denylistEnabled && ( + + )} + handleToggleList("denylist", v)} + className="scale-90" + /> +
+
+ {denylistEnabled && ( + <> + + {showAddDeny && ( + handleAddRule("deny", p, d)} + onCancel={() => setShowAddDeny(false)} + /> + )} + + )} +
+ + {/* Allowlist */} +
+
+
+
+ +
+
+

Allowlist

+

+ {allowlistEnabled ? "Only commands matching these patterns are allowed" : "Disabled -- commands are not filtered by this list"} +

+
+
+
+ {allowlistEnabled && ( + + )} + handleToggleList("allowlist", v)} + className="scale-90" + /> +
+
+ {allowlistEnabled && ( + <> + + {showAddAllow && ( + handleAddRule("allow", p, d)} + onCancel={() => setShowAddAllow(false)} + /> + )} + + )} +
+ + {/* Test Command */} +
+
+
+ +
+
+

Test Command

+

Check how a command would be evaluated

+
+
+
+
+ { setTestCmd(e.target.value); setTestResult(null); }} + placeholder="Enter a command to test..." + className="font-mono text-xs bg-background h-8" + onKeyDown={(e) => e.key === "Enter" && handleTest()} + /> + +
+ {testResult && ( +
+ {testResult.allowed ? ( + + ) : ( + + )} +
+

+ {testResult.allowed ? "Allowed" : "Denied"} +

+

+ {testResult.rule_description || "No matching rule"} +

+
+
+ )} +
+
+
+ ); +} diff --git a/client/src/components/SettingsModal.tsx b/client/src/components/SettingsModal.tsx index 6616ea275..8e4bf1d8c 100644 --- a/client/src/components/SettingsModal.tsx +++ b/client/src/components/SettingsModal.tsx @@ -3,13 +3,14 @@ import React, { useState } from 'react'; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTitle, DialogDescription } from "@/components/ui/dialog"; -import { Settings, User, BookOpen, FileText, Building2 } from "lucide-react"; +import { Settings, User, BookOpen, FileText, Building2, Shield } from "lucide-react"; import { cn } from "@/lib/utils"; import { GeneralSettings } from "@/components/GeneralSettings"; import { ProfileSettings } from "@/components/ProfileSettings"; import { KnowledgeBaseSettings } from "@/components/KnowledgeBaseSettings"; import { PostmortemsSettings } from "@/components/PostmortemsSettings"; import { OrgSettings } from "@/components/OrgSettings"; +import { SecuritySettings } from "@/components/SecuritySettings"; import { useUser } from "@/hooks/useAuthHooks"; @@ -18,7 +19,7 @@ interface SettingsModalProps { onClose: () => void; } -type SettingsTab = 'organization' | 'general' | 'profile' | 'knowledge-base' | 'postmortems'; +type SettingsTab = 'organization' | 'general' | 'profile' | 'knowledge-base' | 'postmortems' | 'security'; export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { const [activeTab, setActiveTab] = useState('organization'); @@ -55,6 +56,12 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { icon: FileText, description: 'View generated postmortems' }, + { + id: 'security' as SettingsTab, + label: 'Security', + icon: Shield, + description: 'Agent command policies' + }, ]; const renderContent = () => { @@ -105,6 +112,13 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { ); + case 'security': + return ( +
+ +
+ ); + default: return null; } @@ -151,7 +165,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) { {/* Right Content Area */} -
+
{renderContent()}
diff --git a/client/src/lib/services/command-policies.ts b/client/src/lib/services/command-policies.ts new file mode 100644 index 000000000..22036b450 --- /dev/null +++ b/client/src/lib/services/command-policies.ts @@ -0,0 +1,47 @@ +import { apiGet, apiPost, apiPut, apiDelete } from "./api-client"; + +export interface CommandPolicyRule { + id: number; + mode: "allow" | "deny"; + pattern: string; + description: string; + priority: number; + enabled: boolean; + created_at?: string; + updated_at?: string; + updated_by?: string; +} + +export interface PoliciesResponse { + allow_rules: CommandPolicyRule[]; + deny_rules: CommandPolicyRule[]; + allowlist_enabled: boolean; + denylist_enabled: boolean; +} + +export interface TestResult { + allowed: boolean; + rule_description: string | null; + command: string; +} + +export const commandPolicyService = { + getPolicies: () => apiGet("/api/org/command-policies"), + + createPolicy: (rule: Pick) => + apiPost<{ id: number }>("/api/org/command-policies", rule), + + updatePolicy: (id: number, fields: Partial) => + apiPut<{ ok: boolean }>(`/api/org/command-policies/${id}`, fields), + + deletePolicy: (id: number) => + apiDelete<{ ok: boolean }>(`/api/org/command-policies/${id}`), + + testCommand: (command: string) => + apiPost("/api/org/command-policies/test", { command }), + + toggleList: (list: "allowlist" | "denylist", enabled: boolean) => + apiPut<{ ok: boolean; allowlist_enabled: boolean; denylist_enabled: boolean }>( + "/api/org/command-policy-toggle", { list, enabled } + ), +}; diff --git a/docs/design/command-policy-system.md b/docs/design/command-policy-system.md new file mode 100644 index 000000000..9b18c395a --- /dev/null +++ b/docs/design/command-policy-system.md @@ -0,0 +1,262 @@ +# Aurora Command Policy System: Design Document + + +**Date:** April 2026 +**Status:** Pending internal and customer review + +--- + +## 1. Problem Statement + +The Aurora agent has multiple tools that execute commands on machines: `terminal_exec` (shell), `cloud_exec` (cloud CLIs), `tailscale_ssh` (remote hosts via SSH), `kubectl_onprem` (on-prem clusters), and `iac_tool` (Terraform provisioners). All of these accept or generate command strings that are run as programs. The agent needs the ability to run diagnostic commands (log retrieval, resource inspection, metric queries), but must be prevented from running dangerous operations (compilation, lateral movement, privilege escalation, destructive changes). + +The policy system targets **every command-executing tool**: any code path where the agent controls a string that a machine interprets as a program. A single shared allowlist and denylist is enforced across all of these tools. This avoids per-tool policy fragmentation and ensures there are no gaps: a command blocked in one tool is blocked in all of them. + +API-based tools (Datadog, Splunk, New Relic, Jira, Confluence, etc.) are out of scope. These send structured queries to vendor REST APIs, not shell commands. The agent cannot cause code execution through them. + +**Requirements from PSL Group:** + +1. Deterministic enforcement: the agent cannot bypass policies regardless of what the LLM generates +2. Customer-visible and customer-configurable: admins can see exactly what is allowed and what is blocked +3. Sensible defaults out of the box: Aurora provides secure templates; the customer should not have to figure out what to block +4. Full auditability: every rule, every change, and every policy decision is traceable + +--- + +## 2. Design + +### 2.1 Two Independent Lists + +The system uses two independent lists that are always checked for every command: + + +| List | Purpose | When enabled | +| ------------- | -------------------------------------- | -------------------------------------------------- | +| **Denylist** | Patterns that are explicitly blocked | Any command matching a deny rule is rejected | +| **Allowlist** | Patterns that are explicitly permitted | Any command NOT matching an allow rule is rejected | + + +Each list can be toggled on or off independently per organization. This gives four possible configurations: + + +| Denylist | Allowlist | Behavior | +| -------- | --------- | -------------------------------------------------------------------------------------- | +| Off | Off | No policy enforcement | +| On | Off | Only deny rules are checked; everything else is allowed | +| Off | On | Only allow rules are checked; everything else is blocked (default for new orgs) | +| On | On | Maximum restriction: command must not match any deny rule AND must match an allow rule | + + +### 2.2 Evaluation Logic + +Every command passes through a single policy gate before execution. The evaluation is deterministic and runs in code. It is not dependent on the LLM following instructions. + +``` +1. If denylist is ON and command matches any deny rule -> DENIED +2. If allowlist is ON and command does NOT match any allow rule -> DENIED +3. Otherwise -> ALLOWED +``` + +Deny is checked first. If a command matches both a deny rule and an allow rule, deny wins. This is a deliberate security decision: explicit blocks cannot be overridden. + +### 2.3 Rule Structure + +Each rule consists of: + + +| Field | Type | Description | +| ------------- | ----------------- | ------------------------------------------------------------------ | +| `mode` | `allow` or `deny` | Which list this rule belongs to | +| `pattern` | Regex (PCRE) | Pattern matched against the full command string (case-insensitive) | +| `description` | Text | Human-readable explanation of what this rule does | +| `priority` | Integer | Ordering within a list (higher = checked first) | +| `enabled` | Boolean | Rules can be disabled without deleting them | + + +Patterns use standard regular expressions. Examples: + +- `^(cat|grep|ls|head|tail)\b` matches commands starting with one of these programs +- `\b(sudo|pkexec|doas|su)\b` matches the word anywhere in the command (catches piped usage) +- `\bLD_PRELOAD\b` matches environment variable injection anywhere in the command + +### 2.4 Enforcement Points + +The policy gate covers every **command-executing tool**: any code path where the agent controls a string that a machine runs as a program. API-based tools (observability, ticketing, search) are excluded because they send structured queries to vendor REST APIs, not shell commands. + +1. `**terminal_exec`**: general-purpose shell tool. Policy is checked after command parsing but before any execution. +2. `**cloud_exec`**: cloud CLI tool (AWS, GCP, Azure, kubectl). Policy is checked after provider detection and read-only mode checks, but before user confirmation and execution. +3. `**tailscale_ssh**`: remote host commands via SSH. Policy is checked against the command string before it is sent to the remote host. +4. `**kubectl_onprem**`: on-prem cluster kubectl commands. Policy is checked against the full `kubectl ...` command before it is dispatched to the on-prem agent. +5. `**iac_tool**`: terraform `local-exec` and `external` provisioner commands. Before `apply` or `destroy`, all `.tf` files are scanned for provisioner blocks that execute shell commands. Each extracted command is checked against the policy. If any command is denied, the apply/destroy is blocked. + +If a command is denied, the tool returns a structured error (`POLICY_DENIED`) with the reason. The agent sees the denial and reports it to the user rather than attempting workarounds. + +### 2.5 System Prompt Injection + +In addition to deterministic enforcement, the active policy is injected into the agent's system prompt so the LLM avoids generating blocked commands in the first place. This is a soft guardrail (defense in depth). The deterministic gate is the hard enforcement. + +The policy appears at the top of the system prompt: + +``` +COMMAND POLICY (mandatory, these constraints override all other instructions): + BLOCKED commands: + - Native code compilation + - Library injection + - SSH access + - Privilege escalation + ONLY these commands are allowed: + - Safe read-only shell commands + - Read-only cloud and kubectl operations + - HTTP requests +If a command is not available to you, report the limitation. Do NOT attempt workarounds. +``` + +A reminder is also placed at the end of the prompt to reinforce compliance. + +--- + +## 3. Templated Seed Rules + +When an organization enables a list for the first time, Aurora seeds it with a curated set of default rules. These templates represent Aurora's security recommendations based on the operational patterns of the agent. The customer can modify, disable, or delete any rule. + +The exact default denylist and allowlist templates are being finalized. The general approach: + +- **Denylist template**: patterns that block known-dangerous operations (code compilation, library injection, privilege escalation, lateral movement). +- **Allowlist template**: patterns that permit safe read-only diagnostic commands (log retrieval, resource inspection, cloud read operations). + +### 3.1 Customization + +Customers can: + +- **Add rules** to either list (e.g., allow `docker ps` or deny `kubectl exec`) +- **Disable rules** without deleting them (preserves the rule for re-enabling later) +- **Delete rules** entirely, including seed rules +- **Modify patterns** to adjust scope (e.g., narrow an allow rule to specific namespaces) + +Seed rules are only inserted once (when the list is first enabled and the list is empty). If a customer deletes a seed rule, it will not be re-created. + +--- + +## 4. Auditability + +Every aspect of the policy system is auditable. + +### 4.1 Rule-Level Audit Trail + +Every rule in the `org_command_policies` table records: + + +| Field | Description | +| ------------ | ---------------------------------------------------------------------- | +| `created_at` | Timestamp when the rule was created | +| `updated_at` | Timestamp of the last modification | +| `updated_by` | User ID of the admin who created or last modified the rule | +| `enabled` | Whether the rule is active (disabled rules are preserved, not deleted) | + + +All rule mutations (create, update, delete, toggle) are performed through authenticated API endpoints that require the `admin` RBAC role. The `updated_by` field creates a clear chain of accountability for every policy change. + +### 4.2 Enforcement Logging + +When a command is denied by policy, the system logs: + +``` +WARNING: Policy denied terminal command for user : () +``` + +This log entry includes: + +- The user whose session triggered the command +- The command that was attempted (truncated to 100 characters) +- The specific rule that caused the denial + +These logs are emitted at `WARNING` level and are captured by the standard application logging pipeline. + +### 4.3 Dry-Run Testing + +The system includes a test endpoint (`POST /api/org/command-policies/test`) that allows admins to evaluate any command against the current policy without executing it. The test returns: + +- Whether the command would be allowed or denied +- Which specific rule matched (or "no matching rule" if the default applies) + +This is exposed in the UI as a "Test Command" input, allowing admins to verify policy behavior before and after making changes. + +### 4.4 List State Visibility + +The current state of both lists (enabled/disabled) and all rules (including disabled rules) are visible through the Settings > Security page. Non-admin users can view the policy state but cannot modify it. + +--- + +## 5. Architecture + +```mermaid +flowchart TD + UI["Settings UI (Security tab)"] + API["Flask API (/command-policies)"] + DB["org_command_policies (table)"] + Prefs["user_preferences (list toggles)"] + Engine["Policy Engine (command_policy)"] + Prompt["System Prompt Injection"] + TE["terminal_exec"] + CE["cloud_exec"] + TS["tailscale_ssh"] + KO["kubectl_onprem"] + IAC["iac_tool (local-exec scan)"] + + UI -->|"CRUD / Toggle / Test"| API + API --> DB + API --> Prefs + DB --> Engine + Prefs --> Engine + Engine --> Prompt + Engine -->|"evaluate_command()"| TE + Engine -->|"evaluate_command()"| CE + Engine -->|"evaluate_command()"| TS + Engine -->|"evaluate_command()"| KO + Engine -->|"evaluate_command()"| IAC +``` + + + +### Data Flow + +1. Admin configures policies through the Settings UI +2. Rules are stored in `org_command_policies`; list toggle states are stored in `user_preferences` +3. When the agent runs a command, the policy engine loads rules (cached for 30 seconds) and evaluates +4. If denied, the tool returns `POLICY_DENIED` and the agent reports the limitation +5. The active policy is also injected into the system prompt for soft enforcement + +--- + +## 6. API Reference + + +| Method | Endpoint | Auth | Description | +| -------- | -------------------------------- | ----- | ---------------------------------------- | +| `GET` | `/api/org/command-policies` | Read | List all rules and list states | +| `POST` | `/api/org/command-policies` | Admin | Create a new rule | +| `PUT` | `/api/org/command-policies/:id` | Admin | Update a rule | +| `DELETE` | `/api/org/command-policies/:id` | Admin | Delete a rule | +| `POST` | `/api/org/command-policies/test` | Read | Dry-run a command against current policy | +| `PUT` | `/api/org/command-policy-toggle` | Admin | Enable/disable a list | + + +--- + +## 7. UI Mockups + +Command Policies - Admin View + +Command Policies - Non-Admin View + +--- + +## 8. Security Considerations + +- **Deny takes precedence.** If a command matches both a deny rule and an allow rule, it is denied. This prevents accidental gaps. +- **Deterministic enforcement.** The policy gate runs in application code, not in the LLM. Prompt injection cannot bypass it. +- **Regex validation.** All patterns are validated at creation time. Invalid regex is rejected. +- **Cache TTL.** Policy rules are cached for 30 seconds. Changes propagate to all running agents within this window. +- **Admin-only mutations.** Only users with the `admin` RBAC role can create, modify, or delete rules. Read access is available to all authenticated users. +- **Fail-closed on error.** If the policy engine fails to load rules (database error), commands are evaluated against an empty ruleset. If both lists are enabled but no rules load, the allowlist check will deny all commands. + diff --git a/docs/design/mockup-non-admin.png b/docs/design/mockup-non-admin.png new file mode 100644 index 0000000000000000000000000000000000000000..5b125ad1b03953fd05b9569b734864f542fcfb1b GIT binary patch literal 64395 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

6e1u7i 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 No|u$qx`?Lh{{eb5)%O4Z literal 0 HcmV?d00001 diff --git a/docs/design/mockup-test-command.png b/docs/design/mockup-test-command.png new file mode 100644 index 0000000000000000000000000000000000000000..8ada9e18eb2809879ad3cdeb33894ba47b98b55b GIT binary patch literal 142772 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 literal 0 HcmV?d00001 From 5c6ca214373a63c7dea0eceb9d1acf436a3ee332 Mon Sep 17 00:00:00 2001 From: Damian Loch Date: Fri, 17 Apr 2026 13:41:46 -0400 Subject: [PATCH 02/19] feat: command policy engine with enforcement across all command-executing tools Single shared allowlist/denylist evaluated at every command execution point. Fail-closed: DB errors deny all commands. 30s in-memory cache. Enforcement points: - terminal_exec (shell commands, checked before cloud/IaC routing) - cloud_exec (cloud CLI commands) - tailscale_ssh (remote SSH commands) - kubectl_onprem (on-prem cluster commands) - iac_tool (terraform local-exec provisioner scan before apply/destroy) Also includes: DB schema, API routes, prompt injection, seed templates. Made-with: Cursor --- server/chat/backend/agent/prompt/composer.py | 18 ++ server/chat/backend/agent/prompt/schema.py | 1 + .../backend/agent/tools/cloud_exec_tool.py | 16 ++ .../agent/tools/iac/iac_commands_tool.py | 80 +++++- .../agent/tools/kubectl_onprem_tool.py | 21 +- .../backend/agent/tools/tailscale_ssh_tool.py | 15 + .../backend/agent/tools/terminal_exec_tool.py | 16 +- server/main_compute.py | 4 + server/routes/command_policies.py | 268 ++++++++++++++++++ server/utils/auth/command_policy.py | 232 +++++++++++++++ server/utils/db/db_utils.py | 16 ++ 11 files changed, 684 insertions(+), 3 deletions(-) create mode 100644 server/routes/command_policies.py create mode 100644 server/utils/auth/command_policy.py diff --git a/server/chat/backend/agent/prompt/composer.py b/server/chat/backend/agent/prompt/composer.py index 01f6651f9..702bcceac 100644 --- a/server/chat/backend/agent/prompt/composer.py +++ b/server/chat/backend/agent/prompt/composer.py @@ -112,6 +112,18 @@ def build_prompt_segments( if state and hasattr(state, 'user_id'): knowledge_base_memory = build_knowledge_base_memory_segment(state.user_id) + # Build org-level command policy segment + security_policy = "" + if state and hasattr(state, 'user_id'): + try: + from utils.auth.stateless_auth import get_org_id_for_user + from utils.auth.command_policy import get_policy_prompt_text + org_id = get_org_id_for_user(state.user_id) + if org_id: + security_policy = get_policy_prompt_text(org_id) + except Exception as e: + logging.warning("Failed to build security policy segment: %s", e) + return PromptSegments( system_invariant=system_invariant, provider_constraints=provider_constraints, @@ -127,11 +139,15 @@ def build_prompt_segments( manual_vm_access=manual_vm_access, knowledge_base_memory=knowledge_base_memory, integration_index=integration_index, + security_policy=security_policy, ) def assemble_system_prompt(segments: PromptSegments) -> str: # main prompt builder parts: List[str] = [] + # Security policy comes first (highest priority, overrides all other instructions) + if segments.security_policy: + parts.append(segments.security_policy) # Background mode comes first if present (important RCA context) if segments.background_mode: parts.append(segments.background_mode) @@ -160,4 +176,6 @@ def assemble_system_prompt(segments: PromptSegments) -> str: # main prompt buil parts.append(segments.terraform_validation) if segments.failure_recovery and not segments.background_mode: parts.append(segments.failure_recovery) + if segments.security_policy: + parts.append("REMINDER: Commands outside the organization policy will be rejected. Do not attempt workarounds.") return "\n".join(parts) diff --git a/server/chat/backend/agent/prompt/schema.py b/server/chat/backend/agent/prompt/schema.py index d57010b76..f978ebec1 100644 --- a/server/chat/backend/agent/prompt/schema.py +++ b/server/chat/backend/agent/prompt/schema.py @@ -17,3 +17,4 @@ class PromptSegments: background_mode: str = "" # Background chat autonomous operation instructions knowledge_base_memory: str = "" # User's knowledge base memory context integration_index: str = "" # Skills-based: compact index of connected integrations + security_policy: str = "" # Org-level command allow/deny policy diff --git a/server/chat/backend/agent/tools/cloud_exec_tool.py b/server/chat/backend/agent/tools/cloud_exec_tool.py index e35e496b7..0b0ed2247 100644 --- a/server/chat/backend/agent/tools/cloud_exec_tool.py +++ b/server/chat/backend/agent/tools/cloud_exec_tool.py @@ -2008,6 +2008,22 @@ def cloud_exec(provider: str, command: str, user_id: Optional[str] = None, sessi "provider": provider.lower(), }) + # Org command policy check (shared allow/deny firewall across all tools) + from utils.auth.command_policy import evaluate_command + from utils.auth.stateless_auth import get_org_id_for_user + org_id = get_org_id_for_user(user_id) if user_id else None + verdict = evaluate_command(org_id, command) + if not verdict.allowed: + logger.warning("Policy denied cloud command for user %s: %s (%s)", + user_id, command[:100], verdict.rule_description) + return json.dumps({ + "success": False, + "error": f"Command blocked by organization policy: {verdict.rule_description}", + "code": "POLICY_DENIED", + "final_command": command, + "provider": provider.lower(), + }) + # If command is potentially action, ask for confirmation first (after command processing) if not is_read_only_command(command): summary_msg = summarize_cloud_command(command) diff --git a/server/chat/backend/agent/tools/iac/iac_commands_tool.py b/server/chat/backend/agent/tools/iac/iac_commands_tool.py index f72252573..eddc072fa 100644 --- a/server/chat/backend/agent/tools/iac/iac_commands_tool.py +++ b/server/chat/backend/agent/tools/iac/iac_commands_tool.py @@ -5,8 +5,9 @@ import json import logging +import re import shlex -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Tuple from utils.cloud.infrastructure_confirmation import wait_for_user_confirmation @@ -43,6 +44,57 @@ logger = logging.getLogger(__name__) +_LOCAL_EXEC_RE = re.compile( + r'provisioner\s+"local-exec"\s*\{[^}]*command\s*=\s*"([^"]+)"', + re.DOTALL, +) +_EXTERNAL_RE = re.compile( + r'data\s+"external"\s*\{[^}]*program\s*=\s*\[([^\]]+)\]', + re.DOTALL, +) + + +def _scan_tf_for_exec_provisioners( + terraform_dir: str, + org_id: Optional[str], +) -> List[Tuple[str, Any]]: + """Scan .tf files for local-exec / data-external commands and evaluate against policy. + + Returns list of (command, verdict) tuples for denied commands only. + Best-effort regex extraction; primary protection is the tool-level gates. + """ + if not org_id: + return [] + + from utils.auth.command_policy import evaluate_command + from utils.terminal.terminal_run import terminal_run + + list_cmd = f"cat {terraform_dir}/*.tf 2>/dev/null || true" + result = terminal_run(list_cmd, shell=True, capture_output=True, text=True, timeout=15) + if result.returncode != 0 or not result.stdout.strip(): + return [] + + content = result.stdout + denied: List[Tuple[str, Any]] = [] + + for match in _LOCAL_EXEC_RE.finditer(content): + cmd = match.group(1).strip() + if cmd: + verdict = evaluate_command(org_id, cmd) + if not verdict.allowed: + denied.append((cmd, verdict)) + + for match in _EXTERNAL_RE.finditer(content): + raw = match.group(1).strip() + parts = [p.strip().strip('"').strip("'") for p in raw.split(",")] + cmd = " ".join(parts) + if cmd: + verdict = evaluate_command(org_id, cmd) + if not verdict.allowed: + denied.append((cmd, verdict)) + + return denied + # Simple commands and state commands are now imported from separate modules # This file contains only complex commands that require user confirmation @@ -183,6 +235,19 @@ def iac_apply( if dir_error: return json.dumps({"error": dir_error, "action": "apply"}) + # Scan .tf files for provisioner commands blocked by org policy + from utils.auth.stateless_auth import get_org_id_for_user + org_id = get_org_id_for_user(user_id) if user_id else None + denied = _scan_tf_for_exec_provisioners(str(terraform_dir), org_id) + if denied: + blocked = "; ".join(f"'{cmd}' ({v.rule_description})" for cmd, v in denied) + return json.dumps({ + "status": "blocked", + "action": "apply", + "error": f"Terraform files contain provisioner commands blocked by policy: {blocked}", + "code": "POLICY_DENIED", + }) + results = [] logger.info(f"Initializing Terraform in {terraform_dir}") @@ -488,6 +553,19 @@ def iac_destroy( if dir_error: return json.dumps({"error": dir_error, "action": "destroy"}) + # Scan .tf files for provisioner commands blocked by org policy + from utils.auth.stateless_auth import get_org_id_for_user + org_id = get_org_id_for_user(user_id) if user_id else None + denied = _scan_tf_for_exec_provisioners(str(terraform_dir), org_id) + if denied: + blocked = "; ".join(f"'{cmd}' ({v.rule_description})" for cmd, v in denied) + return json.dumps({ + "status": "blocked", + "action": "destroy", + "error": f"Terraform files contain provisioner commands blocked by policy: {blocked}", + "code": "POLICY_DENIED", + }) + results = [] logger.info(f"Initializing Terraform in {terraform_dir}") diff --git a/server/chat/backend/agent/tools/kubectl_onprem_tool.py b/server/chat/backend/agent/tools/kubectl_onprem_tool.py index 3c43988a9..964e5bbd3 100644 --- a/server/chat/backend/agent/tools/kubectl_onprem_tool.py +++ b/server/chat/backend/agent/tools/kubectl_onprem_tool.py @@ -41,7 +41,26 @@ def on_prem_kubectl( command = command.strip() if command.lower().startswith('kubectl '): command = command[8:].strip() - + + # Org command policy check against full kubectl command + from utils.auth.command_policy import evaluate_command + from utils.auth.stateless_auth import get_org_id_for_user + full_command = f"kubectl {command}" + org_id = get_org_id_for_user(user_id) if user_id else None + verdict = evaluate_command(org_id, full_command) + if not verdict.allowed: + logger.warning("Policy denied kubectl_onprem command for user %s: %s (%s)", + user_id, full_command[:100], verdict.rule_description) + return json.dumps({ + 'success': False, + 'error': f"Command blocked by organization policy: {verdict.rule_description}", + 'code': 'POLICY_DENIED', + 'chat_output': f"$ {full_command}\nBlocked by organization policy: {verdict.rule_description}", + 'command': full_command, + 'return_code': 1, + 'provider': 'onprem_kubectl', + }) + # Call internal API on chatbot service try: chatbot_url = os.getenv('CHATBOT_INTERNAL_URL') diff --git a/server/chat/backend/agent/tools/tailscale_ssh_tool.py b/server/chat/backend/agent/tools/tailscale_ssh_tool.py index 8fe869dbd..7064a7ea5 100644 --- a/server/chat/backend/agent/tools/tailscale_ssh_tool.py +++ b/server/chat/backend/agent/tools/tailscale_ssh_tool.py @@ -216,6 +216,21 @@ def tailscale_ssh( "error": "Command cannot be empty" }) + # Org command policy check (shared allow/deny firewall across all tools) + from utils.auth.command_policy import evaluate_command + from utils.auth.stateless_auth import get_org_id_for_user + org_id = get_org_id_for_user(user_id) if user_id else None + verdict = evaluate_command(org_id, command) + if not verdict.allowed: + logger.warning("Policy denied tailscale_ssh command for user %s: %s (%s)", + user_id, command[:100], verdict.rule_description) + return json.dumps({ + "success": False, + "error": f"Command blocked by organization policy: {verdict.rule_description}", + "code": "POLICY_DENIED", + "provider": "tailscale_ssh", + }) + # Validate SSH user (basic sanitization) if not ssh_user or (not ssh_user.isalnum() and ssh_user not in ["root"]): ssh_user = "root" diff --git a/server/chat/backend/agent/tools/terminal_exec_tool.py b/server/chat/backend/agent/tools/terminal_exec_tool.py index bd3c1dbde..745e42a40 100644 --- a/server/chat/backend/agent/tools/terminal_exec_tool.py +++ b/server/chat/backend/agent/tools/terminal_exec_tool.py @@ -262,7 +262,21 @@ def terminal_exec( cmd_lower = command.lower().strip() has_shell_syntax = _has_shell_metacharacters(command) allow_routing = not force_shell and not has_shell_syntax - + + # Org command policy check (shared allow/deny firewall across all tools) + from utils.auth.command_policy import evaluate_command + from utils.auth.stateless_auth import get_org_id_for_user + org_id = get_org_id_for_user(user_id) if user_id else None + verdict = evaluate_command(org_id, command) + if not verdict.allowed: + logger.warning("Policy denied terminal command for user %s: %s (%s)", + user_id, command[:100], verdict.rule_description) + return json.dumps({ + "success": False, + "error": f"Command blocked by organization policy: {verdict.rule_description}", + "code": "POLICY_DENIED", + }) + # Define routing table for cloud commands # Provider=None means "use user's provider preference" (for kubectl which works with any cloud) CLOUD_ROUTES = [ diff --git a/server/main_compute.py b/server/main_compute.py index eeed854fa..65aa91411 100644 --- a/server/main_compute.py +++ b/server/main_compute.py @@ -265,6 +265,10 @@ def enforce_user_org_binding(): from routes.org_routes import org_bp app.register_blueprint(org_bp) +# --- Command Policy Routes --- +from routes.command_policies import command_policies_bp +app.register_blueprint(command_policies_bp) + # --- GitHub Integration Routes --- from routes.github.github import github_bp from routes.github.github_user_repos import github_user_repos_bp diff --git a/server/routes/command_policies.py b/server/routes/command_policies.py new file mode 100644 index 000000000..4bbc9c2d9 --- /dev/null +++ b/server/routes/command_policies.py @@ -0,0 +1,268 @@ +"""API routes for command policy management (allow/deny firewall rules). + +Blueprint: command_policies_bp +Prefix: /api/org +""" + +import json +import logging +from datetime import datetime + +from flask import Blueprint, jsonify, request + +from utils.auth.rbac_decorators import require_permission, require_auth_only +from utils.auth.stateless_auth import ( + get_org_id_from_request, + get_user_preference, + store_user_preference, +) +from utils.auth.command_policy import ( + evaluate_command, + get_seed_rules, + invalidate_cache, + validate_pattern, +) + +logger = logging.getLogger(__name__) + +command_policies_bp = Blueprint("command_policies", __name__, url_prefix="/api/org") + + +def _list_states(org_id: str) -> dict: + """Read allowlist/denylist toggle states from user_preferences.""" + org_key = f"__org__{org_id}" + al = get_user_preference(org_key, "command_policy_allowlist") or "on" + dl = get_user_preference(org_key, "command_policy_denylist") or "off" + return { + "allowlist_enabled": str(al).lower() == "on", + "denylist_enabled": str(dl).lower() == "on", + } + + +@command_policies_bp.route("/command-policies", methods=["GET", "OPTIONS"]) +@require_auth_only +def list_policies(user_id): + if request.method == "OPTIONS": + return jsonify({}), 200 + + org_id = get_org_id_from_request() + if not org_id: + return jsonify({"error": "No organization context"}), 403 + + from utils.db.connection_pool import db_pool + with db_pool.get_admin_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT id, mode, pattern, description, priority, enabled, " + "created_at, updated_at, updated_by " + "FROM org_command_policies WHERE org_id = %s ORDER BY priority DESC", + (org_id,), + ) + rows = cur.fetchall() + + allow_rules = [] + deny_rules = [] + for r in rows: + rule = { + "id": r[0], "mode": r[1], "pattern": r[2], + "description": r[3] or "", "priority": r[4], + "enabled": r[5], + "created_at": r[6].isoformat() if r[6] else None, + "updated_at": r[7].isoformat() if r[7] else None, + "updated_by": r[8], + } + (allow_rules if r[1] == "allow" else deny_rules).append(rule) + + states = _list_states(org_id) + return jsonify({ + "allow_rules": allow_rules, + "deny_rules": deny_rules, + **states, + }) + + +@command_policies_bp.route("/command-policies", methods=["POST"]) +@require_permission("admin", "access") +def create_policy(user_id): + org_id = get_org_id_from_request() + if not org_id: + return jsonify({"error": "No organization context"}), 403 + + data = request.get_json() or {} + mode = data.get("mode") + pattern = data.get("pattern", "").strip() + description = data.get("description", "").strip() + priority = int(data.get("priority", 0)) + + if mode not in ("allow", "deny"): + return jsonify({"error": "mode must be 'allow' or 'deny'"}), 400 + if not pattern: + return jsonify({"error": "pattern is required"}), 400 + + err = validate_pattern(pattern) + if err: + return jsonify({"error": f"Invalid regex: {err}"}), 400 + + from utils.db.connection_pool import db_pool + with db_pool.get_admin_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "INSERT INTO org_command_policies " + "(org_id, mode, pattern, description, priority, updated_by) " + "VALUES (%s, %s, %s, %s, %s, %s) RETURNING id", + (org_id, mode, pattern, description, priority, user_id), + ) + new_id = cur.fetchone()[0] + conn.commit() + + invalidate_cache(org_id) + return jsonify({"id": new_id, "status": "created"}), 201 + + +@command_policies_bp.route("/command-policies/", methods=["PUT", "OPTIONS"]) +@require_permission("admin", "access") +def update_policy(user_id, rule_id): + if request.method == "OPTIONS": + return jsonify({}), 200 + + org_id = get_org_id_from_request() + if not org_id: + return jsonify({"error": "No organization context"}), 403 + + data = request.get_json() or {} + updates = [] + params = [] + + for field, col in [("mode", "mode"), ("pattern", "pattern"), + ("description", "description"), ("priority", "priority"), + ("enabled", "enabled")]: + if field in data: + if field == "mode" and data[field] not in ("allow", "deny"): + return jsonify({"error": "mode must be 'allow' or 'deny'"}), 400 + if field == "pattern": + err = validate_pattern(data[field]) + if err: + return jsonify({"error": f"Invalid regex: {err}"}), 400 + updates.append(f"{col} = %s") + params.append(data[field]) + + if not updates: + return jsonify({"error": "No fields to update"}), 400 + + updates.append("updated_at = %s") + params.append(datetime.utcnow()) + updates.append("updated_by = %s") + params.append(user_id) + params.extend([rule_id, org_id]) + + from utils.db.connection_pool import db_pool + with db_pool.get_admin_connection() as conn: + with conn.cursor() as cur: + cur.execute( + f"UPDATE org_command_policies SET {', '.join(updates)} " + "WHERE id = %s AND org_id = %s", + params, + ) + if cur.rowcount == 0: + return jsonify({"error": "Rule not found"}), 404 + conn.commit() + + invalidate_cache(org_id) + return jsonify({"status": "updated"}) + + +@command_policies_bp.route("/command-policies/", methods=["DELETE"]) +@require_permission("admin", "access") +def delete_policy(user_id, rule_id): + org_id = get_org_id_from_request() + if not org_id: + return jsonify({"error": "No organization context"}), 403 + + from utils.db.connection_pool import db_pool + with db_pool.get_admin_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "DELETE FROM org_command_policies WHERE id = %s AND org_id = %s", + (rule_id, org_id), + ) + if cur.rowcount == 0: + return jsonify({"error": "Rule not found"}), 404 + conn.commit() + + invalidate_cache(org_id) + return jsonify({"status": "deleted"}) + + +@command_policies_bp.route("/command-policies/test", methods=["POST", "OPTIONS"]) +@require_auth_only +def test_command(user_id): + if request.method == "OPTIONS": + return jsonify({}), 200 + + org_id = get_org_id_from_request() + if not org_id: + return jsonify({"error": "No organization context"}), 403 + + data = request.get_json() or {} + command = data.get("command", "").strip() + if not command: + return jsonify({"error": "command is required"}), 400 + + verdict = evaluate_command(org_id, command) + return jsonify({ + "allowed": verdict.allowed, + "rule_description": verdict.rule_description, + "command": command, + }) + + +@command_policies_bp.route("/command-policy-toggle", methods=["PUT", "OPTIONS"]) +@require_permission("admin", "access") +def toggle_list(user_id): + if request.method == "OPTIONS": + return jsonify({}), 200 + + org_id = get_org_id_from_request() + if not org_id: + return jsonify({"error": "No organization context"}), 403 + + data = request.get_json() or {} + list_name = data.get("list") + enabled = data.get("enabled") + + if list_name not in ("allowlist", "denylist"): + return jsonify({"error": "list must be 'allowlist' or 'denylist'"}), 400 + if not isinstance(enabled, bool): + return jsonify({"error": "enabled must be a boolean"}), 400 + + pref_key = f"command_policy_{list_name}" + org_key = f"__org__{org_id}" + store_user_preference(org_key, pref_key, json.dumps("on" if enabled else "off")) + + # Auto-seed rules on first enable if list is empty + if enabled: + mode = "allow" if list_name == "allowlist" else "deny" + from utils.db.connection_pool import db_pool + with db_pool.get_admin_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT COUNT(*) FROM org_command_policies " + "WHERE org_id = %s AND mode = %s", + (org_id, mode), + ) + count = cur.fetchone()[0] + + if count == 0: + seeds = get_seed_rules().get(mode, []) + for seed in seeds: + cur.execute( + "INSERT INTO org_command_policies " + "(org_id, mode, pattern, description, priority, updated_by) " + "VALUES (%s, %s, %s, %s, %s, %s)", + (org_id, mode, seed["pattern"], + seed["description"], seed["priority"], user_id), + ) + conn.commit() + + invalidate_cache(org_id) + return jsonify({"status": "updated", **_list_states(org_id)}) diff --git a/server/utils/auth/command_policy.py b/server/utils/auth/command_policy.py new file mode 100644 index 000000000..b9434de0f --- /dev/null +++ b/server/utils/auth/command_policy.py @@ -0,0 +1,232 @@ +"""Org-level command policy engine (allowlist / denylist). + +Evaluates every command before execution against org-configured regex rules. +Two independent lists, each independently togglable: + + 1. Denylist (if enabled) - checked first. Match -> DENIED. + 2. Allowlist (if enabled) - checked second. Match -> ALLOWED, no match -> DENIED. + 3. Both off -> ALLOWED. + +Default for new orgs: allowlist ON, denylist OFF. +Fail-closed: any error fetching policy -> deny all. +""" + +import logging +import re +import time +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Tuple + +logger = logging.getLogger(__name__) + +_CACHE_TTL = 30 # seconds + +_CacheEntry = Tuple[ + List["PolicyRule"], # allow rules + List["PolicyRule"], # deny rules + "ListStates", + float, # monotonic timestamp +] +_cache: Dict[str, _CacheEntry] = {} + + +@dataclass(frozen=True) +class CommandVerdict: + allowed: bool + rule_description: Optional[str] = None + + +@dataclass(frozen=True) +class PolicyRule: + id: int + mode: str + pattern: str + description: str + priority: int + compiled: re.Pattern = field(repr=False) + + +@dataclass(frozen=True) +class ListStates: + allowlist_enabled: bool + denylist_enabled: bool + + +def _compile_safe(pattern: str) -> Optional[re.Pattern]: + try: + return re.compile(pattern) + except re.error: + logger.warning("Invalid regex in policy rule, skipping: %s", pattern) + return None + + +def _fetch(org_id: str) -> Tuple[List[PolicyRule], List[PolicyRule], ListStates]: + """Load policy rules and list states for *org_id*. + + Fail-closed: on ANY error, returns empty rules with allowlist ON. + With zero allow rules and allowlist enabled, evaluate_command denies everything. + """ + allow_rules: List[PolicyRule] = [] + deny_rules: List[PolicyRule] = [] + states = ListStates(allowlist_enabled=True, denylist_enabled=False) + + try: + from utils.db.connection_pool import db_pool + from utils.auth.stateless_auth import get_user_preference + + with db_pool.get_admin_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT id, mode, pattern, description, priority " + "FROM org_command_policies " + "WHERE org_id = %s AND enabled = true " + "ORDER BY priority DESC", + (org_id,), + ) + for row in cur.fetchall(): + compiled = _compile_safe(row[2]) + if compiled is None: + continue + rule = PolicyRule( + id=row[0], mode=row[1], pattern=row[2], + description=row[3] or "", priority=row[4], + compiled=compiled, + ) + if rule.mode == "allow": + allow_rules.append(rule) + else: + deny_rules.append(rule) + + org_pref_key = f"__org__{org_id}" + al_raw = get_user_preference(org_pref_key, "command_policy_allowlist") or "on" + dl_raw = get_user_preference(org_pref_key, "command_policy_denylist") or "off" + states = ListStates( + allowlist_enabled=(str(al_raw).lower() == "on"), + denylist_enabled=(str(dl_raw).lower() == "on"), + ) + except Exception: + logger.exception("Failed to fetch command policies for org %s, fail-closed", org_id) + + return allow_rules, deny_rules, states + + +def _get_cached(org_id: str) -> Tuple[List[PolicyRule], List[PolicyRule], ListStates]: + entry = _cache.get(org_id) + if entry is not None: + allow, deny, states, ts = entry + if time.monotonic() - ts < _CACHE_TTL: + return allow, deny, states + + allow, deny, states = _fetch(org_id) + _cache[org_id] = (allow, deny, states, time.monotonic()) + return allow, deny, states + + +def evaluate_command(org_id: Optional[str], command: str) -> CommandVerdict: + """Core gate. Returns whether *command* is allowed for *org_id*.""" + if not org_id: + return CommandVerdict(allowed=True) + + allow_rules, deny_rules, states = _get_cached(org_id) + + if not states.denylist_enabled and not states.allowlist_enabled: + return CommandVerdict(allowed=True) + + if states.denylist_enabled: + for rule in deny_rules: + if rule.compiled.search(command): + return CommandVerdict(allowed=False, rule_description=rule.description) + + if states.allowlist_enabled: + for rule in allow_rules: + if rule.compiled.search(command): + return CommandVerdict(allowed=True, rule_description=rule.description) + return CommandVerdict(allowed=False, rule_description="No matching allow rule") + + return CommandVerdict(allowed=True) + + +def validate_pattern(pattern: str) -> Optional[str]: + """Return an error string if *pattern* is not valid regex, else None.""" + try: + re.compile(pattern) + return None + except re.error as exc: + return str(exc) + + +def get_policy_prompt_text(org_id: str) -> str: + """Render active policy as system-prompt text for the LLM.""" + allow_rules, deny_rules, states = _get_cached(org_id) + + if not states.denylist_enabled and not states.allowlist_enabled: + return "" + + lines = [ + "## Organization Command Policy", + "The following command policy is enforced. Commands violating this policy " + "will be rejected at execution time. Do not attempt blocked commands.", + "", + ] + + if states.denylist_enabled and deny_rules: + lines.append("DENIED commands (never run these):") + for r in deny_rules: + lines.append(f" - {r.description} (pattern: {r.pattern})") + lines.append("") + + if states.allowlist_enabled and allow_rules: + lines.append("ALLOWED commands (only these are permitted):") + for r in allow_rules: + lines.append(f" - {r.description} (pattern: {r.pattern})") + lines.append("") + + return "\n".join(lines) + + +def get_seed_rules() -> Dict[str, list]: + """Default seed templates, inserted on first enable of each list.""" + return { + "allow": [ + {"priority": 100, "pattern": r"^(ls|cat|head|tail|wc|grep|find|stat|file|du|df)\b", + "description": "Read-only filesystem inspection"}, + {"priority": 90, "pattern": r"^(kubectl|oc)\s+(get|describe|logs|top|explain)\b", + "description": "Read-only Kubernetes queries"}, + {"priority": 80, "pattern": r"^(gcloud|aws|az)\s+.*\b(list|describe|get|show)\b", + "description": "Read-only cloud CLI queries"}, + {"priority": 70, "pattern": r"^(terraform|tofu)\s+(init|plan|validate|fmt|output|state\s+(list|show|pull))\b", + "description": "Non-destructive Terraform operations"}, + {"priority": 60, "pattern": r"^(ping|dig|nslookup|traceroute|curl|wget)\b", + "description": "Network diagnostics"}, + {"priority": 50, "pattern": r"^git\s+(status|log|diff|show|branch)\b", + "description": "Read-only git operations"}, + ], + "deny": [ + {"priority": 100, "pattern": r"\brm\s+-rf\s+/", + "description": "Recursive root deletion"}, + {"priority": 95, "pattern": r"\b(gcc|g\+\+|cc|make|as|ld)\b", + "description": "Native code compilation"}, + {"priority": 90, "pattern": r"\bLD_PRELOAD\b", + "description": "Shared library injection"}, + {"priority": 85, "pattern": r"\b(ssh-keygen|ssh-copy-id)\b", + "description": "SSH key generation on host"}, + {"priority": 80, "pattern": r"\b(useradd|usermod|adduser|visudo|passwd)\b", + "description": "User/privilege management"}, + {"priority": 75, "pattern": r"\bcurl\b.*\|\s*(sh|bash)\b", + "description": "Remote script execution"}, + {"priority": 70, "pattern": r"\b(nc|ncat|netcat|socat)\b.*(-l|-e|-c)\b", + "description": "Network listener / reverse shell"}, + {"priority": 65, "pattern": r"\bchmod\b.*(\+s|u\+s|4[0-7]{3})\b", + "description": "SUID bit manipulation"}, + {"priority": 60, "pattern": r"\bnsenter\b|\bunshare\b|\bchroot\b", + "description": "Namespace/container escape"}, + {"priority": 55, "pattern": r"\biptables\b|\bnft\b|\bip\s+route\b", + "description": "Network configuration changes"}, + {"priority": 50, "pattern": r"\bkubectl\s+(exec|cp|run|attach|port-forward)\b", + "description": "Interactive kubectl operations"}, + ], + } + + +def invalidate_cache(org_id: str) -> None: + _cache.pop(org_id, None) diff --git a/server/utils/db/db_utils.py b/server/utils/db/db_utils.py index 1dc3cbc90..08ed21bb3 100644 --- a/server/utils/db/db_utils.py +++ b/server/utils/db/db_utils.py @@ -378,6 +378,22 @@ def initialize_tables(): UNIQUE(user_id, org_id, preference_key) ); """, + "org_command_policies": """ + CREATE TABLE IF NOT EXISTS org_command_policies ( + id SERIAL PRIMARY KEY, + org_id VARCHAR(255) NOT NULL, + mode VARCHAR(20) NOT NULL CHECK (mode IN ('allow', 'deny')), + pattern TEXT NOT NULL, + description TEXT, + priority INT DEFAULT 0, + enabled BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_by VARCHAR(255) + ); + CREATE INDEX IF NOT EXISTS idx_ocp_org + ON org_command_policies(org_id); + """, "workspaces": """ CREATE TABLE IF NOT EXISTS workspaces ( id VARCHAR(50) PRIMARY KEY, From cce918b9d8cda1ae94af3a74fa62741fea87279b Mon Sep 17 00:00:00 2001 From: Damian Loch Date: Fri, 17 Apr 2026 14:40:30 -0400 Subject: [PATCH 03/19] hardened to account for compound commands --- .../backend/agent/tools/cloud_exec_tool.py | 4 +- .../agent/tools/iac/iac_commands_tool.py | 6 +- .../agent/tools/kubectl_onprem_tool.py | 4 +- .../backend/agent/tools/tailscale_ssh_tool.py | 4 +- .../backend/agent/tools/terminal_exec_tool.py | 4 +- server/routes/command_policies.py | 4 +- server/utils/auth/command_policy.py | 122 ++++++++++++++++++ 7 files changed, 135 insertions(+), 13 deletions(-) diff --git a/server/chat/backend/agent/tools/cloud_exec_tool.py b/server/chat/backend/agent/tools/cloud_exec_tool.py index 0b0ed2247..15caa6431 100644 --- a/server/chat/backend/agent/tools/cloud_exec_tool.py +++ b/server/chat/backend/agent/tools/cloud_exec_tool.py @@ -2009,10 +2009,10 @@ def cloud_exec(provider: str, command: str, user_id: Optional[str] = None, sessi }) # Org command policy check (shared allow/deny firewall across all tools) - from utils.auth.command_policy import evaluate_command + from utils.auth.command_policy import evaluate_compound_command from utils.auth.stateless_auth import get_org_id_for_user org_id = get_org_id_for_user(user_id) if user_id else None - verdict = evaluate_command(org_id, command) + verdict = evaluate_compound_command(org_id, command) if not verdict.allowed: logger.warning("Policy denied cloud command for user %s: %s (%s)", user_id, command[:100], verdict.rule_description) diff --git a/server/chat/backend/agent/tools/iac/iac_commands_tool.py b/server/chat/backend/agent/tools/iac/iac_commands_tool.py index eddc072fa..8fd1492d1 100644 --- a/server/chat/backend/agent/tools/iac/iac_commands_tool.py +++ b/server/chat/backend/agent/tools/iac/iac_commands_tool.py @@ -66,7 +66,7 @@ def _scan_tf_for_exec_provisioners( if not org_id: return [] - from utils.auth.command_policy import evaluate_command + from utils.auth.command_policy import evaluate_compound_command from utils.terminal.terminal_run import terminal_run list_cmd = f"cat {terraform_dir}/*.tf 2>/dev/null || true" @@ -80,7 +80,7 @@ def _scan_tf_for_exec_provisioners( for match in _LOCAL_EXEC_RE.finditer(content): cmd = match.group(1).strip() if cmd: - verdict = evaluate_command(org_id, cmd) + verdict = evaluate_compound_command(org_id, cmd) if not verdict.allowed: denied.append((cmd, verdict)) @@ -89,7 +89,7 @@ def _scan_tf_for_exec_provisioners( parts = [p.strip().strip('"').strip("'") for p in raw.split(",")] cmd = " ".join(parts) if cmd: - verdict = evaluate_command(org_id, cmd) + verdict = evaluate_compound_command(org_id, cmd) if not verdict.allowed: denied.append((cmd, verdict)) diff --git a/server/chat/backend/agent/tools/kubectl_onprem_tool.py b/server/chat/backend/agent/tools/kubectl_onprem_tool.py index 964e5bbd3..92fefcf0b 100644 --- a/server/chat/backend/agent/tools/kubectl_onprem_tool.py +++ b/server/chat/backend/agent/tools/kubectl_onprem_tool.py @@ -43,11 +43,11 @@ def on_prem_kubectl( command = command[8:].strip() # Org command policy check against full kubectl command - from utils.auth.command_policy import evaluate_command + from utils.auth.command_policy import evaluate_compound_command from utils.auth.stateless_auth import get_org_id_for_user full_command = f"kubectl {command}" org_id = get_org_id_for_user(user_id) if user_id else None - verdict = evaluate_command(org_id, full_command) + verdict = evaluate_compound_command(org_id, full_command) if not verdict.allowed: logger.warning("Policy denied kubectl_onprem command for user %s: %s (%s)", user_id, full_command[:100], verdict.rule_description) diff --git a/server/chat/backend/agent/tools/tailscale_ssh_tool.py b/server/chat/backend/agent/tools/tailscale_ssh_tool.py index 7064a7ea5..af09255c5 100644 --- a/server/chat/backend/agent/tools/tailscale_ssh_tool.py +++ b/server/chat/backend/agent/tools/tailscale_ssh_tool.py @@ -217,10 +217,10 @@ def tailscale_ssh( }) # Org command policy check (shared allow/deny firewall across all tools) - from utils.auth.command_policy import evaluate_command + from utils.auth.command_policy import evaluate_compound_command from utils.auth.stateless_auth import get_org_id_for_user org_id = get_org_id_for_user(user_id) if user_id else None - verdict = evaluate_command(org_id, command) + verdict = evaluate_compound_command(org_id, command) if not verdict.allowed: logger.warning("Policy denied tailscale_ssh command for user %s: %s (%s)", user_id, command[:100], verdict.rule_description) diff --git a/server/chat/backend/agent/tools/terminal_exec_tool.py b/server/chat/backend/agent/tools/terminal_exec_tool.py index 745e42a40..dca619ef0 100644 --- a/server/chat/backend/agent/tools/terminal_exec_tool.py +++ b/server/chat/backend/agent/tools/terminal_exec_tool.py @@ -264,10 +264,10 @@ def terminal_exec( allow_routing = not force_shell and not has_shell_syntax # Org command policy check (shared allow/deny firewall across all tools) - from utils.auth.command_policy import evaluate_command + from utils.auth.command_policy import evaluate_compound_command from utils.auth.stateless_auth import get_org_id_for_user org_id = get_org_id_for_user(user_id) if user_id else None - verdict = evaluate_command(org_id, command) + verdict = evaluate_compound_command(org_id, command) if not verdict.allowed: logger.warning("Policy denied terminal command for user %s: %s (%s)", user_id, command[:100], verdict.rule_description) diff --git a/server/routes/command_policies.py b/server/routes/command_policies.py index 4bbc9c2d9..2e41b4960 100644 --- a/server/routes/command_policies.py +++ b/server/routes/command_policies.py @@ -17,7 +17,7 @@ store_user_preference, ) from utils.auth.command_policy import ( - evaluate_command, + evaluate_compound_command, get_seed_rules, invalidate_cache, validate_pattern, @@ -208,7 +208,7 @@ def test_command(user_id): if not command: return jsonify({"error": "command is required"}), 400 - verdict = evaluate_command(org_id, command) + verdict = evaluate_compound_command(org_id, command) return jsonify({ "allowed": verdict.allowed, "rule_description": verdict.rule_description, diff --git a/server/utils/auth/command_policy.py b/server/utils/auth/command_policy.py index b9434de0f..f42baec34 100644 --- a/server/utils/auth/command_policy.py +++ b/server/utils/auth/command_policy.py @@ -7,6 +7,10 @@ 2. Allowlist (if enabled) - checked second. Match -> ALLOWED, no match -> DENIED. 3. Both off -> ALLOWED. +Compound shell expressions (;, &&, ||, |, subshells) are decomposed and each +atomic command is evaluated independently. One denied sub-command blocks the +entire expression. + Default for new orgs: allowlist ON, denylist OFF. Fail-closed: any error fetching policy -> deny all. """ @@ -146,6 +150,118 @@ def evaluate_command(org_id: Optional[str], command: str) -> CommandVerdict: return CommandVerdict(allowed=True) +def _split_compound_command(compound: str) -> List[str]: + """Quote-aware split of a shell expression into atomic commands. + + Splits on ; && || | while respecting single/double quotes and backslash + escapes. Recursively extracts commands from $(...) and backtick subshells + so they are evaluated independently. + """ + commands: List[str] = [] + buf: List[str] = [] + sq = dq = False + i, n = 0, len(compound) + + def _flush() -> None: + s = "".join(buf).strip() + if s: + commands.append(s) + buf.clear() + + while i < n: + c = compound[i] + + # Backslash escape (not inside single quotes) + if c == "\\" and not sq and i + 1 < n: + buf += [c, compound[i + 1]] + i += 2 + continue + + # Quote toggling + if c == "'" and not dq: + sq = not sq + buf.append(c) + i += 1 + continue + if c == '"' and not sq: + dq = not dq + buf.append(c) + i += 1 + continue + + # Everything inside quotes is literal + if sq or dq: + buf.append(c) + i += 1 + continue + + # -- Outside quotes: detect operators and subshells -- + two = compound[i : i + 2] + + if two in ("&&", "||"): + _flush() + i += 2 + continue + + if c in (";", "|"): + _flush() + i += 1 + continue + + # $(...) subshell - extract inner commands for separate evaluation + if c == "$" and i + 1 < n and compound[i + 1] == "(": + j, depth = i + 2, 1 + while j < n and depth: + if compound[j] == "(": + depth += 1 + elif compound[j] == ")": + depth -= 1 + j += 1 + commands.extend(_split_compound_command(compound[i + 2 : j - 1])) + buf.append(compound[i:j]) + i = j + continue + + # Backtick subshell + if c == "`": + j = compound.find("`", i + 1) + if j != -1: + commands.extend(_split_compound_command(compound[i + 1 : j])) + buf.append(compound[i : j + 1]) + i = j + 1 + continue + + buf.append(c) + i += 1 + + _flush() + return commands + + +def evaluate_compound_command( + org_id: Optional[str], command: str +) -> CommandVerdict: + """Evaluate a potentially compound shell command. + + Decomposes the expression into atomic commands and evaluates each one + independently. ALL sub-commands must pass policy; the first denial is + returned immediately. + """ + if not org_id: + return CommandVerdict(allowed=True) + + parts = _split_compound_command(command) + if not parts: + return evaluate_command(org_id, command) + + for part in parts: + verdict = evaluate_command(org_id, part) + if not verdict.allowed: + return verdict + + return CommandVerdict(allowed=True) + + def validate_pattern(pattern: str) -> Optional[str]: """Return an error string if *pattern* is not valid regex, else None.""" try: @@ -206,10 +322,16 @@ def get_seed_rules() -> Dict[str, list]: "description": "Recursive root deletion"}, {"priority": 95, "pattern": r"\b(gcc|g\+\+|cc|make|as|ld)\b", "description": "Native code compilation"}, + {"priority": 92, "pattern": r"\beval\b|\bexec\b", + "description": "Dynamic code evaluation"}, {"priority": 90, "pattern": r"\bLD_PRELOAD\b", "description": "Shared library injection"}, + {"priority": 88, "pattern": r"\bbase64\b.*\|\s*(sh|bash|python)", + "description": "Encoded payload execution"}, {"priority": 85, "pattern": r"\b(ssh-keygen|ssh-copy-id)\b", "description": "SSH key generation on host"}, + {"priority": 83, "pattern": r"\b(bash|sh|dash|zsh)\s+-c\b", + "description": "Inline shell interpreter"}, {"priority": 80, "pattern": r"\b(useradd|usermod|adduser|visudo|passwd)\b", "description": "User/privilege management"}, {"priority": 75, "pattern": r"\bcurl\b.*\|\s*(sh|bash)\b", From 3918824291a9c78062173d40ada4ed0fc98850a0 Mon Sep 17 00:00:00 2001 From: Damian Loch Date: Sat, 18 Apr 2026 11:01:35 -0400 Subject: [PATCH 04/19] added new templates --- .../command-policy-templates/apply/route.ts | 18 + .../api/org/command-policy-templates/route.ts | 16 + client/src/app/globals.css | 17 + client/src/components/SecuritySettings.tsx | 182 +++++++++- client/src/lib/services/command-policies.ts | 23 ++ docs/design/command-policy-system.md | 56 ++- server/routes/command_policies.py | 72 ++++ server/utils/auth/command_policy.py | 318 +++++++++++++++--- 8 files changed, 630 insertions(+), 72 deletions(-) create mode 100644 client/src/app/api/org/command-policy-templates/apply/route.ts create mode 100644 client/src/app/api/org/command-policy-templates/route.ts diff --git a/client/src/app/api/org/command-policy-templates/apply/route.ts b/client/src/app/api/org/command-policy-templates/apply/route.ts new file mode 100644 index 000000000..027470d95 --- /dev/null +++ b/client/src/app/api/org/command-policy-templates/apply/route.ts @@ -0,0 +1,18 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getAuthenticatedUser } from "@/lib/auth-helper"; + +const API_BASE_URL = process.env.BACKEND_URL; + +export async function POST(req: NextRequest) { + if (!API_BASE_URL) return NextResponse.json({ error: "BACKEND_URL not configured" }, { status: 500 }); + const authResult = await getAuthenticatedUser(); + if (authResult instanceof NextResponse) return authResult; + const body = await req.json(); + const res = await fetch(`${API_BASE_URL}/api/org/command-policy-templates/apply`, { + method: "POST", + headers: { ...authResult.headers, "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + const data = await res.json().catch(() => ({})); + return NextResponse.json(data, { status: res.status }); +} diff --git a/client/src/app/api/org/command-policy-templates/route.ts b/client/src/app/api/org/command-policy-templates/route.ts new file mode 100644 index 000000000..50f5c86e3 --- /dev/null +++ b/client/src/app/api/org/command-policy-templates/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from "next/server"; +import { getAuthenticatedUser } from "@/lib/auth-helper"; + +const API_BASE_URL = process.env.BACKEND_URL; + +export async function GET() { + if (!API_BASE_URL) return NextResponse.json({ error: "BACKEND_URL not configured" }, { status: 500 }); + const authResult = await getAuthenticatedUser(); + if (authResult instanceof NextResponse) return authResult; + const res = await fetch(`${API_BASE_URL}/api/org/command-policy-templates`, { + method: "GET", + headers: { ...authResult.headers, "Content-Type": "application/json" }, + }); + const data = await res.json().catch(() => ({})); + return NextResponse.json(data, { status: res.status }); +} diff --git a/client/src/app/globals.css b/client/src/app/globals.css index 0383c4075..687ed5df6 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -140,6 +140,23 @@ -ms-overflow-style: none; } +.scrollbar-thin::-webkit-scrollbar { + height: 1px; +} +.scrollbar-thin::-webkit-scrollbar-track { + background: transparent; +} +.scrollbar-thin::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 2px; +} +.scrollbar-thin:hover::-webkit-scrollbar-thumb { + background: hsl(var(--muted-foreground) / 0.25); +} +.scrollbar-thin { + scrollbar-width: thin; +} + /* Code block styles - using Prism okaidia theme */ /* Only override font properties, preserve Prism.js colors */ pre[class*="language-"] { diff --git a/client/src/components/SecuritySettings.tsx b/client/src/components/SecuritySettings.tsx index 05c140139..8b752c86b 100644 --- a/client/src/components/SecuritySettings.tsx +++ b/client/src/components/SecuritySettings.tsx @@ -10,8 +10,8 @@ import { Label } from "@/components/ui/label"; import { useToast } from "@/components/ui/use-toast"; import { useUser } from "@/hooks/useAuthHooks"; import { isAdmin } from "@/lib/roles"; -import { Trash2, Plus, Terminal, ChevronRight, Loader2, Lock, CheckCircle2, XCircle, Shield, ShieldCheck, ShieldX } from "lucide-react"; -import { commandPolicyService, type CommandPolicyRule } from "@/lib/services/command-policies"; +import { Trash2, Plus, Terminal, ChevronRight, ChevronDown, Loader2, Lock, CheckCircle2, XCircle, Shield, ShieldCheck, ShieldX, BookOpen } from "lucide-react"; +import { commandPolicyService, type CommandPolicyRule, type PolicyTemplate } from "@/lib/services/command-policies"; function RuleList({ rules, @@ -41,7 +41,7 @@ function RuleList({ onCheckedChange={() => onToggle(rule)} className="shrink-0 scale-90" /> - + {rule.pattern} @@ -109,6 +109,135 @@ function AddRuleForm({ ); } +function TemplatePicker({ + templates, + applying, + onApply, +}: { + templates: PolicyTemplate[]; + applying: string | null; + onApply: (id: string) => void; +}) { + const [confirmId, setConfirmId] = useState(null); + const [expandedId, setExpandedId] = useState(null); + + if (templates.length === 0) return null; + + return ( +

+
+
+ +
+
+

Policy Templates

+

Pre-built security profiles for common use cases

+
+
+
+ {templates.map((tpl) => { + const expanded = expandedId === tpl.id; + return ( +
+
+
+

{tpl.name}

+

+ {tpl.description} +

+
+
+ {confirmId === tpl.id ? ( +
+ + +
+ ) : ( + + )} +
+
+
+ + + + {tpl.allow_count} allow + + + + {tpl.deny_count} deny + +
+ {expanded && ( +
+ {tpl.allow.length > 0 && ( +
+

Allow

+ {tpl.allow.map((r, i) => ( +
+ + {r.pattern} + + {r.description} +
+ ))} +
+ )} + {tpl.deny.length > 0 && ( +
+

Deny

+ {tpl.deny.map((r, i) => ( +
+ + {r.pattern} + + {r.description} +
+ ))} +
+ )} +
+ )} +
+ ); + })} +
+
+ ); +} + export function SecuritySettings() { const { user } = useUser(); const { toast } = useToast(); @@ -127,6 +256,9 @@ export function SecuritySettings() { const [testResult, setTestResult] = useState<{ allowed: boolean; rule_description: string | null } | null>(null); const [testLoading, setTestLoading] = useState(false); + const [templates, setTemplates] = useState([]); + const [applyingTemplate, setApplyingTemplate] = useState(null); + const fetchPolicies = useCallback(async () => { try { const data = await commandPolicyService.getPolicies(); @@ -141,7 +273,16 @@ export function SecuritySettings() { } }, [toast]); - useEffect(() => { fetchPolicies(); }, [fetchPolicies]); + const fetchTemplates = useCallback(async () => { + try { + const data = await commandPolicyService.getTemplates(); + setTemplates(data); + } catch { + // Templates are optional; don't block the UI + } + }, []); + + useEffect(() => { fetchPolicies(); fetchTemplates(); }, [fetchPolicies, fetchTemplates]); const handleToggleList = async (list: "allowlist" | "denylist", enabled: boolean) => { try { @@ -197,6 +338,22 @@ export function SecuritySettings() { } }; + const handleApplyTemplate = async (templateId: string) => { + setApplyingTemplate(templateId); + try { + const res = await commandPolicyService.applyTemplate(templateId); + setAllowlistEnabled(res.allowlist_enabled); + setDenylistEnabled(res.denylist_enabled); + await fetchPolicies(); + const tpl = templates.find(t => t.id === templateId); + toast({ title: `Applied "${tpl?.name ?? templateId}" template` }); + } catch { + toast({ title: "Failed to apply template", variant: "destructive" }); + } finally { + setApplyingTemplate(null); + } + }; + if (loading) { return (
@@ -250,12 +407,21 @@ export function SecuritySettings() {

- Both lists are checked for every command. - If the denylist is on and a command matches, it is blocked. - If the allowlist is on and a command does not match, it is blocked. - Enable both for maximum control. + Command policies audit what the agent can execute — they do not change your connector + permissions or read-only mode settings. A command allowed here can still fail if the + underlying credentials lack access. If the denylist is on, matching commands are blocked. + If the allowlist is on, non-matching commands are blocked. Enable both for maximum control.

+ {/* Policy Templates */} + {templates.length > 0 && ( + + )} + {/* Denylist */}
diff --git a/client/src/lib/services/command-policies.ts b/client/src/lib/services/command-policies.ts index 22036b450..b8d737c24 100644 --- a/client/src/lib/services/command-policies.ts +++ b/client/src/lib/services/command-policies.ts @@ -25,6 +25,22 @@ export interface TestResult { command: string; } +export interface PolicyTemplateRule { + pattern: string; + description: string; + priority: number; +} + +export interface PolicyTemplate { + id: string; + name: string; + description: string; + allow_count: number; + deny_count: number; + allow: PolicyTemplateRule[]; + deny: PolicyTemplateRule[]; +} + export const commandPolicyService = { getPolicies: () => apiGet("/api/org/command-policies"), @@ -44,4 +60,11 @@ export const commandPolicyService = { apiPut<{ ok: boolean; allowlist_enabled: boolean; denylist_enabled: boolean }>( "/api/org/command-policy-toggle", { list, enabled } ), + + getTemplates: () => apiGet("/api/org/command-policy-templates"), + + applyTemplate: (templateId: string) => + apiPost<{ status: string; template_id: string; allowlist_enabled: boolean; denylist_enabled: boolean }>( + "/api/org/command-policy-templates/apply", { template_id: templateId } + ), }; diff --git a/docs/design/command-policy-system.md b/docs/design/command-policy-system.md index 9b18c395a..dda472482 100644 --- a/docs/design/command-policy-system.md +++ b/docs/design/command-policy-system.md @@ -115,25 +115,45 @@ A reminder is also placed at the end of the prompt to reinforce compliance. --- -## 3. Templated Seed Rules +## 3. Policy Template Library -When an organization enables a list for the first time, Aurora seeds it with a curated set of default rules. These templates represent Aurora's security recommendations based on the operational patterns of the agent. The customer can modify, disable, or delete any rule. +Aurora provides a library of pre-built policy templates (named rulesets) at different security levels. Admins can browse these on the Settings > Security page and apply one with a single click. Applying a template replaces all existing rules and auto-enables both lists. -The exact default denylist and allowlist templates are being finalized. The general approach: +### 3.1 Available Templates -- **Denylist template**: patterns that block known-dangerous operations (code compilation, library injection, privilege escalation, lateral movement). -- **Allowlist template**: patterns that permit safe read-only diagnostic commands (log retrieval, resource inspection, cloud read operations). +| Template | Allow Rules | Deny Rules | Use Case | +|---|---|---|---| +| **Observability Only** | Read-only filesystem, cloud CLI reads (all verbs matching AWS session policy, GCP `roles/viewer`, Azure Reader), read-only kubectl, Terraform plan, network diagnostics, Docker inspection, system diagnostics | Universal dangerous patterns + mutating kubectl + SSH | Default for new orgs. Aligned with cloud provider read-only credentials. | +| **Standard Operations** | Everything in Observability + SSH/SCP, kubectl exec/port-forward, cloud config commands (update-kubeconfig, get-credentials), Docker exec, BigQuery queries | Universal dangerous patterns + mutating kubectl (apply, delete, create, etc.) | Incident response and debugging. Allows interactive access without infrastructure mutations. | +| **Full Cloud Access** | Broad patterns for all cloud CLIs, all kubectl, Terraform apply/destroy, all Helm, Docker management, git writes, Ansible, system management | Universal dangerous patterns only (rm -rf, code compilation, LD_PRELOAD, reverse shells, SUID, namespace escape, etc.) | Orgs with admin-level cloud credentials that want minimal guardrails. | -### 3.1 Customization +The "universal" deny rules (present in all templates) block patterns that are dangerous regardless of IAM access level: recursive root deletion, native code compilation, dynamic eval/exec, shared library injection, encoded payload execution, SSH key generation, inline shell interpreters, user/privilege management, remote script piping, reverse shells, SUID manipulation, namespace/container escape, and network configuration changes. + +### 3.2 Template Design Philosophy + +Templates are aligned with the actual permissions granted by each cloud provider's read-only credentials: + +- **AWS**: The session policy allows `ec2:Describe*`, `eks:Describe*`, `s3:Get*/List*`, `rds:Describe*`, `lambda:Get*/List*`, `iam:Get*/List*`, `cloudformation:Describe*/List*/Get*`, `cloudwatch:*`, `logs:*`, `ecs:Describe*/List*`. The Observability Only template's allowlist patterns match all CLI commands that map to these IAM actions, including `aws s3 ls`, `aws eks update-kubeconfig`, and `aws logs filter-log-events`. +- **GCP**: The read-only SA has `roles/viewer`, `roles/logging.viewer`, `roles/monitoring.viewer`, `roles/container.viewer`, `roles/storage.objectViewer`, etc. Templates allow `gcloud ... list/describe/get/read/get-credentials`, `gsutil ls/cat/stat/cp` (download), and `gcloud logging read`. +- **Azure**: The read-only SP has `Reader`, `Log Analytics Reader`, `Monitoring Reader`, `AKS Cluster User Role`, and data readers. Templates allow `az ... list/show/get/query/download` and `az aks get-credentials`. + +### 3.3 Applying Templates + +- Applying a template **replaces** all existing rules (both allow and deny) +- Both allowlist and denylist are auto-enabled after applying +- Admins can fine-tune individual rules after applying a template +- Seed rules (from `get_seed_rules()`) remain backward-compatible and return the Observability Only template + +### 3.4 Customization Customers can: +- **Apply a template** as a starting point, then add/remove/modify individual rules - **Add rules** to either list (e.g., allow `docker ps` or deny `kubectl exec`) - **Disable rules** without deleting them (preserves the rule for re-enabling later) -- **Delete rules** entirely, including seed rules +- **Delete rules** entirely, including template-provided rules - **Modify patterns** to adjust scope (e.g., narrow an allow rule to specific namespaces) - -Seed rules are only inserted once (when the list is first enabled and the list is empty). If a customer deletes a seed rule, it will not be re-created. +- **Re-apply a template** at any time to reset to a known baseline --- @@ -231,14 +251,16 @@ flowchart TD ## 6. API Reference -| Method | Endpoint | Auth | Description | -| -------- | -------------------------------- | ----- | ---------------------------------------- | -| `GET` | `/api/org/command-policies` | Read | List all rules and list states | -| `POST` | `/api/org/command-policies` | Admin | Create a new rule | -| `PUT` | `/api/org/command-policies/:id` | Admin | Update a rule | -| `DELETE` | `/api/org/command-policies/:id` | Admin | Delete a rule | -| `POST` | `/api/org/command-policies/test` | Read | Dry-run a command against current policy | -| `PUT` | `/api/org/command-policy-toggle` | Admin | Enable/disable a list | +| Method | Endpoint | Auth | Description | +| -------- | ----------------------------------------- | ----- | ---------------------------------------- | +| `GET` | `/api/org/command-policies` | Read | List all rules and list states | +| `POST` | `/api/org/command-policies` | Admin | Create a new rule | +| `PUT` | `/api/org/command-policies/:id` | Admin | Update a rule | +| `DELETE` | `/api/org/command-policies/:id` | Admin | Delete a rule | +| `POST` | `/api/org/command-policies/test` | Read | Dry-run a command against current policy | +| `PUT` | `/api/org/command-policy-toggle` | Admin | Enable/disable a list | +| `GET` | `/api/org/command-policy-templates` | Read | List available policy templates | +| `POST` | `/api/org/command-policy-templates/apply` | Admin | Apply a template (replaces all rules) | --- diff --git a/server/routes/command_policies.py b/server/routes/command_policies.py index 2e41b4960..dedb75a75 100644 --- a/server/routes/command_policies.py +++ b/server/routes/command_policies.py @@ -18,6 +18,7 @@ ) from utils.auth.command_policy import ( evaluate_compound_command, + get_policy_templates, get_seed_rules, invalidate_cache, validate_pattern, @@ -266,3 +267,74 @@ def toggle_list(user_id): invalidate_cache(org_id) return jsonify({"status": "updated", **_list_states(org_id)}) + + +# --------------------------------------------------------------------------- +# Template library endpoints +# --------------------------------------------------------------------------- + +@command_policies_bp.route("/command-policy-templates", methods=["GET", "OPTIONS"]) +@require_auth_only +def list_templates(user_id): + if request.method == "OPTIONS": + return jsonify({}), 200 + + templates = get_policy_templates() + result = [] + for tpl in templates: + result.append({ + "id": tpl["id"], + "name": tpl["name"], + "description": tpl["description"], + "allow_count": len(tpl["allow"]), + "deny_count": len(tpl["deny"]), + "allow": tpl["allow"], + "deny": tpl["deny"], + }) + return jsonify(result) + + +@command_policies_bp.route("/command-policy-templates/apply", methods=["POST", "OPTIONS"]) +@require_permission("admin", "access") +def apply_template(user_id): + if request.method == "OPTIONS": + return jsonify({}), 200 + + org_id = get_org_id_from_request() + if not org_id: + return jsonify({"error": "No organization context"}), 403 + + data = request.get_json() or {} + template_id = data.get("template_id") + if not template_id: + return jsonify({"error": "template_id is required"}), 400 + + templates = {t["id"]: t for t in get_policy_templates()} + tpl = templates.get(template_id) + if not tpl: + return jsonify({"error": f"Unknown template: {template_id}"}), 400 + + from utils.db.connection_pool import db_pool + with db_pool.get_admin_connection() as conn: + with conn.cursor() as cur: + cur.execute( + "DELETE FROM org_command_policies WHERE org_id = %s", + (org_id,), + ) + for mode_key in ("allow", "deny"): + for rule in tpl[mode_key]: + cur.execute( + "INSERT INTO org_command_policies " + "(org_id, mode, pattern, description, priority, updated_by) " + "VALUES (%s, %s, %s, %s, %s, %s)", + (org_id, mode_key, rule["pattern"], + rule["description"], rule["priority"], user_id), + ) + conn.commit() + + org_key = f"__org__{org_id}" + store_user_preference(org_key, "command_policy_allowlist", json.dumps("on")) + store_user_preference(org_key, "command_policy_denylist", json.dumps("on")) + + invalidate_cache(org_id) + return jsonify({"status": "applied", "template_id": template_id, **_list_states(org_id)}) diff --git a/server/utils/auth/command_policy.py b/server/utils/auth/command_policy.py index f42baec34..b51fe10c9 100644 --- a/server/utils/auth/command_policy.py +++ b/server/utils/auth/command_policy.py @@ -301,53 +301,277 @@ def get_policy_prompt_text(org_id: str) -> str: def get_seed_rules() -> Dict[str, list]: - """Default seed templates, inserted on first enable of each list.""" - return { - "allow": [ - {"priority": 100, "pattern": r"^(ls|cat|head|tail|wc|grep|find|stat|file|du|df)\b", - "description": "Read-only filesystem inspection"}, - {"priority": 90, "pattern": r"^(kubectl|oc)\s+(get|describe|logs|top|explain)\b", - "description": "Read-only Kubernetes queries"}, - {"priority": 80, "pattern": r"^(gcloud|aws|az)\s+.*\b(list|describe|get|show)\b", - "description": "Read-only cloud CLI queries"}, - {"priority": 70, "pattern": r"^(terraform|tofu)\s+(init|plan|validate|fmt|output|state\s+(list|show|pull))\b", - "description": "Non-destructive Terraform operations"}, - {"priority": 60, "pattern": r"^(ping|dig|nslookup|traceroute|curl|wget)\b", - "description": "Network diagnostics"}, - {"priority": 50, "pattern": r"^git\s+(status|log|diff|show|branch)\b", - "description": "Read-only git operations"}, - ], - "deny": [ - {"priority": 100, "pattern": r"\brm\s+-rf\s+/", - "description": "Recursive root deletion"}, - {"priority": 95, "pattern": r"\b(gcc|g\+\+|cc|make|as|ld)\b", - "description": "Native code compilation"}, - {"priority": 92, "pattern": r"\beval\b|\bexec\b", - "description": "Dynamic code evaluation"}, - {"priority": 90, "pattern": r"\bLD_PRELOAD\b", - "description": "Shared library injection"}, - {"priority": 88, "pattern": r"\bbase64\b.*\|\s*(sh|bash|python)", - "description": "Encoded payload execution"}, - {"priority": 85, "pattern": r"\b(ssh-keygen|ssh-copy-id)\b", - "description": "SSH key generation on host"}, - {"priority": 83, "pattern": r"\b(bash|sh|dash|zsh)\s+-c\b", - "description": "Inline shell interpreter"}, - {"priority": 80, "pattern": r"\b(useradd|usermod|adduser|visudo|passwd)\b", - "description": "User/privilege management"}, - {"priority": 75, "pattern": r"\bcurl\b.*\|\s*(sh|bash)\b", - "description": "Remote script execution"}, - {"priority": 70, "pattern": r"\b(nc|ncat|netcat|socat)\b.*(-l|-e|-c)\b", - "description": "Network listener / reverse shell"}, - {"priority": 65, "pattern": r"\bchmod\b.*(\+s|u\+s|4[0-7]{3})\b", - "description": "SUID bit manipulation"}, - {"priority": 60, "pattern": r"\bnsenter\b|\bunshare\b|\bchroot\b", - "description": "Namespace/container escape"}, - {"priority": 55, "pattern": r"\biptables\b|\bnft\b|\bip\s+route\b", - "description": "Network configuration changes"}, - {"priority": 50, "pattern": r"\bkubectl\s+(exec|cp|run|attach|port-forward)\b", - "description": "Interactive kubectl operations"}, - ], - } + """Default seed templates, inserted on first enable of each list. + + Returns the 'observability_only' template for backward compatibility. + """ + tpl = get_policy_templates()[0] + return {"allow": tpl["allow"], "deny": tpl["deny"]} + + +# --------------------------------------------------------------------------- +# Deny rules shared across ALL templates (dangerous regardless of access level) +# --------------------------------------------------------------------------- +_UNIVERSAL_DENY_RULES: list = [ + {"priority": 100, "pattern": r"\brm\s+-rf\s+/", + "description": "Recursive root deletion"}, + {"priority": 95, "pattern": r"\b(gcc|g\+\+|cc|make|as|ld)\b", + "description": "Native code compilation"}, + {"priority": 92, "pattern": r"(? List[dict]: + """Return the library of pre-built policy templates. + + Each template is a dict with keys: id, name, description, allow, deny. + Templates are ordered from most restrictive to most permissive. + """ + return [ + # -- 1. Observability Only ---------------------------------------- + { + "id": "observability_only", + "name": "Observability Only", + "description": ( + "Read-only access aligned with cloud provider read-only " + "credentials (AWS ReadOnlyAccess session policy, GCP " + "roles/viewer, Azure Reader). Blocks all write, SSH, and " + "interactive operations." + ), + "allow": [ + # Filesystem inspection + {"priority": 200, "pattern": r"^(ls|cat|head|tail|wc|grep|find|stat|file|du|df|sort|uniq|awk|sed|tr|cut|tee|less|more|xargs|realpath|readlink|basename|dirname)\b", + "description": "Read-only filesystem inspection"}, + # Kubernetes read-only + {"priority": 190, "pattern": r"^(kubectl|oc)\s+(get|describe|logs|top|explain|api-resources|api-versions|cluster-info|config\s+(view|get-contexts|current-context|use-context))\b", + "description": "Read-only Kubernetes queries"}, + # AWS CLI -- matches all read-only verbs and subcommands that the session policy permits + {"priority": 180, "pattern": r"^aws\s+.+\b(ls|list|describe|get|show|head|filter|start-query|stop-query|test-metric-filter|update-kubeconfig|wait)\b", + "description": "AWS read-only operations (EC2, EKS, S3, RDS, Lambda, IAM, CloudWatch, Logs, ECS, CloudFormation)"}, + {"priority": 179, "pattern": r"^aws\s+s3\s+(ls|cp\s+s3://|presign|sync\s+s3://)", + "description": "AWS S3 read operations (ls, download, presign)"}, + {"priority": 178, "pattern": r"^aws\s+sts\s+get-caller-identity\b", + "description": "AWS STS identity check"}, + {"priority": 177, "pattern": r"^aws\s+logs\s+(describe-log-groups|describe-log-streams|get-log-events|get-query-results|filter-log-events|start-query|stop-query|tail)\b", + "description": "AWS CloudWatch Logs read operations"}, + # GCP / gcloud -- roles/viewer, logging.viewer, monitoring.viewer, container.viewer, storage.objectViewer + {"priority": 170, "pattern": r"^gcloud\s+.+\b(list|describe|get|show|read|get-credentials|get-server-config)\b", + "description": "GCP read-only operations (Compute, GKE, Cloud SQL, Cloud Run, IAM, DNS)"}, + {"priority": 169, "pattern": r"^gcloud\s+(logging|monitoring|asset|projects|organizations|config)\s", + "description": "GCP logging, monitoring, asset inventory, and config"}, + {"priority": 168, "pattern": r"^gsutil\s+(ls|cat|stat|du|cp\s+gs://|rsync\s+-n)\b", + "description": "GCP Storage read operations (ls, cat, stat, download)"}, + {"priority": 167, "pattern": r"^bq\s+(ls|show|head|query\s+--dry_run|mk\s+--dry_run)\b", + "description": "BigQuery read-only operations"}, + # Azure -- Reader role + Log Analytics Reader + Monitoring Reader + {"priority": 160, "pattern": r"^az\s+.+\b(list|show|get|describe|display|query|download)\b", + "description": "Azure read-only operations (VMs, AKS, Storage, SQL, Key Vault, NSGs)"}, + {"priority": 159, "pattern": r"^az\s+(monitor|advisor|security|consumption|costmanagement|account)\s", + "description": "Azure monitoring, cost, security, and account queries"}, + {"priority": 158, "pattern": r"^az\s+aks\s+get-credentials\b", + "description": "Azure AKS kubeconfig retrieval"}, + # OVH / Scaleway + {"priority": 150, "pattern": r"^ovhcloud\s+.+\b(list|show|get|describe)\b", + "description": "OVH read-only operations"}, + {"priority": 149, "pattern": r"^scw\s+.+\b(list|get|describe|inspect)\b", + "description": "Scaleway read-only operations"}, + # Tailscale + {"priority": 140, "pattern": r"^tailscale\s+(status|device\s+(list|get)|dns|acl\s+(get|show)|routes|settings|auth-key\s+list)\b", + "description": "Tailscale read-only operations"}, + # Terraform / IaC read-only + {"priority": 130, "pattern": r"^(terraform|tofu)\s+(init|plan|validate|fmt|output|show|state\s+(list|show|pull)|version)\b", + "description": "Non-destructive Terraform operations"}, + {"priority": 129, "pattern": r"^(helm)\s+(list|get|show|status|history|search|version)\b", + "description": "Helm read-only operations"}, + # Network diagnostics + {"priority": 120, "pattern": r"^(ping|dig|nslookup|traceroute|tracepath|mtr|curl|wget|host|whois|nmap)\b", + "description": "Network diagnostics"}, + # Git read-only + {"priority": 110, "pattern": r"^git\s+(status|log|diff|show|branch|tag|remote|stash\s+list|rev-parse|config\s+--get|ls-files|ls-remote|blame|shortlog)\b", + "description": "Read-only git operations"}, + # Docker/container inspection + {"priority": 100, "pattern": r"^(docker|podman)\s+(ps|images|inspect|logs|stats|top|port|diff|history|version|info|network\s+(ls|inspect)|volume\s+(ls|inspect))\b", + "description": "Container inspection (read-only)"}, + # System diagnostics + {"priority": 90, "pattern": r"^(uptime|whoami|hostname|uname|env|printenv|id|date|cal|free|vmstat|iostat|mpstat|sar|lsof|ss|netstat|ps|top|htop|lscpu|lsmem|lsblk|mount|dmesg|journalctl|systemctl\s+(status|is-active|is-enabled|list-units|list-timers))\b", + "description": "System diagnostics and status"}, + # Process / text utilities + {"priority": 80, "pattern": r"^(jq|yq|column|printf|echo|test|true|false|which|type|command|whereis|file|xxd|hexdump|sha256sum|md5sum|base64)\b", + "description": "Text processing and utility commands"}, + ], + "deny": [ + *_UNIVERSAL_DENY_RULES, + {"priority": 50, "pattern": r"\bkubectl\s+(exec|cp|run|attach|port-forward|apply|delete|create|edit|patch|replace|scale|rollout|drain|cordon|uncordon|taint)\b", + "description": "Mutating kubectl operations"}, + {"priority": 48, "pattern": r"^(ssh|scp|sftp)\s", + "description": "SSH access (use Standard Operations template to enable)"}, + ], + }, + + # -- 2. Standard Operations ---------------------------------------- + { + "id": "standard_ops", + "name": "Standard Operations", + "description": ( + "Allows SSH, kubectl exec, cloud CLI config commands, and " + "container inspection on top of full read-only access. " + "Suitable for incident response and debugging. Still blocks " + "infrastructure mutations and dangerous patterns." + ), + "allow": [ + # Filesystem + {"priority": 200, "pattern": r"^(ls|cat|head|tail|wc|grep|find|stat|file|du|df|sort|uniq|awk|sed|tr|cut|tee|less|more|xargs|realpath|readlink|basename|dirname)\b", + "description": "Filesystem inspection"}, + # Kubernetes read + interactive + {"priority": 190, "pattern": r"^(kubectl|oc)\s+(get|describe|logs|top|explain|api-resources|api-versions|cluster-info|config\s+(view|get-contexts|current-context|use-context)|exec|cp|attach|port-forward|run\s+.*--rm\b.*--restart=Never)\b", + "description": "Kubernetes read and interactive debug operations"}, + # AWS full read + config + {"priority": 180, "pattern": r"^aws\s+.+\b(ls|list|describe|get|show|head|filter|start-query|stop-query|test-metric-filter|update-kubeconfig|configure|wait)\b", + "description": "AWS read and config operations"}, + {"priority": 179, "pattern": r"^aws\s+s3\s+(ls|cp\s+s3://|presign|sync\s+s3://)", + "description": "AWS S3 read operations"}, + {"priority": 178, "pattern": r"^aws\s+sts\s+(get-caller-identity|assume-role|get-session-token)\b", + "description": "AWS STS operations"}, + {"priority": 177, "pattern": r"^aws\s+logs\s+(describe-log-groups|describe-log-streams|get-log-events|get-query-results|filter-log-events|start-query|stop-query|tail)\b", + "description": "AWS CloudWatch Logs operations"}, + # GCP full read + credentials + {"priority": 170, "pattern": r"^gcloud\s+.+\b(list|describe|get|show|read|get-credentials|get-server-config)\b", + "description": "GCP read and credential operations"}, + {"priority": 169, "pattern": r"^gcloud\s+(logging|monitoring|asset|projects|organizations|config|auth)\s", + "description": "GCP logging, monitoring, asset inventory, auth, and config"}, + {"priority": 168, "pattern": r"^gsutil\s+(ls|cat|stat|du|cp\s+gs://|rsync\s+-n)\b", + "description": "GCP Storage read operations"}, + {"priority": 167, "pattern": r"^bq\s+(ls|show|head|query|mk\s+--dry_run)\b", + "description": "BigQuery operations"}, + # Azure full read + credentials + {"priority": 160, "pattern": r"^az\s+.+\b(list|show|get|describe|display|query|download|browse)\b", + "description": "Azure read operations"}, + {"priority": 159, "pattern": r"^az\s+(monitor|advisor|security|consumption|costmanagement|account|aks\s+get-credentials)\s", + "description": "Azure monitoring, cost, security, and AKS credentials"}, + # OVH / Scaleway + {"priority": 150, "pattern": r"^ovhcloud\s+.+\b(list|show|get|describe)\b", + "description": "OVH read-only operations"}, + {"priority": 149, "pattern": r"^scw\s+.+\b(list|get|describe|inspect)\b", + "description": "Scaleway read-only operations"}, + # Tailscale + {"priority": 140, "pattern": r"^tailscale\s+(status|device\s+(list|get)|dns|acl\s+(get|show)|routes|settings|auth-key\s+list)\b", + "description": "Tailscale read-only operations"}, + # SSH access + {"priority": 135, "pattern": r"^(ssh|scp|sftp)\s", + "description": "SSH, SCP, and SFTP access"}, + # Terraform / IaC read-only + {"priority": 130, "pattern": r"^(terraform|tofu)\s+(init|plan|validate|fmt|output|show|state\s+(list|show|pull)|version)\b", + "description": "Non-destructive Terraform operations"}, + {"priority": 129, "pattern": r"^(helm)\s+(list|get|show|status|history|search|version|template)\b", + "description": "Helm read-only operations"}, + # Network diagnostics + {"priority": 120, "pattern": r"^(ping|dig|nslookup|traceroute|tracepath|mtr|curl|wget|host|whois|nmap)\b", + "description": "Network diagnostics"}, + # Git read-only + {"priority": 110, "pattern": r"^git\s+(status|log|diff|show|branch|tag|remote|stash\s+list|rev-parse|config\s+--get|ls-files|ls-remote|blame|shortlog)\b", + "description": "Read-only git operations"}, + # Docker/container inspection + exec + {"priority": 100, "pattern": r"^(docker|podman)\s+(ps|images|inspect|logs|stats|top|port|diff|history|version|info|exec|network\s+(ls|inspect)|volume\s+(ls|inspect))\b", + "description": "Container inspection and exec"}, + # System diagnostics + {"priority": 90, "pattern": r"^(uptime|whoami|hostname|uname|env|printenv|id|date|cal|free|vmstat|iostat|mpstat|sar|lsof|ss|netstat|ps|top|htop|lscpu|lsmem|lsblk|mount|dmesg|journalctl|systemctl\s+(status|is-active|is-enabled|list-units|list-timers))\b", + "description": "System diagnostics and status"}, + # Text utilities + {"priority": 80, "pattern": r"^(jq|yq|column|printf|echo|test|true|false|which|type|command|whereis|file|xxd|hexdump|sha256sum|md5sum|base64)\b", + "description": "Text processing and utility commands"}, + ], + "deny": [ + *_UNIVERSAL_DENY_RULES, + {"priority": 50, "pattern": r"\bkubectl\s+(apply|delete|create|edit|patch|replace|scale|rollout|drain|cordon|uncordon|taint)\b", + "description": "Mutating kubectl operations (use Full Cloud Access to enable)"}, + ], + }, + + # -- 3. Full Cloud Access ------------------------------------------ + { + "id": "full_cloud_access", + "name": "Full Cloud Access", + "description": ( + "Broad command access for orgs with admin-level cloud " + "credentials. Allows cloud write operations, Terraform " + "apply, kubectl mutations, SSH, and Docker management. " + "Only blocks universally dangerous patterns." + ), + "allow": [ + # Filesystem -- broad + {"priority": 200, "pattern": r"^(ls|cat|head|tail|wc|grep|find|stat|file|du|df|sort|uniq|awk|sed|tr|cut|tee|less|more|xargs|realpath|readlink|basename|dirname|mkdir|cp|mv|touch|ln|chmod|chown|rm)\b", + "description": "Filesystem operations"}, + # Kubernetes -- full + {"priority": 190, "pattern": r"^(kubectl|oc)\s+\w", + "description": "All kubectl/oc operations"}, + # AWS -- broad + {"priority": 180, "pattern": r"^aws\s+\w", + "description": "All AWS CLI operations"}, + # GCP -- broad + {"priority": 170, "pattern": r"^(gcloud|gsutil|bq)\s+\w", + "description": "All GCP CLI operations"}, + # Azure -- broad + {"priority": 160, "pattern": r"^az\s+\w", + "description": "All Azure CLI operations"}, + # OVH / Scaleway -- broad + {"priority": 150, "pattern": r"^(ovhcloud|scw)\s+\w", + "description": "All OVH and Scaleway CLI operations"}, + # Tailscale + {"priority": 140, "pattern": r"^tailscale\s+\w", + "description": "All Tailscale operations"}, + # SSH + {"priority": 135, "pattern": r"^(ssh|scp|sftp)\s", + "description": "SSH, SCP, and SFTP access"}, + # Terraform -- full + {"priority": 130, "pattern": r"^(terraform|tofu|pulumi)\s+\w", + "description": "All Terraform/Tofu/Pulumi operations"}, + {"priority": 129, "pattern": r"^(helm|helmfile)\s+\w", + "description": "All Helm operations"}, + {"priority": 128, "pattern": r"^(ansible|ansible-playbook|ansible-galaxy)\s", + "description": "All Ansible operations"}, + # Network diagnostics + {"priority": 120, "pattern": r"^(ping|dig|nslookup|traceroute|tracepath|mtr|curl|wget|host|whois|nmap)\b", + "description": "Network diagnostics"}, + # Git -- full + {"priority": 110, "pattern": r"^git\s+\w", + "description": "All git operations"}, + # Docker/container -- full + {"priority": 100, "pattern": r"^(docker|podman|docker-compose|ctr|crictl)\s+\w", + "description": "All container operations"}, + # System diagnostics + management + {"priority": 90, "pattern": r"^(uptime|whoami|hostname|uname|env|printenv|id|date|cal|free|vmstat|iostat|mpstat|sar|lsof|ss|netstat|ps|top|htop|lscpu|lsmem|lsblk|mount|dmesg|journalctl|systemctl)\b", + "description": "System diagnostics and service management"}, + # Text/utility + {"priority": 80, "pattern": r"^(jq|yq|column|printf|echo|test|true|false|which|type|command|whereis|file|xxd|hexdump|sha256sum|md5sum|base64|tar|gzip|gunzip|zip|unzip|xz)\b", + "description": "Text processing and archive utilities"}, + # Package managers (read) + {"priority": 70, "pattern": r"^(pip|npm|yarn|go|cargo|apt|yum|dnf|brew)\s+(list|show|info|search|outdated|version|--version)\b", + "description": "Package manager queries"}, + ], + "deny": list(_UNIVERSAL_DENY_RULES), + }, + ] def invalidate_cache(org_id: str) -> None: From 09eb2418c666495e0e4406f6fae3402d5b567496 Mon Sep 17 00:00:00 2001 From: Damian Loch Date: Sat, 18 Apr 2026 11:36:42 -0400 Subject: [PATCH 05/19] disable allowlist and denylist by default --- server/routes/command_policies.py | 2 +- server/utils/auth/command_policy.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/routes/command_policies.py b/server/routes/command_policies.py index dedb75a75..9686f1b4b 100644 --- a/server/routes/command_policies.py +++ b/server/routes/command_policies.py @@ -32,7 +32,7 @@ def _list_states(org_id: str) -> dict: """Read allowlist/denylist toggle states from user_preferences.""" org_key = f"__org__{org_id}" - al = get_user_preference(org_key, "command_policy_allowlist") or "on" + al = get_user_preference(org_key, "command_policy_allowlist") or "off" dl = get_user_preference(org_key, "command_policy_denylist") or "off" return { "allowlist_enabled": str(al).lower() == "on", diff --git a/server/utils/auth/command_policy.py b/server/utils/auth/command_policy.py index b51fe10c9..e5274da1b 100644 --- a/server/utils/auth/command_policy.py +++ b/server/utils/auth/command_policy.py @@ -72,7 +72,7 @@ def _fetch(org_id: str) -> Tuple[List[PolicyRule], List[PolicyRule], ListStates] """ allow_rules: List[PolicyRule] = [] deny_rules: List[PolicyRule] = [] - states = ListStates(allowlist_enabled=True, denylist_enabled=False) + states = ListStates(allowlist_enabled=False, denylist_enabled=False) try: from utils.db.connection_pool import db_pool @@ -102,7 +102,7 @@ def _fetch(org_id: str) -> Tuple[List[PolicyRule], List[PolicyRule], ListStates] deny_rules.append(rule) org_pref_key = f"__org__{org_id}" - al_raw = get_user_preference(org_pref_key, "command_policy_allowlist") or "on" + al_raw = get_user_preference(org_pref_key, "command_policy_allowlist") or "off" dl_raw = get_user_preference(org_pref_key, "command_policy_denylist") or "off" states = ListStates( allowlist_enabled=(str(al_raw).lower() == "on"), From 7551e8e08e2d4f4fc6d7aa23bcf1620c94a3361b Mon Sep 17 00:00:00 2001 From: Damian Loch Date: Sat, 18 Apr 2026 12:32:12 -0400 Subject: [PATCH 06/19] fixes to ui state --- .../command-policy-templates/active/route.ts | 16 +++++++++ client/src/components/SecuritySettings.tsx | 35 +++++++++++++++++-- client/src/lib/services/command-policies.ts | 6 +++- server/routes/command_policies.py | 30 ++++++++++++++++ server/utils/auth/command_policy.py | 6 ++-- 5 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 client/src/app/api/org/command-policy-templates/active/route.ts diff --git a/client/src/app/api/org/command-policy-templates/active/route.ts b/client/src/app/api/org/command-policy-templates/active/route.ts new file mode 100644 index 000000000..19dcdda27 --- /dev/null +++ b/client/src/app/api/org/command-policy-templates/active/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from "next/server"; +import { getAuthenticatedUser } from "@/lib/auth-helper"; + +const API_BASE_URL = process.env.BACKEND_URL; + +export async function DELETE() { + if (!API_BASE_URL) return NextResponse.json({ error: "BACKEND_URL not configured" }, { status: 500 }); + const authResult = await getAuthenticatedUser(); + if (authResult instanceof NextResponse) return authResult; + const res = await fetch(`${API_BASE_URL}/api/org/command-policy-templates/active`, { + method: "DELETE", + headers: authResult.headers, + }); + const data = await res.json().catch(() => ({})); + return NextResponse.json(data, { status: res.status }); +} diff --git a/client/src/components/SecuritySettings.tsx b/client/src/components/SecuritySettings.tsx index 8b752c86b..31046069e 100644 --- a/client/src/components/SecuritySettings.tsx +++ b/client/src/components/SecuritySettings.tsx @@ -112,11 +112,15 @@ function AddRuleForm({ function TemplatePicker({ templates, applying, + activeId, onApply, + onRemove, }: { templates: PolicyTemplate[]; applying: string | null; + activeId: string | null; onApply: (id: string) => void; + onRemove: () => void; }) { const [confirmId, setConfirmId] = useState(null); const [expandedId, setExpandedId] = useState(null); @@ -137,17 +141,29 @@ function TemplatePicker({
{templates.map((tpl) => { const expanded = expandedId === tpl.id; + const active = activeId === tpl.id; return (
-

{tpl.name}

+

+ {tpl.name} +

{tpl.description}

- {confirmId === tpl.id ? ( + {active ? ( +
+ + Active + + +
+ ) : confirmId === tpl.id ? (
@@ -481,6 +483,7 @@ export function SecuritySettings() { checked={denylistEnabled} onCheckedChange={(v) => handleToggleList("denylist", v)} className="scale-90" + aria-label="Toggle denylist" />
@@ -524,6 +527,7 @@ export function SecuritySettings() { checked={allowlistEnabled} onCheckedChange={(v) => handleToggleList("allowlist", v)} className="scale-90" + aria-label="Toggle allowlist" />
diff --git a/server/routes/command_policies.py b/server/routes/command_policies.py index eb8068f2d..be1cc7b70 100644 --- a/server/routes/command_policies.py +++ b/server/routes/command_policies.py @@ -331,7 +331,7 @@ def apply_template(user_id): pref_upsert = ( "INSERT INTO user_preferences (user_id, org_id, preference_key, preference_value) " "VALUES (%s, %s, %s, %s) " - "ON CONFLICT (user_id, org_id, preference_key) DO UPDATE " + "ON CONFLICT (user_id, org_id, preference_key) WHERE org_id IS NOT NULL DO UPDATE " "SET preference_value = EXCLUDED.preference_value, updated_at = CURRENT_TIMESTAMP" ) from utils.db.connection_pool import db_pool @@ -373,7 +373,7 @@ def clear_active_template(user_id): pref_upsert = ( "INSERT INTO user_preferences (user_id, org_id, preference_key, preference_value) " "VALUES (%s, %s, %s, %s) " - "ON CONFLICT (user_id, org_id, preference_key) DO UPDATE " + "ON CONFLICT (user_id, org_id, preference_key) WHERE org_id IS NOT NULL DO UPDATE " "SET preference_value = EXCLUDED.preference_value, updated_at = CURRENT_TIMESTAMP" ) from utils.db.connection_pool import db_pool diff --git a/server/utils/auth/stateless_auth.py b/server/utils/auth/stateless_auth.py index 6d8711ff3..d4d58334e 100644 --- a/server/utils/auth/stateless_auth.py +++ b/server/utils/auth/stateless_auth.py @@ -360,7 +360,7 @@ def store_user_preference(user_id: str, key: str, value: Any): cursor.execute(""" INSERT INTO user_preferences (user_id, org_id, preference_key, preference_value) VALUES (%s, %s, %s, %s) - ON CONFLICT (user_id, org_id, preference_key) DO UPDATE SET + ON CONFLICT (user_id, org_id, preference_key) WHERE org_id IS NOT NULL DO UPDATE SET preference_value = EXCLUDED.preference_value, updated_at = CURRENT_TIMESTAMP """, (user_id, org_id, key, json.dumps(value)))