Metadata-Version: 2.5
Name: initguard
Version: 2026.3.4
Summary: Embedded ABAC policy engine for AI agents
Project-URL: Repository, https://github.com/vladkesler/initguard
Project-URL: Issues, https://github.com/vladkesler/initguard/issues
Author-email: vladimir kesler <contact@initrunner.ai>
License-Expression: MIT OR Apache-2.0
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
Keywords: abac,agent,ai,authorization,cel,policy,yaml
Classifier: Development Status :: 7 - Inactive
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Security
Requires-Python: >=3.11
Requires-Dist: cel-python>=0.5.0
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.15.7; extra == 'dev'
Requires-Dist: ty>=0.0.1a7; extra == 'dev'
Description-Content-Type: text/markdown

# initguard

> **Unmaintained.** initguard is no longer maintained and will not get security fixes. The GitHub repository is being deleted. Existing releases stay on PyPI so old lockfiles keep resolving. Don't use it in new projects.

Embedded ABAC policy engine for AI agents.

[![PyPI](https://img.shields.io/pypi/v/initguard)](https://pypi.org/project/initguard/)
[![Python](https://img.shields.io/pypi/pyversions/initguard)](https://pypi.org/project/initguard/)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](LICENSE-MIT)

Write authorization policies in YAML with [CEL](https://cel.dev/) conditions, evaluate them in-process. No sidecar, no HTTP, no external service. Policies use `when`/`unless` clauses for readable rules and return structured `Decision` objects with human-readable reasons and advice.

## Install

```bash
pip install initguard
```

## Quick start

Create a policy file:

```yaml
# policies/tool_policy.yaml
apiVersion: initguard/v1
kind: ResourcePolicy
resource: tool
rules:
  - actions: ["execute"]
    effect: allow
    roles: ["agent"]
    when: request.resource.attr.tool_type in ["http", "search"]

  - actions: ["execute"]
    effect: deny
    roles: ["agent"]
    when: request.resource.attr.tool_type == "shell"
    unless: request.principal.attr.tags.exists(t, t == "trusted")
    advice: "Shell tools require the 'trusted' tag."
```

Load and evaluate:

```python
from initguard import PolicyEngine, Principal, load_policies

engine = PolicyEngine(load_policies("./policies"))

agent = Principal(
    id="agent:helper",
    roles=["agent"],
    attrs={"tags": ["basic"]},
)

decision = engine.check(agent, "tool", "execute", resource_attrs={"tool_type": "shell"})

print(decision.allowed)  # False
print(decision.reason)   # "denied by tool_policy.yaml:rules[1]"
print(decision.advice)   # "Shell tools require the 'trusted' tag."
```

## Policy format

Three document kinds, all in YAML.

### Schema (optional)

Lint your policies against known attribute names. Catches typos at load time.

```yaml
apiVersion: initguard/v1
kind: Schema
principals:
  agent:
    attrs:
      team: string
      tags: list
resources:
  tool:
    attrs:
      tool_type: string
    actions: [execute]
```

### DerivedRoles

Conditional role elevation using CEL. A definition matches when `when` is true and `unless` is false (or absent). Names must be globally unique.

```yaml
apiVersion: initguard/v1
kind: DerivedRoles
name: agent_roles
definitions:
  - name: trusted_agent
    parentRoles: ["agent"]
    when: request.principal.attr.tags.exists(t, t == "trusted")

  - name: same_team
    parentRoles: ["agent"]
    when: request.principal.attr.team != ""
    unless: request.principal.attr.team != request.resource.attr.team
```

### ResourcePolicy

Allow/deny rules for a resource kind. Deny always wins over allow. Every rule must declare at least one of `roles` or `derivedRoles`.

```yaml
apiVersion: initguard/v1
kind: ResourcePolicy
resource: tool
importDerivedRoles: [agent_roles]
rules:
  - actions: ["execute"]
    effect: allow
    derivedRoles: ["trusted_agent"]

  - actions: ["execute"]
    effect: deny
    roles: ["agent"]
    when: request.resource.attr.tool_type in ["shell", "python"]
    unless: request.principal.attr.tags.exists(t, t == "trusted")
    advice: "Requires the 'trusted' tag."
```

Derived roles are **scoped to imports** -- a ResourcePolicy only sees roles from the sets it explicitly imports, preventing privilege leakage across policy domains.

## API

### `load_policies(policy_dir) -> PolicySet`

Load all `*.yaml` / `*.yml` files from a directory. Files are loaded in sorted order for deterministic precedence. All CEL expressions are compiled at load time. Raises `PolicyLoadError` on invalid YAML, bad CEL syntax, duplicate role names, or broken references.

### `PolicyEngine(policy_set)`

```python
engine = PolicyEngine(policy_set)
```

#### `engine.check(principal, resource_kind, action, resource_id="*", resource_attrs=None) -> Decision`

Evaluate policies and return a `Decision`. Also available as `check_async` for async contexts.

#### `engine.info() -> EngineInfo`

Returns loaded policy summary: resource kinds, derived role sets, policy/rule counts, schema presence.

### `Decision`

| Field | Type | Description |
|-------|------|-------------|
| `allowed` | `bool` | Whether the action is permitted |
| `reason` | `str` | Human-readable explanation, always populated |
| `matched_rule` | `str` | Stable rule identifier, e.g. `"tool_policy.yaml:rules[1]"` |
| `advice` | `str` | From the rule's `advice` field, or empty |

### `Principal`

| Field | Type | Description |
|-------|------|-------------|
| `id` | `str` | Identity string, e.g. `"agent:code-reviewer"` |
| `roles` | `list[str]` | Role list, e.g. `["agent", "team:platform"]` |
| `attrs` | `dict[str, Any]` | Attributes for CEL conditions |

## CEL expressions

Conditions use [Google Common Expression Language](https://cel.dev/). Supported patterns:

```python
# List membership
request.resource.attr.tool_type in ["shell", "python"]

# Existential quantifier
request.principal.attr.tags.exists(t, t == "trusted")

# Attribute equality
request.principal.attr.team == request.resource.attr.team

# Boolean operators
!request.principal.attr.tags.exists(t, t == "admin") && request.resource.attr.tool_type == "shell"
```

Missing attributes in CEL expressions evaluate to `false` (condition not met), not an error.

## Development

```bash
uv sync --all-extras
uv run pytest tests/ -v
uv run ruff check . && uv run ruff format --check .
```

## License

MIT OR Apache-2.0
