Files
cleveragents-core/docs/reference/security_eval.md
T
2026-02-21 16:32:03 +00:00

180 lines
5.7 KiB
Markdown

# Security: eval/exec Removal (SEC1)
This document describes the security hardening applied to CleverAgents
configuration parsing to eliminate all code-injection attack surfaces.
## Background
Prior to SEC1, certain code paths in the reactive stream router allowed
arbitrary Python execution through `eval()` and `exec()` calls:
- `reactive/stream_router.py` line 113: `exec(code, ...)` — allowed tool
agents to run arbitrary code blocks supplied in YAML configs.
- `reactive/stream_router.py` line 340: `eval(fn_str)` — allowed transform
operators to evaluate arbitrary expressions.
Both paths have been replaced with **named registries** that only permit
pre-registered, audited operations.
## Replacement Patterns
### SimpleToolAgent: Named Operations
**Before (INSECURE)**:
```yaml
tools:
- code: "import os; os.system('echo pwned')"
```
**After (SECURE)**:
```yaml
tools:
- operation: uppercase
```
Only operations registered in `SimpleToolAgent._SAFE_OPERATIONS` are
allowed. Custom operations are registered via:
```python
from cleveragents.reactive.stream_router import SimpleToolAgent
SimpleToolAgent.register_operation(
"my_op",
lambda content, meta, ctx: content.upper(),
)
```
### ReactiveStreamRouter: Named Transforms
**Before (INSECURE)**:
```yaml
operators:
- type: transform
params:
fn: "lambda x: __import__('os').system('rm -rf /')"
```
**After (SECURE)**:
```yaml
operators:
- type: transform
params:
fn: uppercase
```
Only transforms registered in `ReactiveStreamRouter._TRANSFORM_REGISTRY`
are allowed. Custom transforms are registered via:
```python
from cleveragents.reactive.stream_router import ReactiveStreamRouter
ReactiveStreamRouter.register_transform(
"my_transform",
lambda x: x.upper(),
)
```
## Config Security Scanner
The `cleveragents.config.security_scanner` module provides automated
scanning of configuration files for dangerous patterns.
### Scanned Patterns
| Token | Severity | Rationale |
|---|---|---|
| `eval(` | CRITICAL | Executes arbitrary Python |
| `exec(` | CRITICAL | Executes arbitrary Python |
| `__import__` | CRITICAL | Dynamic module loading |
| `os.system` | CRITICAL | Shell command execution |
| `subprocess.call` | CRITICAL | Shell command execution |
| `subprocess.run` | CRITICAL | Shell command execution |
| `subprocess.Popen` | CRITICAL | Process spawning |
| `subprocess.check_output` | CRITICAL | Shell command execution |
| `subprocess.check_call` | CRITICAL | Shell command execution |
| `compile(` | HIGH | Code compilation |
| `os.popen` | HIGH | Shell command execution |
| `importlib.import_module` | HIGH | Dynamic module loading |
| `pickle.loads` | HIGH | Arbitrary code execution via deserialization |
| `{{` | MEDIUM | Template directive (potential injection) |
| `{%` | MEDIUM | Template directive (potential injection) |
### Programmatic Usage
```python
from cleveragents.config.security_scanner import scan_files, Severity
violations = scan_files(["config.yaml", "settings.toml"])
for v in violations:
print(f"{v.file_path}:{v.line_number} [{v.severity.name}] {v.message}")
```
### Validation Gate
Use `validate_config_safety()` to hard-fail if a config file contains
dangerous patterns:
```python
from cleveragents.config.security_scanner import validate_config_safety
content = open("config.yaml").read()
validate_config_safety(content, file_path="config.yaml")
# Raises ConfigurationError if any violations are detected.
```
### CLI
```bash
python -m cleveragents.config.security_scanner path/to/config.yaml
```
## Known Limitations
The scanner performs **literal substring matching** against a fixed set of
tokens. This is intentional — the goal is fast, low-false-positive
detection of the most common code-injection patterns in configuration
files, not a full static-analysis engine.
The following bypass vectors are **not** detected by the current scanner:
| Bypass technique | Example | Why not detected |
|---|---|---|
| Whitespace before paren | `eval (` | Tokens match literal `eval(` only |
| Indirect calls | `getattr(os, 'system')('cmd')` | No semantic analysis |
| `pickle.load` (singular) | `pickle.load(f)` | Only `pickle.loads` is registered |
| `shutil.rmtree` | `shutil.rmtree('/')` | Destructive FS ops not in scope |
| `pty.spawn` | `pty.spawn('/bin/sh')` | Terminal spawning not in scope |
| `ctypes` FFI | `ctypes.CDLL('libc.so')` | Native code loading not in scope |
| String concatenation | `"ev" + "al("` | No expression evaluation |
These limitations are acceptable for a defence-in-depth config scanner.
Production security relies on the removal of `eval()`/`exec()` from
production code paths (SEC1), not solely on this scanner. Future
iterations may extend the pattern list.
## Security Rationale
1. **No backwards compatibility for unsafe features.** The `code` block
and expression-eval features were explicitly removed with no migration
path. Users must switch to named operations/transforms.
2. **Defense in depth.** Even after removing `eval()`/`exec()`, the
config scanner provides an additional layer to catch any future
regressions or accidental re-introduction of dangerous patterns.
3. **Fail-fast.** `validate_config_safety()` raises immediately on first
detection, preventing any partial loading of tainted configuration.
## Audit Summary
| File | Findings | Action |
|---|---|---|
| `reactive/stream_router.py:113` | `exec()` in SimpleToolAgent | Fully removed; code blocks unconditionally rejected |
| `reactive/stream_router.py:340` | `eval()` in transform operator | Replaced with named transform registry |
| `config/config_parser.py` | `re.compile()` | Safe — not code execution |
| All other production code | No `eval`/`exec`/`compile` usage | No action needed |