Policy Model
The zero-config classifier catches the catastrophic cases everyone agrees on. A policy lets you say exactly what your agent is allowed to do — everything else denies by default. The classifier remains a floor underneath: when both evaluate an action, the more restrictive decision wins (Core Concepts).
Policies run in two places, with the same evaluation semantics:
- A local policy file (
SUVRA_POLICY_PATH, orpolicy="policy.yaml"in the SDK) — how the free local firewall is configured. - Control-plane policies managed in the dashboard, composed from the Rules Library — how Team/Enterprise deployments manage policy across agents.
Writing a policy file
Policy files are JSON or YAML with two top-level keys, defaults and rules:
{
"defaults": {
"mode": "deny"
},
"rules": [
{
"id": "allow_workspace_writes",
"effect": "allow",
"type": "fs.write_file",
"constraints": {
"path_prefix": "workspace/",
"max_bytes": 1048576
}
},
{
"id": "delete_requires_approval",
"effect": "needs_approval",
"type": "fs.delete_file",
"constraints": {
"path_prefix": "workspace/"
}
},
{
"id": "deny_database_drop",
"effect": "deny",
"type": "database.drop",
"constraints": {}
}
]
}
defaults.modeshould stay"deny"— Suvra is deny-by-default, and any action that matches no rule is denied regardless.- Each rule has an
id, aneffect(allow,deny, orneeds_approval), atype(the action type it matches, e.g.fs.write_file,database.drop,mcp.<tool_name>), and optionalconstraintsthat narrow the match. - Within a policy, the last matching rule wins — put narrower/stricter rules after broader ones when they should take precedence.
Point Suvra at the file with SUVRA_POLICY_PATH=./my-policy.yaml, or pass policy="my-policy.yaml" to the SDK.
Deny-by-default
If no rule matches, the decision is always deny. There is no allow_all, no flag that changes this. Deny-by-default is a hard guarantee, not a configurable setting.
Two-tier evaluation
In a control-plane deployment, active runtime evaluation uses exactly two policy layers:
- Global policy (
SuvraPolicy) applies to every agent. - Agent policy (optional) is bound to a specific agent via
agents.policy_id.
Precedence is deterministic:
- Global policy rules are evaluated in listed order
- Agent policy rules are evaluated in listed order
- Within a policy, the last matching rule wins
- Agent-policy matches override global-policy matches
- If no rule matches, the final decision is
deny
Legacy scopes — tenant, business unit, domain, workspace, environment — remain in audit metadata and
params_hashnormalization for backward compatibility, but they are not active policy scopes. Only global + agent policies are evaluated.
Control-plane policies don't inline rule logic; they hold ordered references into the Rules Library ({ rule_id, enabled }), hydrated into fully materialized rules at evaluation time. See Rules Library and the Policy dashboard for managing them.
Constraints reference
A rule matches only when its type matches the action's type and every configured constraint passes. Matching is exact-string for scalar constraints.
Filesystem
path_prefix,max_bytes,working_dir_prefix
HTTP
method,allow_domains,timeout_seconds,host_in,host_prefix
Identity / request context
agent,user,role,workspace,environmentlabels— every listed label must be present (deterministic normalization: trim, dedupe, sort)tenant_id,business_unit,domain— retained for compatibility-sensitive matching
Command / provider
allow_commands(forshell.exec)allow_providers,allow_mailboxes(case-insensitive, foremail.delete)allow_names(exact match, forsecrets.read)
Security scoring
injection_risk_min/injection_risk_max— thresholds from 0.0 to 1.0injection_scanner— match a specific scanner (e.g.lakera,presidio)
Anomaly detection
anomaly_detected—true/falseanomaly_severity_in— list oflow/medium/high
Anomaly and injection constraints
Two constraint groups enforce on signals rather than action shape:
Injection risk — Suvra does not compute injection risk; it enforces on scores from your existing scanner (Lakera, Presidio, custom). Pass injection_risk (0.0–1.0) and optional injection_scanner in the action payload, then write rules with the threshold constraints:
rule_id: block_high_injection
effect: deny
action_type: "*"
constraints:
injection_risk_min: 0.8
Behavioral anomaly detection — opt-in (SUVRA_ANOMALY_DETECTION_ENABLED, see the environment reference). Suvra computes per-agent statistical baselines from audit history — action rates, unique HTTP domains, deny rates — and flags deviations at low/medium/high severity, which rules can match with anomaly_detected / anomaly_severity_in. Anomaly scoring never blocks enforcement itself; if computation fails, the action proceeds with no anomaly fields attached.
Example rules
Allow writes under workspace/reports/ up to 2 MiB, require approval for writes anywhere else:
rules:
- id: fs.write_reports_allow
type: fs.write_file
effect: allow
constraints:
path_prefix: workspace/reports/
max_bytes: 2097152
- id: fs.write_other_approve
type: fs.write_file
effect: needs_approval
HTTP allow-list:
rules:
- id: http.trusted_domains_allow
type: http.request
effect: allow
constraints:
method: GET
allow_domains:
- api.example.com
- data.example.com
timeout_seconds: 10
Environment-aware deny (allow deletes in the workspace, never in production):
rules:
- id: fs.delete_any_allow
type: fs.delete_file
effect: allow
constraints:
path_prefix: workspace/
- id: fs.delete_prod_deny
type: fs.delete_file
effect: deny
constraints:
environment: production
Identity-aware approvals:
rules:
- id: secrets.read_critical_approve
type: secrets.read
effect: needs_approval
constraints:
allow_names: [stripe_api_key, github_token]
labels:
- risk:critical
Testing with the simulator
/simulate evaluates an action against policy without executing side effects and without creating approvals — always in strict mode, regardless of SUVRA_MODE:
suvra serve
# then, in another terminal:
curl -s -X POST http://127.0.0.1:8000/simulate \
-H 'content-type: application/json' \
-d '{"action_id":"t1","type":"fs.delete_file","params":{"path":"workspace/report.csv"},"meta":{"actor":"demo"}}'
The response includes a structured decision_trace showing exactly which rule matched and why. The dashboard has the same capability at /dashboard/simulate, plus an Effective View on the Policy dashboard that renders the merged global + agent rule stack for any agent.
Explainability output
Every policy-driven response — validate, execute, simulate — includes:
matched_rule_idandmatched_policy_id— the winning rule (present even on constraint-failure fallback)reasons— business-readable stringschecks— per-constraint{name, ok, detail}entriesdecision_trace— structured trace: loaded policy layers, matched rules, precedence order, winning rule, overridden rules, final decision, reason
Audit persistence stores this explainability with every event, so any historical decision can be re-rendered — see the Audit Explorer.
Related
- Rules Library — the built-in rules control-plane policies compose from
- Policy dashboard — create policies, add rules, effective view, rule builder
- Human approvals & Slack — what
needs_approvaltriggers