Back to skill

Security audit

mmxagent-guardian

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed local file-protection tool, but it can copy, move, restore, and persist sensitive files outside the project without strong boundaries.

Install only if you are comfortable with an agent-maintained local backup store that can retain sensitive files. Avoid using it on credential directories, .env files, cloud profiles, SSH/GPG keys, or system paths unless you explicitly intend that, and keep operations limited to trusted project files.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/minivcs/minivcs.py:493
Finding
Unrestricted File Operations Outside the Project Root<![CDATA[ ## Vulnerability Details **File Location**: `scripts/minivcs/minivcs.py:493-496`, `scripts/minivcs/minivcs.py:516-517`, `scripts/minivcs/minivcs.py:641-642`, `scripts/minivcs/minivcs.py:704-705`, `scripts/minivcs/minivcs.py:734-739` **Vulnerability Type**: Missing project-boundary validation for file read, move, and overwrite operations **Risk Level**: Medium ### Vulnerable Code ```python def _get_relative_path(self, absolute_path: str) -> str: if absolute_path.startswith(self.project_root + os.sep): return absolute_path[len(self.project_root) + 1 :] return absolute_path ``` The modification and deletion operations accept the resulting unrestricted path: ```python def record_modify(self, file_path: str) -> Dict[str, Any]: abs_path = os.path.abspath(file_path) if not os.path.exists(abs_path): return {"success": False, "error": f"File not found: {file_path}"} ``` ```python def record_delete(self, file_path: str) -> Dict[str, Any]: abs_path = os.path.abspath(file_path) if not os.path.exists(abs_path): return {"success": False, "error": f"File not found: {file_path}"} ``` Restore operations preserve absolute paths and can write directly to them: ```python file_path = record.get("filePath", "") target_path = file_path if os.path.isabs(file_path) else os.path.join(self.project_root, file_path) success = self.file_manager.restore_from_trash(trash_file, target_path) ``` ```python file_path = record.get("filePath", "") target_path = file_path if os.path.isabs(file_path) else os.path.join(self.project_root, file_path) os.makedirs(os.path.dirname(target_path), exist_ok=True) with open(target_path, "w", encoding="utf-8") as f: f.write(content) ``` ### Technical Analysis `project_root` is used only to convert paths into relative record names. It is not enforced as an authorization boundary. If a target is outside the project, `_get_relative_path()` deliberately returns its absolute path. As a result, th ...[truncated 2432 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce project-root containment before every read, move, copy, restore, or overwrite: ```python project_root = os.path.realpath(self.project_root) target = os.path.realpath(file_path) try: contained = os.path.commonpath([project_root, target]) == project_root except ValueError: contained = False if not contained: return {"success": False, "error": "Path is outside the project root"} ``` 2. Apply this check independently in: - `record_modify()` - `_record_binary_backup()` - `record_delete()` - `restore_file()` - History and merge operations that derive paths from user input. 3. Resolve symbolic links and reject targets whose canonical paths leave the authorized root. 4. If protecting external files is a required feature, separate it from normal project operations: - Require an explicit `--allow-external-path` option. - Require a canonical-path allowlist. - Display a high-risk warning for credential and system directories. - Require fresh confirmation for each external target. 5. Do not run the Skill with administrative or root privileges unless a narrowly scoped operation explicitly requires them. 6. Validate paths recovered from `logs.json` before restore. Treat the log as untrusted state and reject absolute or escaping paths unless they were explicitly authorized. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/minivcs/minivcs.py:186
Finding
Plaintext Sensitive Backups Created Without Enforced Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/minivcs/minivcs.py:186-205`, `scripts/minivcs/minivcs.py:359-365`, `scripts/minivcs/minivcs.py:396-454` **Vulnerability Type**: Insecure permissions and plaintext retention of sensitive file contents **Risk Level**: Medium ### Vulnerable Code Storage directories are created using ambient process permissions: ```python self.trash_dir = os.path.join(vcs_root, "trash") self.diffs_dir = os.path.join(vcs_root, "diffs") self.bases_dir = os.path.join(vcs_root, "bases") self.snapshots_dir = os.path.join(vcs_root, "snapshots") self.backups_dir = os.path.join(vcs_root, "backups") os.makedirs(self.trash_dir, exist_ok=True) os.makedirs(self.diffs_dir, exist_ok=True) os.makedirs(self.bases_dir, exist_ok=True) os.makedirs(self.snapshots_dir, exist_ok=True) os.makedirs(self.backups_dir, exist_ok=True) ``` The operation log is also created without an explicit restrictive mode: ```python def _ensure_log_file(self): if not os.path.exists(self.log_file): os.makedirs(os.path.dirname(self.log_file), exist_ok=True) with open(self.log_file, "w", encoding="utf-8") as f: json.dump({"version": "1.0", "records": []}, f, ensure_ascii=False, indent=2) ``` Text diffs, snapshots, and baselines use ordinary `open()` calls governed by the ambient `umask`: ```python def save_diff(self, relative_path: str, diff_content: str) -> str: timestamp = int(time.time() * 1000) safe_path = _make_safe_path(relative_path) diff_path = os.path.join(self.diffs_dir, f"{timestamp}_{safe_path}.patch") with open(diff_path, "w", encoding="utf-8") as f: f.write(diff_content) return diff_path def save_snapshot(self, relative_path: str, content: str) -> str: timestamp = int(time.time() * 1000) safe_path = _make_safe_path(relative_path) snap_path = os.path.join(self.snapshots_dir, f"{timestamp}_{safe_path}.snap") with open(snap_path, "w", encoding="utf-8") as f: f.wri ...[truncated 3713 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the MiniVCS root and all child directories with owner-only permissions: ```python os.makedirs(path, mode=0o700, exist_ok=True) os.chmod(path, 0o700) ``` 2. Create logs, diffs, snapshots, baselines, trash copies, and backups with mode `0600`. Use `os.open()` with explicit flags and permissions where appropriate: ```python fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as f: f.write(content) ``` 3. After `shutil.move()` or `shutil.copy2()`, explicitly apply the destination policy: ```python os.chmod(destination, 0o600) ``` 4. At initialization, audit and repair permissions on existing MiniVCS directories and files. Refuse operation if ownership is unexpected or secure permissions cannot be established. 5. Consider encrypting backups that contain files from credential or system-configuration directories. Keep encryption keys separate from the backup directory. 6. Minimize sensitive metadata in `logs.json`. Avoid storing unnecessary absolute paths and consider redacting home-directory and credential-path details from ordinary history output. 7. Provide an option to exclude credential-bearing directories entirely. Require explicit opt-in before backing up files under `.ssh`, `.gnupg`, `.aws`, `.azure`, `.kube`, and similar locations. 8. Document that historical diffs and snapshots may retain secrets removed from active files, and provide a secure purge command that removes all associated artifacts. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (14)

Ae1

High
Category
analysis-evasion
Content
**Runtime requirement**: Python 3 is required to run `scripts/minivcs/minivcs.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Runtime requirement**: Python 3 is required to run `scripts/minivcs/minivcs.py`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Script location**: `scripts/minivcs/minivcs.py` in the same directory as this `SKILL.md`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

External Script Fetching

High
Category
Supply Chain
Content
If Python 3 is missing:

- Do **not** run dependency installation commands from this Skill
- Do **not** run remote install scripts such as `curl | bash`
- Do **not** write to shell config files such as `~/.zshrc` or `~/.bash_profile`
- Do **not** modify global environment variables on the user's behalf from this Skill
- Tell the user that Python 3 is required, and ask them to install it first or explicitly authorize a separate environment-setup flow
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Credential Access

High
Category
Privilege Escalation
Content
| File Type | Retention Days | Decision Rule |
|---------|---------|---------|
| Important files | **14 days** | System paths (`/etc/`, `/root/`, `/usr/local/etc/`, `/opt/`), user config directories (`~/.ssh/`, `~/.gnupg/`, `~/.config/`, `~/.openclaw/`, `~/.kube/`, `~/.docker/`, `~/.aws/`, `~/.azure/`, `~/.local/share/`), Windows system directories (`C:\Windows\`, `C:\ProgramData\`, `C:\Program Files\`), config files (`.yaml/.toml/.env`, etc.), entry files (`main.py/index.ts`, etc.) |
| Normal files | **7 days** | All other files |

When each record is created, the `expireAt` (expiration timestamp) and `expireAtDatetime` (human-readable time) fields are set automatically.
Confidence
93% confidence
Finding
The retention policy explicitly treats highly sensitive directories such as ~/.ssh, ~/.gnupg, ~/.aws, ~/.azure, ~/.kube, and ~/.docker as files to be copied into local backup/trash storage. This can duplicate credentials, tokens, and private keys into a secondary location under ~/.openclaw/minivcs/, expanding the attack surface and increasing the chance of credential theft if that store is accessed.

Credential Access

High
Category
Privilege Escalation
Content
".ini",
    ".cfg",
    ".conf",
    ".env",
    "main.py",
    "app.py",
    "server.py",
Confidence
90% confidence
Finding
The inclusion of '.env' in IMPORTANT_FILENAME_PATTERNS signals special retention of a file type that commonly contains secrets such as API keys, tokens, and database credentials. In this skill, that increases the chance that plaintext secrets are copied into persistent backup/snapshot storage under the user's home directory, expanding credential exposure if that store is compromised.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill clearly instructs file modification, deletion, restore, and history operations, but it does not declare any tool scope or allowed-tools restrictions. That mismatch weakens sandboxing and review controls, because an agent could be granted broader file read/write capability than users or policy expect.

Session Persistence

Medium
Category
Rogue Agent
Content
- Do **not** run dependency installation commands from this Skill
- Do **not** run remote install scripts such as `curl | bash`
- Do **not** write to shell config files such as `~/.zshrc` or `~/.bash_profile`
- Do **not** modify global environment variables on the user's behalf from this Skill
- Tell the user that Python 3 is required, and ask them to install it first or explicitly authorize a separate environment-setup flow
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The example trigger "Help me restore/roll back xxx" is broad and conversational, and could overlap with ordinary user requests outside a narrowly defined skill-invocation context. The document does not provide explicit trigger constraints, exclusions, or a bounded activation scope for when this skill should engage on such phrasing.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill explicitly treats credential and configuration directories such as ~/.ssh, ~/.gnupg, ~/.aws, ~/.docker, ~/.kube, and .env/config-style files as 'important' and therefore retains copies of them longer. That behavior expands the data collection scope beyond the stated openclaw file-protection use case and increases exposure of highly sensitive material in ~/.openclaw/minivcs if the host or that directory is accessed by another process or user.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
record_modify accepts any supplied path, converts it to an absolute path, and proceeds as long as the path exists and is not on a small skip list; similar behavior exists for delete/restore/cleanup flows. This means the skill can snapshot, move to trash, restore, and clean up files outside the intended project/openclaw scope, enabling unintended manipulation of arbitrary filesystem locations.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
restore_file restores deleted files or overwrites current files from stored snapshots/backups without any built-in confirmation, dry-run, or destination validation. In an agent context, that makes destructive rollback or file recreation easier to trigger programmatically, especially when combined with broad path handling and sensitive-file retention.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
delete_record_by_id permanently removes metadata and associated diff/trash/snapshot/backup artifacts immediately, with no confirmation or soft-delete stage. In an agent-driven workflow this can erase recovery material and audit history, making accidental or malicious cleanup of backups harder to detect or reverse.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The record_modify docstring states it should be called once after each edit and that later calls save the pre-edit snapshot needed for rollback. However, this restore error says rollback is only possible when record_modify was called both before and after the edit, which directly contradicts the documented and implemented single-post-edit workflow.

Static analysis

No suspicious patterns detected.