T07 · Tool Hijacking and Spoofing
- Location
scripts/run_memory.py:11- Finding
Out-of-Scope Python Module Execution Through Attacker-Influenced PYTHONPATH
- Content
View full analysis
Path: here = Path(__file__).resolve() return here.parents[3] ``` ```python def main() -> None: args = _parse_args() repo_root = _find_repo_root() subprocess_env = os.environ.copy() if args.dsn.strip(): subprocess_env[args.dsn_env] = args.dsn.strip() repo_src = str((repo_root / "src").resolve()) existing_pythonpath = subprocess_env.get("PYTHONPATH", "").strip() subprocess_env["PYTHONPATH"] = f"{repo_src}:{existing_pythonpath}" if existing_pythonpath else repo_src base = [ sys.executable, "-m", "openclaw_memory.cli", args.action, "--dsn-env", args.dsn_env, "--dsn-file", args.dsn_file, "--dsn-key", args.dsn_key, ] ``` ```python result = subprocess.run(base, cwd=repo_root, capture_output=True, text=True, env=subprocess_env) ``` The subprocess invocation also occurs in the `configure-dsn` branch at line 84. ### Technical Analysis The launcher calculates its repository root using `here.parents[3]`. Given the audited layout: ```text /tmp//artifact/scripts/run_memory.py ``` the calculated path is `/tmp`, rather than the `artifact` project directory. The launcher consequently prepends `/tmp/src` to `PYTHONPATH` and executes: ```text python -m openclaw_memory.cli ``` The audited project does not contain the referenced `openclaw_memory` package. Python therefore attempts to resolve the module from the calculated external source directory or other environment paths. If an attacker can create or control `/tmp/src/openclaw_memory`, their module can be loaded instead of the intended implementation. The child process receives a copy of the caller's environment. When `--dsn` is supplied, th ...[truncated 2408 chars]- Remediation
View remediation
Path: return Path(__file__).resolve().parent.parent ``` 2. Validate that the expected package exists beneath the trusted root before executing it: ```python repo_root = _find_repo_root() module_path = repo_root / "src" / "openclaw_memory" / "cli.py" if not module_path.is_file(): raise RuntimeError(f"Expected CLI module is missing: {module_path}") ``` 3. Ensure the resolved package path remains within the trusted project root: ```python repo_root = repo_root.resolve() repo_src = (repo_root / "src").resolve() repo_src.relative_to(repo_root) ``` 4. Avoid prepending a broadly writable ancestor directory such as `/tmp/src` to `PYTHONPATH`. Prefer installing the package into a controlled virtual environment and invoking its verified entry point. 5. Package the complete `openclaw_memory` implementation with the project, including an appropriate packaging manifest, so the executable backend is included in security review. 6. Consider importing the bundled module directly after validating its location rather than delegating resolution to an externally influenced Python search path. 7. Minimize the subprocess environment by passing only required variables. Do not forward unrelated secrets from `os.environ` to the child process. 8. Add a startup assertion and automated test verifying that `repo_root` equals the expected project directory and that module resolution cannot escape it. 9. Fail closed if the expected local module is missing, has an unexpected resolved path, or cannot be integrity-verified. ]]>
