-
Notifications
You must be signed in to change notification settings - Fork 125
Description
Summary
build_deck.py loads and executes arbitrary Python from content-extra.py files via importlib.util.spec_from_file_location() without any validation. A malicious or careless content-extra.py can import dangerous stdlib modules (e.g., subprocess, os, shutil) or call dangerous builtins (eval, exec, __import__, compile, breakpoint) to gain full code execution in the build environment.
CWE: CWE-94 – Improper Control of Generation of Code ('Code Injection')
Severity: CRITICAL
Acceptance Criteria
-
content-extra.pyscripts are validated against a known-safe allowlist beforeimportlibexecution - Blocked stdlib modules (
subprocess,os,shutil,ctypes, etc.) raise a clear error with the module name - Dangerous builtins (
eval,exec,__import__,compile,breakpoint) are detected and rejected - Indirect bypass builtins (
getattr,setattr,delattr,globals,locals,vars) are detected and rejected - Safe imports (
pptx,pathlib,copy,math,json, etc.) continue to work -
--allow-scriptsCLI flag opts in to skipping validation for trusted content - Runtime namespace restriction strips dangerous builtins from executed module
- Security documentation added to SKILL.md
- CLI integration tests verify
--allow-scriptspropagates throughmain() - Unit tests cover blocked imports, dangerous builtins, indirect bypass builtins, namespace restriction, valid scripts, and edge cases
Implementation Details
AST-based validation (_validate_content_extra())
Parses content-extra.py with ast.parse() before execution and walks the AST to check:
- Import statements (
import X,from X import Y): top-level module must be inpptx.*or a safe subset of stdlib. 30 blocked stdlib modules in_BLOCKED_STDLIB_MODULES. Third-party packages are rejected. - Dangerous builtins (
_DANGEROUS_BUILTINS): calls toeval,exec,__import__,compile,breakpointare rejected. - Indirect bypass builtins (
_INDIRECT_BYPASS_BUILTINS): calls togetattr,setattr,delattr,globals,locals,varsare rejected to preventgetattr(builtins, 'exec')style bypasses. - Syntax errors: scripts that fail to parse are rejected before execution.
Runtime namespace restriction
Even after AST validation passes, the executed module's __builtins__ dict is restricted to safe builtins only. Dangerous builtins (eval, exec, compile, breakpoint) and indirect bypass builtins (getattr, setattr, delattr, globals, locals, vars) are stripped from the namespace before spec.loader.exec_module() runs. __import__ is kept because the import machinery requires it; the AST checker blocks direct __import__() calls.
--allow-scripts opt-in flag
build_slide() accepts a keyword-only allow_scripts: bool = False parameter. When True, both AST validation and runtime namespace restriction are skipped — intended for trusted content scenarios. The CLI exposes this as --allow-scripts, threaded through all three build_slide() call sites in main().
Security documentation (SKILL.md)
A "Security Validation" subsection was added under the "Complex Drawings (content-extra.py)" section documenting:
- Allowed and blocked imports
- Blocked builtins (dangerous and indirect bypass)
- Runtime namespace restriction behavior
--allow-scriptsflag usage with CLI example- Error behavior (
ContentExtraError)
Audit findings
No other skills in the repository use importlib for dynamic code execution. export_slides.py uses subprocess.run for controlled tool invocation (not user-controlled dynamic code).
Test coverage
119 tests pass (was 90 before this work). New tests cover:
- Blocked stdlib modules (subprocess, os, shutil, socket, ctypes)
- From-import syntax (
from os import path) - Third-party imports (e.g.,
requests) - Dangerous builtins (eval, exec, import, compile, breakpoint)
- Indirect bypass builtins (delattr, getattr, globals, locals, setattr, vars)
- Syntax errors in content-extra scripts
- Valid pptx and safe stdlib imports pass through
build_slide()integration: valid content-extra executes, invalid is rejectedallow_scripts=Truebypasses validationallow_scripts=Falsestill validates- Runtime namespace blocks
evalvia__builtins__dict - Runtime namespace allows safe builtins (
len,range,str) - CLI integration:
--allow-scriptsflag propagates throughmain()tobuild_slide() - CLI integration: without
--allow-scripts, blocked imports fail throughmain()
Files changed
| File | Changes |
|---|---|
scripts/build_deck.py |
Added import ast, import builtins, _BLOCKED_STDLIB_MODULES, _DANGEROUS_BUILTINS, _INDIRECT_BYPASS_BUILTINS, ContentExtraError, _check_module_allowed(), _validate_content_extra(), allow_scripts parameter, runtime namespace restriction, --allow-scripts CLI argument |
tests/test_build_deck.py |
Added TestContentExtraValidation (18 tests) and TestAllowScriptsCLI (2 integration tests) |
SKILL.md |
Added "Security Validation" subsection with allowlist/blocklist docs, namespace restriction, --allow-scripts usage |