180 lines
5.7 KiB
Markdown
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 |
|