Metadata-Version: 2.4
Name: suvra
Version: 0.1.4
Summary: Suvra v0 secure execution guardrail layer for autonomous agents
License: MIT License
        
        Copyright (c) 2026 Avinash Aila
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.111.0
Requires-Dist: uvicorn>=0.30.0
Requires-Dist: pydantic>=2.7.0
Requires-Dist: pyyaml>=6.0.1
Requires-Dist: httpx>=0.27.0
Requires-Dist: jinja2>=3.1.0
Provides-Extra: dev
Requires-Dist: pytest>=8.2.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Dynamic: license-file

# Suvra

> Secure, policy-first execution guardrails for autonomous agents.

![Status](https://img.shields.io/badge/status-active-success)
![PyPI](https://img.shields.io/pypi/v/suvra)
![Python](https://img.shields.io/pypi/pyversions/suvra)

Suvra is a guardrail layer that sits between an agent and real-world side effects.
It evaluates declarative actions against `policy.yaml`, enforces allow/deny/approval decisions, executes deterministically, and logs an audit trail to SQLite.

This matters when you want AI automation with explicit control,
deterministic enforcement, explainable decisions, and rollback-ready operations.

Suvra turns probabilistic agent intent into policy-governed, auditable execution.

## Install

```bash
pip install suvra
```

Requires Python 3.9+.

## Core Guarantees

- Deny-by-default enforcement.
- Deterministic policy enforcement with no LLM in the enforcement path.
- Approval gating for sensitive actions.
- Workspace jail confinement with configurable root via `SUVRA_WORKSPACE_DIR`.
- Fail-closed guard-boundary actions (`shell.exec`, `email.delete`, `secrets.read`).
- SQLite audit trail.

## Architecture
Agent → Suvra Policy Engine → Executor → Audit Log

## Table of Contents

- [Features](#features)
- [Install](#install)
- [Core Guarantees](#core-guarantees)
- [Project Layout](#project-layout)
- [Quickstart](#quickstart)
  - [Embedded SDK (in-process)](#embedded-sdk-in-process)
  - [Remote SDK (HTTP)](#remote-sdk-http)
- [Examples](#examples)
  - [Embedded Guard usage](#embedded-guard-usage)
  - [Remote Guard usage](#remote-guard-usage)
  - [OpenClaw adapter example](#openclaw-adapter-example)
  - [guarded_tool decorator example](#guarded_tool-decorator-example)
- [API Usage](#api-usage)
- [CLI Usage](#cli-usage)
- [Dashboard](#dashboard)
- [Enforcement Modes](docs/enforcement_modes.md)
- [Feedback](#feedback)
- [License](#license)

## Features

- Policy-based enforcement with deny-by-default fallback.
- Decision modes: `allow`, `deny`, `needs_approval`.
- Deterministic action executors:
  - `fs.write_file`
  - `fs.delete_file`
  - `http.request` (GET only)
- Policy/simulation-only action types (recognized by policy + `/simulate`, execution fails closed):
  - `shell.exec`
  - `email.delete`
  - `secrets.read`
- Dry-run validation and execution flows.
- Approval workflow endpoints and rollback endpoint.
- Embedded SDK (`Guard(policy=...)`) and Remote SDK (`Guard(url=...)`).
- SQLite audit logging (`data/audit.db`).
- FastAPI service and a direct-library CLI.

## Project Layout

- `suvra/app/main.py` - FastAPI app
- `suvra/core/policy.py` - policy loading/evaluation
- `suvra/core/executors/` - action executors
- `suvra/core/audit.py` - SQLite audit log
- `suvra/core/service.py` - orchestration layer
- `suvra/sdk/guard.py` - SDK guard (embedded + remote)
- `suvra/sdk/decorators.py` - `guarded_tool` decorator
- `suvra/integrations/openclaw.py` - OpenClaw adapter
- `suvra/cli.py` - CLI tool
- `examples/` - sample action payloads
- `tests/` - pytest tests

## Quickstart

### Embedded SDK (in-process)

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
```

```python
from suvra import Guard

guard = Guard(policy="policy.yaml", db_path="data/audit.db")

result = guard.execute(
    {
        "action_id": "quickstart-embedded",
        "type": "fs.write_file",
        "params": {
            "path": "workspace/quickstart_embedded.txt",
            "content": "hello from embedded mode"
        },
        "meta": {"actor": "quickstart"},
        "dry_run": False,
    }
)

print(result)
```

### Remote SDK (HTTP)

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
uvicorn suvra.app.main:app --reload
# or
suvra serve
```

```python
from suvra import Guard

guard = Guard(url="http://127.0.0.1:8000")

result = guard.validate(
    {
        "action_id": "quickstart-remote",
        "type": "fs.write_file",
        "params": {
            "path": "workspace/quickstart_remote.txt",
            "content": "hello from remote mode"
        },
        "meta": {"actor": "quickstart"},
        "dry_run": True,
    }
)

print(result)
```

Environment config:

- `SUVRA_POLICY_PATH` (default: `policy.yaml`)
- `SUVRA_DB_PATH` (default: `data/audit.db`)
- `SUVRA_WORKSPACE_DIR` (default: `workspace`)

### OpenClaw Quickstart: safest defaults

Suvra starts deny-by-default and only allows narrow actions from the built-in safe template on first run.
Writes must target the configured workspace root (default: `workspace/`), and deletes require approval.

```bash
docker run --rm -p 8000:8000 \
  -e SUVRA_ENV=dev \
  -e SUVRA_DB_PATH=/app/data/audit.db \
  -v "$(pwd)/workspace:/app/workspace" \
  -v "$(pwd)/data:/app/data" \
  suvra:latest
```

Use the configured workspace root (default `workspace/`) as the writable mount for OpenClaw tasks.

## Examples

### Embedded Guard usage

```python
from suvra import Guard

guard = Guard(policy="policy.yaml", db_path="data/audit.db")

action = {
    "action_id": "sdk-embedded-1",
    "type": "fs.write_file",
    "params": {"path": "workspace/embedded.txt", "content": "hello"},
    "meta": {"actor": "sdk"},
    "dry_run": False,
}

result = guard.execute(action)
print(result)
```

### Remote Guard usage

```python
from suvra import Guard

guard = Guard(url="http://127.0.0.1:8000")

action = {
    "action_id": "sdk-remote-1",
    "type": "fs.write_file",
    "params": {"path": "workspace/remote.txt", "content": "hello"},
    "meta": {"actor": "sdk"},
    "dry_run": True,
}

result = guard.validate(action)
print(result)
```

`Guard` raises `ValueError` if both `policy` and `url` are provided.

### OpenClaw adapter example

```python
from suvra import Guard
from suvra.integrations.openclaw import SuvraExecutor

guard = Guard(policy="policy.yaml", db_path="data/audit.db")
executor = SuvraExecutor(guard)

execute_result = executor.execute(
    action_type="fs.write_file",
    params={"path": "workspace/demo/oc_adapter.txt", "content": "openclaw adapter"},
)

validate_result = executor.validate(
    action_type="fs.write_file",
    params={"path": "workspace/demo/oc_adapter.txt", "content": "openclaw adapter"},
)

print(execute_result)
print(validate_result)
```

### guarded_tool decorator example

```python
from pathlib import Path

from suvra import Guard, guarded_tool

guard = Guard(policy="policy.yaml", db_path="data/audit.db")

@guarded_tool(guard, "fs.write_file")
def write_file(path: str, content: str) -> str:
    target = Path(path)
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(content)
    return str(target)

written = write_file(path="workspace/decorated.txt", content="decorator write")
print(written)
```

## API Usage

Run server:

```bash
uvicorn suvra.app.main:app --reload
# or
suvra serve
```

Docker:

```bash
docker build -t suvra:latest .
docker run --rm -p 8000:8000 \
  -e SUVRA_POLICY_PATH=/app/policy.yaml \
  -e SUVRA_DB_PATH=/app/data/audit.db \
  -v "$(pwd)/policy.yaml:/app/policy.yaml:ro" \
  -v "$(pwd)/data:/app/data" \
  suvra:latest
```

Docker Compose:

```bash
docker compose up --build
```

FastAPI endpoints:

- `GET /health`
- `POST /actions/validate`
- `POST /actions/execute`
- `POST /approvals/request`
- `POST /approvals/{approval_id}/approve`
- `POST /approvals/{approval_id}/deny`
- `GET /approvals/{approval_id}`
- `POST /rollback/{action_id}`
- `GET /audit`

Canonical `ActionRequest` shape:

```json
{
  "action_id": "string",
  "type": "string",
  "params": {},
  "meta": {
    "actor": "string",
    "reason": "optional string",
    "approval_id": "optional string"
  },
  "dry_run": false
}
```

Guard-boundary-only action parameter shapes (recognized for policy and simulation, unsupported on execute):

- `shell.exec`:
  - `command` (string, required)
  - `args` (list[string], optional)
  - `cwd` (string, optional)
  - `dry_run` (bool, optional)
- `email.delete`:
  - `provider` (string, optional)
  - `message_id` (string, required)
  - `mailbox` (string, optional)
  - `reason` (string, optional)
  - `dry_run` (bool, optional)
- `secrets.read`:
  - `name` (string, required)
  - `purpose` (string, optional)
  - `dry_run` (bool, optional)

Backward compatibility is preserved for deprecated top-level `actor` and `approval_id`, but clients should migrate to `meta.actor` and `meta.approval_id`.

Validate (dry-run by design):

```bash
curl -s -X POST http://127.0.0.1:8000/actions/validate \
  -H 'content-type: application/json' \
  -d @examples/action_write_ok.json
```

Execute action:

```bash
curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @examples/action_write_ok.json
```

Note: `shell.exec`, `email.delete`, and `secrets.read` are intentionally unsupported by execution.  
`POST /actions/execute` fails closed with `EXECUTION_ERROR` for those action types in all modes.

Execute in dry-run mode (no side effects):

```bash
jq '. + {dry_run: true}' examples/action_write_ok.json | \
  curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @-
```

Simulation-only policy examples (execution still unsupported):

```yaml
rules:
  - id: shell_git_status_only
    effect: allow
    type: shell.exec
    constraints:
      allow_commands: ["git status"]

  - id: email_delete_inbox_approval
    effect: needs_approval
    type: email.delete
    constraints:
      allow_providers: ["gmail"]
      allow_mailboxes: ["INBOX"]

  - id: secrets_openai_key_approval
    effect: needs_approval
    type: secrets.read
    constraints:
      allow_names: ["OPENAI_API_KEY"]
```

Approval flow example (default `policy.yaml` has `fs.delete_file` as `needs_approval`):

```bash
curl -s -X POST http://127.0.0.1:8000/actions/validate \
  -H 'content-type: application/json' \
  -d @examples/action_delete_file.json
```

Delete with approval + rollback demo (using default `policy.yaml`):

```bash
# 1) Write a file
curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @examples/action_write_ok.json

# 2) Execute delete without approval_id -> returns decision=needs_approval + approval_id
curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @examples/action_delete_file.json

# 3) Approve it
curl -s -X POST http://127.0.0.1:8000/approvals/<approval_id>/approve \
  -H 'content-type: application/json' \
  -d '{"decided_by":"admin","note":"approved"}'

# 4) Re-execute delete with meta.approval_id
jq --arg approval_id "<approval_id>" '.meta += {approval_id:$approval_id}' examples/action_delete_file.json | \
  curl -s -X POST http://127.0.0.1:8000/actions/execute \
  -H 'content-type: application/json' \
  -d @-

# 5) Roll back the delete by action_id
curl -s -X POST http://127.0.0.1:8000/rollback/delete-1 \
  -H 'content-type: application/json' \
  -d '{"dry_run":false}'

# 6) Verify restored file content
cat workspace/demo/hello.txt
```

## CLI Usage
The CLI wraps the same EnforcementEngine used by the SDK and API.

Validate from JSON file:

```bash
python -m suvra.cli validate examples/action_write_ok.json
```

Execute from JSON file:

```bash
python -m suvra.cli execute examples/action_write_ok.json
```

Execute dry-run:

```bash
python -m suvra.cli execute examples/action_write_ok.json --dry-run
```

Run tests:

```bash
pytest
```

## Dashboard

Start the server:

```bash
suvra serve
```

Then open `http://127.0.0.1:8000/dashboard`.

## Feedback

Suvra is currently in soft beta.

If you're experimenting with it in OpenClaw or other agent frameworks and have feedback, ideas, or edge cases to report, feel free to reach out.

Early builder feedback is especially appreciated.

## Documentation Rule

Any change that affects:

- API endpoints
- Policy schema
- Enforcement logic
- Executors
- Enforcement modes
- Audit schema
- Dashboard routes
- SDK behavior
- Metrics

MUST update `docs/SUVRA_CONTEXT.md` in the same commit.

Commits modifying `core/` without updating `SUVRA_CONTEXT.md` are incomplete.

## License

MIT License. See the LICENSE file for details.
