forked from HAL9000/cleveragents-core
48cff5cfe0
Renames `plan lifecycle-list` to `plan list` and `plan lifecycle-apply` to `plan apply` to align with the specification's canonical command names. Removes legacy V2 plan commands that occupied those names. - Renamed CLI command registrations from lifecycle-list/lifecycle-apply to list/apply - Removed legacy V2 apply and list commands (~200 lines) - Updated apply shortcut in main.py to delegate to v3 lifecycle - Added defensive null check for plan existence in apply command - Updated 63+ test, doc, and benchmark files for consistency Closes #881 Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
107 lines
3.2 KiB
Markdown
107 lines
3.2 KiB
Markdown
# Security: Read-Only Action Enforcement
|
|
|
|
This document describes the multi-layer read-only enforcement in
|
|
CleverAgents, ensuring that plans marked `read_only=True` never
|
|
produce side-effects.
|
|
|
|
## Background
|
|
|
|
A **read-only plan** is created when the originating Action has
|
|
`read_only=True`. The flag propagates through the execution layers:
|
|
|
|
```
|
|
Plan.read_only -> CLI fail-fast guard (plan execute / plan apply)
|
|
-> ExecuteStubActor.execute(read_only=...) -> ChangeSetCapture
|
|
-> ToolRuntime._enforce_capabilities (via ToolExecutionContext)
|
|
-> SkillContext.enforce_write_guard
|
|
```
|
|
|
|
## Enforcement Layers
|
|
|
|
### 1. ToolRuntime._enforce_capabilities
|
|
|
|
Location: `src/cleveragents/tool/lifecycle.py`
|
|
|
|
When a tool is about to execute, `_enforce_capabilities()` checks:
|
|
|
|
```python
|
|
if ctx.plan_read_only and cap.writes:
|
|
raise ToolAccessDeniedError(
|
|
f"Tool '{tool.name}' has writes=True but plan "
|
|
f"'{ctx.plan_id}' is read-only"
|
|
)
|
|
```
|
|
|
|
Any tool with `ToolCapability.writes=True` is blocked regardless of
|
|
`cap.read_only`. The error always includes the tool name for
|
|
auditability.
|
|
|
|
### 2. SkillContext.enforce_write_guard
|
|
|
|
Location: `src/cleveragents/skills/context.py`
|
|
|
|
Skills call `enforce_write_guard(tool_name)` before executing write
|
|
operations. When `read_only=True`:
|
|
|
|
```python
|
|
raise SkillExecutionError(
|
|
SkillError(
|
|
error_type=SkillErrorType.PERMISSION_DENIED,
|
|
message=f"Write operation denied: tool '{tool_name}' cannot "
|
|
f"write in read-only context (plan={self.plan_id})",
|
|
...
|
|
)
|
|
)
|
|
```
|
|
|
|
### 3. ChangeSetCapture (wired via ExecuteStubActor)
|
|
|
|
Location: `src/cleveragents/tool/builtins/changeset.py`
|
|
|
|
`ChangeSetCapture` accepts a `read_only` keyword argument. When
|
|
`read_only=True`, `wrap_tool()` raises `ReadOnlyViolationError` for
|
|
any tool spec with `writes=True`:
|
|
|
|
```python
|
|
raise ReadOnlyViolationError(
|
|
f"Cannot wrap write-capable tool '{tool_spec.name}' "
|
|
f"in read-only ChangeSetCapture (plan={self._plan_id})"
|
|
)
|
|
```
|
|
|
|
Read-only tool specs pass through unchanged.
|
|
|
|
`ExecuteStubActor.execute()` propagates the plan's `read_only` flag
|
|
into `ChangeSetCapture`, and `PlanExecutor._run_execute_with_stub()`
|
|
reads `plan.read_only` from the plan model.
|
|
|
|
### 4. CLI Fail-Fast Guards
|
|
|
|
Location: `src/cleveragents/cli/commands/plan.py`
|
|
|
|
Both `plan execute` and `plan apply` check `plan.read_only`
|
|
before calling the lifecycle service. If the plan is read-only, the
|
|
CLI prints an error and aborts without making any state transitions.
|
|
|
|
## Error Types
|
|
|
|
| Layer | Error Class | Module |
|
|
|----------------|---------------------------|-----------------------------|
|
|
| ToolRuntime | `ToolAccessDeniedError` | `tool.lifecycle` |
|
|
| SkillContext | `SkillExecutionError` | `skills.context` |
|
|
| ChangeSet | `ReadOnlyViolationError` | `tool.builtins.changeset` |
|
|
| CLI | `typer.Abort` | `cli.commands.plan` |
|
|
|
|
## Testing
|
|
|
|
BDD scenarios covering all layers are in:
|
|
|
|
- `features/security_readonly.feature` (18 scenarios)
|
|
- `features/steps/security_readonly_steps.py`
|
|
|
|
Run with:
|
|
|
|
```bash
|
|
nox -s unit_tests
|
|
```
|