Metadata-Version: 2.4
Name: crewai-agentlock
Version: 0.1.0
Summary: AgentLock authorization middleware for CrewAI tool calls.
Project-URL: Homepage, https://agentlock.dev
Project-URL: Source, https://github.com/webpro255/crewai-agentlock
Author: AgentLock contributors
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agent,agentlock,ai,authorization,crewai
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.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: agentlock>=1.2.1
Requires-Dist: crewai>=1.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# crewai-agentlock

[![PyPI version](https://img.shields.io/pypi/v/crewai-agentlock.svg)](https://pypi.org/project/crewai-agentlock/)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
[![Tests](https://img.shields.io/badge/tests-49%20passing-brightgreen)](#)
[![agentlock.dev](https://img.shields.io/badge/site-agentlock.dev-0a0a0a)](https://agentlock.dev)

Per-tool authorization for CrewAI agents. Every tool call gated, logged, and cryptographically signed.

## The Problem

CrewAI registers tools permissively by default. If an agent has a tool in its `tools=[...]` list, it can invoke it on any input the model produces. When one agent delegates to another, permissions are not enforced at the tool level: the receiving agent runs the call with whatever role its own configuration grants. There is no runtime layer that evaluates whether *this specific call*, with *these parameters*, by *this caller*, at *this point in the session*, should be allowed.

## Benchmark

AgentLock core ships with 847 tests. Benchmarked against 222 adversarial attack vectors with a reference defender scoring 99.5/A. Full benchmark methodology at [agentlock.dev](https://agentlock.dev).

## Install

```bash
pip install crewai-agentlock
```

## Quick Start

```python
from agentlock import AgentLockPermissions, AuthorizationGate
from crewai import Agent, Crew, Task
from crewai.tools import tool
from crewai_agentlock import agentlock_session, lock_crew

@tool
def web_search(query: str) -> str:
    """Search the web."""
    return f"results for {query}"

@tool
def write_summary(topic: str) -> str:
    """Write a summary."""
    return f"summary of {topic}"

researcher = Agent(role="researcher", goal="research", backstory="A researcher.",
                   tools=[web_search, write_summary], allow_delegation=False)
writer = Agent(role="writer", goal="write", backstory="A writer.",
               tools=[write_summary], allow_delegation=False)

crew = Crew(
    agents=[researcher, writer],
    tasks=[
        Task(description="research", expected_output="notes", agent=researcher),
        Task(description="write", expected_output="text", agent=writer),
    ],
)

gate = AuthorizationGate()
lock_crew(crew, gate, permissions={
    "web_search": AgentLockPermissions(
        risk_level="medium",
        allowed_roles=["researcher"],
        rate_limit={"max_calls": 5, "window_seconds": 60},
        scope={"data_boundary": "authenticated_user_only", "max_records": 50},
    ),
    "write_summary": AgentLockPermissions(
        risk_level="low",
        allowed_roles=["researcher", "writer"],
    ),
})

with agentlock_session(user_id="u1", role="researcher", session_id="s1"):
    crew.kickoff()
```

The writer agent cannot call `web_search` even though the function is importable in the same process. Authorization is enforced at the gate, not at tool registration.

## Delegation Enforcement

When Agent A delegates to Agent B, Agent B inherits the intersection of its own permissions and Agent A's permissions. A child agent can never exceed the permissions of the parent that spawned it. This is enforced through the active `agentlock_session()` and the role bound to it for the duration of the delegated call.

```python
from crewai_agentlock import agentlock_session

with agentlock_session(user_id="u1", role="researcher"):
    # Researcher delegates to writer. The session role stays "researcher",
    # so the writer subtask can call any tool the researcher can call,
    # but tools whose allowed_roles exclude "researcher" still deny.
    result = crew.kickoff()
```

The session role is the upper bound. Even if the writer's own `allowed_roles` would permit a sensitive tool, the session role gates the call. Delegation cannot escalate.

## Decision Types

Every `gate.authorize()` call returns one of five decisions:

- **ALLOW**: the call meets every policy. The gate issues a single-use, parameter-bound execution token and the tool runs.
- **DENY**: the call fails one or more policies (role, scope, rate limit, data classification, hardening). The tool body never executes.
- **MODIFY**: the call is allowed but parameters or output are transformed first. Used when a policy can sanitize the request rather than reject it.
- **DEFER**: the gate cannot decide without out-of-band human review. The tool is suspended and a deferral id is returned to the caller.
- **STEP_UP**: the call requires fresh authentication beyond the existing session. Triggered on first use of high-risk tools, post-denial retries, and accumulated PII access in one session.

Example of MODIFY rewriting a path before execution:

```python
from agentlock import AgentLockPermissions

write_file_perms = AgentLockPermissions(
    risk_level="high",
    allowed_roles=["admin"],
    modify_policy={
        "enabled": True,
        "transformations": [
            {
                "type": "regex_replace",
                "field": "path",
                "pattern": r"^\.\./",
                "replacement": "",
            },
        ],
    },
)
```

A request with `path="../etc/passwd"` is rewritten to `path="etc/passwd"` and runs. A whitelisted-only field that fails its allowlist escalates MODIFY to DENY automatically.

## Audit Trail

Every authorization decision produces an Ed25519 signed receipt:

```json
{
  "receipt_id": "rcpt_a3f7c91b2d4e6f80",
  "timestamp": 1745776800.123456,
  "decision": "allow",
  "tool_name": "web_search",
  "user_id": "u1",
  "role": "researcher",
  "parameters_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "reason": "",
  "policy_version_hash": "c2b1...",
  "context_hash": "5e88...",
  "trust_ceiling": "derived",
  "signing_key_id": "key_0a1b2c3d",
  "signature": "f3a2..."
}
```

Receipts are hash-chained. Tamper with one and the entire chain breaks.

## Related

- AgentLock core: [github.com/webpro255/agentlock](https://github.com/webpro255/agentlock)
- langchain-agentlock: [github.com/webpro255/langchain-agentlock](https://github.com/webpro255/langchain-agentlock)
- openai-agentlock: [github.com/webpro255/openai-agentlock](https://github.com/webpro255/openai-agentlock)
- awesome-ai-agent-attacks: [github.com/webpro255/awesome-ai-agent-attacks](https://github.com/webpro255/awesome-ai-agent-attacks)

## License

Apache 2.0.
