Metadata-Version: 2.4
Name: checkrd
Version: 0.1.0
Summary: Proxy layer for AI agent API calls. Policy enforcement, credential vaulting, kill switch, and observability.
Project-URL: Homepage, https://checkrd.dev
Project-URL: Documentation, https://docs.checkrd.dev
Project-URL: Repository, https://github.com/checkrd/checkrd
Project-URL: Issues, https://github.com/checkrd/checkrd/issues
Author-email: Checkrd <hello@checkrd.dev>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,ai,observability,policy,proxy,security
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software 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
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<2,>=0.24.0
Requires-Dist: pyyaml<7,>=6.0
Requires-Dist: wasmtime<100,>=21.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# checkrd

Proxy layer for AI agent API calls. Policy enforcement, credential vaulting, kill switch, and observability -- with one line of code.

**62μs median overhead per request.** Policy evaluation runs in a WebAssembly sandbox via a Rust core compiled to WASM.

## Install

```bash
pip install checkrd
```

## Quick Start

```python
from checkrd import wrap
import httpx

client = wrap(
    httpx.Client(),
    agent_id="sales-agent",
    policy="policy.yaml",
    credentials={"api.stripe.com": [["Authorization", "Bearer sk_live_xxx"]]},
)

response = client.get("https://api.stripe.com/v1/charges")
```

## Policy Format

Policies are YAML files that define what your agent is allowed to do:

```yaml
agent: sales-agent
default: deny

rules:
  - name: read-contacts
    allow:
      method: [GET]
      url: "api.salesforce.com/*/sobjects/Contact/*"

  - name: create-small-charges
    allow:
      method: [POST]
      url: "api.stripe.com/v1/charges"
    body:
      jsonpath: "$.amount"
      max: 50000

  - name: block-all-deletes
    deny:
      method: [DELETE]
      url: "*"

  - name: rate-limit
    limit:
      calls_per_minute: 60
      per: endpoint

  - name: business-hours-only
    deny:
      time_outside: "09:00-17:00"
      timezone: "UTC"
```

## Configuration

```python
# From a YAML file
client = wrap(httpx.Client(), agent_id="agent", policy="./policy.yaml")

# From a dict
client = wrap(httpx.Client(), agent_id="agent", policy={
    "agent": "my-agent",
    "default": "deny",
    "rules": [{"name": "allow-all-get", "allow": {"method": ["GET"], "url": "*"}}],
})

# From default location (~/.checkrd/policy.yaml)
client = wrap(httpx.Client(), agent_id="agent")

# Override config directory via environment variable
# CHECKRD_CONFIG_DIR=/app/config
```

## Credential Injection

Credentials are injected into allowed requests automatically. The agent never sees the real API keys:

```python
client = wrap(
    httpx.Client(),
    agent_id="agent",
    policy=policy,
    credentials={
        "api.stripe.com": [["Authorization", "Bearer sk_live_xxx"]],
        "api.salesforce.com": [["Authorization", "Bearer sf_token"]],
    },
)

# The Authorization header is injected by the engine, not by your code
response = client.get("https://api.stripe.com/v1/charges")
```

Credentials can also be loaded from `~/.checkrd/credentials.json`.

## Error Handling

Denied requests raise `CheckrdPolicyDenied`:

```python
from checkrd import wrap, CheckrdPolicyDenied

client = wrap(httpx.Client(), agent_id="agent", policy=policy)

try:
    client.delete("https://api.stripe.com/v1/charges/ch_xxx")
except CheckrdPolicyDenied as e:
    print(e.reason)      # "denied by rule 'block-all-deletes'"
    print(e.request_id)  # UUID for correlation with telemetry
```

## Async Support

```python
from checkrd import wrap_async
import httpx

client = wrap_async(httpx.AsyncClient(), agent_id="agent", policy=policy)
response = await client.get("https://api.stripe.com/v1/charges")
```

## Dry-Run Mode

Observe policy decisions without blocking requests. Use this to roll out Checkrd safely:

```python
client = wrap(httpx.Client(), agent_id="agent", policy=policy, enforce=False)

# Denied requests are logged as warnings but still forwarded
response = client.delete("https://api.stripe.com/v1/charges/ch_xxx")
# WARNING: checkrd: req-xxx would be denied (dry-run): denied by rule 'block-all-deletes'
```

## Disabling

Bypass all policy evaluation without code changes:

```bash
CHECKRD_DISABLED=1 python my_agent.py
```

## Logging

Checkrd logs to the `checkrd` Python logger:

```python
import logging

# See all policy decisions
logging.getLogger("checkrd").setLevel(logging.INFO)

# See evaluation timing (microseconds per request)
logging.getLogger("checkrd").setLevel(logging.DEBUG)
```

Log levels:
- `DEBUG` -- evaluation timing per request
- `INFO` -- allowed requests with status code and latency
- `WARNING` -- denied requests, dry-run denials

## Security

- The WASM core runs in a sandbox with no filesystem, network, or system call access
- Credentials are stored locally. For production, use a secrets manager and inject via environment variables
- Request/response bodies are never stored or transmitted in telemetry
- See [SECURITY.md](SECURITY.md) for vulnerability reporting

## License

Apache-2.0
