T07 · Tool Hijacking and Spoofing
Error
- Location
- scripts/run_memory.py:11
- Finding
- Out-of-Scope Python Module Execution Through Attacker-Influenced PYTHONPATH<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run_memory.py:11-13, 59-73, 84, 163` **Vulnerability Type**: Local Python module hijacking **Risk Level**: High ### Vulnerable Code ```python def _find_repo_root() -> 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/<project-directory>/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
- <![CDATA[ ## Remediation Suggestions 1. Resolve the project root to the actual Skill directory instead of a fixed distant ancestor: ```python def _find_repo_root() -> 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. ]]>
