# 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 |