Suvra

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, or policy="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.mode should stay "deny" — Suvra is deny-by-default, and any action that matches no rule is denied regardless.
  • Each rule has an id, an effect (allow, deny, or needs_approval), a type (the action type it matches, e.g. fs.write_file, database.drop, mcp.<tool_name>), and optional constraints that 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:

  1. Global policy (SuvraPolicy) applies to every agent.
  2. Agent policy (optional) is bound to a specific agent via agents.policy_id.

Precedence is deterministic:

  1. Global policy rules are evaluated in listed order
  2. Agent policy rules are evaluated in listed order
  3. Within a policy, the last matching rule wins
  4. Agent-policy matches override global-policy matches
  5. If no rule matches, the final decision is deny

Legacy scopes — tenant, business unit, domain, workspace, environment — remain in audit metadata and params_hash normalization 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, environment
  • labels — 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 (for shell.exec)
  • allow_providers, allow_mailboxes (case-insensitive, for email.delete)
  • allow_names (exact match, for secrets.read)

Security scoring

  • injection_risk_min / injection_risk_max — thresholds from 0.0 to 1.0
  • injection_scanner — match a specific scanner (e.g. lakera, presidio)

Anomaly detection

  • anomaly_detectedtrue/false
  • anomaly_severity_in — list of low / 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_id and matched_policy_id — the winning rule (present even on constraint-failure fallback)
  • reasons — business-readable strings
  • checks — per-constraint {name, ok, detail} entries
  • decision_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.