Back to skill

Security audit

OpenClaw Universal Memory

Security checks for vulnerabilities and agentic risk

Overview

The skill has a reasonable database-memory purpose, but its launcher can run unbundled Python code from outside the reviewed skill while forwarding database credentials and the user environment.

Review before installing. Only run this in a trusted project where you have verified the actual openclaw_memory package being executed, use a dedicated least-privilege database credential, and avoid exposing unrelated secrets in the environment when invoking the launcher.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

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. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Env Variable Harvesting

High
Category
Data Exfiltration
Content
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())
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill exposes shell and environment-variable based capabilities through documented commands, but it does not declare any explicit tool scope such as permissions or allowed-tools. In an agent setting, this can lead to overbroad execution authority, allowing the skill to access local environment secrets like DATABASE_DSN or invoke arbitrary shell operations beyond the minimum needed for database workflows.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
base.extend(["--config-path", args.config_path.strip()])
        if args.force:
            base.append("--force")
        result = subprocess.run(base, cwd=repo_root, capture_output=True, text=True, env=subprocess_env)
        if result.stdout:
            print(result.stdout.strip())
        if result.stderr:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
base.extend(["--config-path", args.config_path.strip()])
        if args.force:
            base.append("--force")
        result = subprocess.run(base, cwd=repo_root, capture_output=True, text=True, env=subprocess_env)
        if result.stdout:
            print(result.stdout.strip())
        if result.stderr:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.