5.7 KiB
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.pyline 113:exec(code, ...)— allowed tool agents to run arbitrary code blocks supplied in YAML configs.reactive/stream_router.pyline 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):
tools:
- code: "import os; os.system('echo pwned')"
After (SECURE):
tools:
- operation: uppercase
Only operations registered in SimpleToolAgent._SAFE_OPERATIONS are
allowed. Custom operations are registered via:
from cleveragents.reactive.stream_router import SimpleToolAgent
SimpleToolAgent.register_operation(
"my_op",
lambda content, meta, ctx: content.upper(),
)
ReactiveStreamRouter: Named Transforms
Before (INSECURE):
operators:
- type: transform
params:
fn: "lambda x: __import__('os').system('rm -rf /')"
After (SECURE):
operators:
- type: transform
params:
fn: uppercase
Only transforms registered in ReactiveStreamRouter._TRANSFORM_REGISTRY
are allowed. Custom transforms are registered via:
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
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:
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
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
-
No backwards compatibility for unsafe features. The
codeblock and expression-eval features were explicitly removed with no migration path. Users must switch to named operations/transforms. -
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. -
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 |