Back to skill

Security audit

Openclaw Migration

Security checks for vulnerabilities and agentic risk

Overview

This migration skill mostly matches its stated purpose, but it can copy sensitive tokens and persist command permissions in ways that are broader than the user-facing instructions imply.

Review this skill before installing or running it. Use dry-run first, avoid user-data mode unless Discord and Slack token copying is fixed or explicitly acceptable, inspect any command allowlist entries before importing them, and ensure the target Hermes .env is protected with owner-only permissions.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/openclaw_to_hermes.py:924
Finding

Secret migration controls are bypassed for Discord and Slack tokens

Content
View full analysis
None: config = config or self.load_openclaw_config() additions: Dict[str, str] = {} discord = config.get("channels", {}).get("discord", {}) if isinstance(discord, dict): token = discord.get("token") if isinstance(token, str) and token.strip(): additions["DISCORD_BOT_TOKEN"] = token.strip() allow_from = discord.get("allowFrom", []) if isinstance(allow_from, list): users = [str(u).strip() for u in allow_from if str(u).strip()] if users: additions["DISCORD_ALLOWED_USERS"] = ",".join(users) if additions: self.merge_env_values( additions, "discord-settings", self.source_root / "openclaw.json", ) else: self.record( "discord-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No Discord settings found", ) def migrate_slack_settings(self, config: Optional[Dict[str, Any]] = None) -> None: config = config or self.load_openclaw_config() additions: Dict[str, str] = {} slack = config.get("channels", {}).get("slack", {}) if isinstance(slack, dict): bot_token = slack.get("botToken") if isinstance(bot_token, str) and bot_token.strip(): additions["SLACK_BOT_TOKEN"] = bot_token.strip() app_token = slack.get("appToken") if isinstance(app_token, str) and app_token.strip(): additions["SLACK_APP_TOKEN"] = app_token.strip() allow_from = slack.get("allowFrom", []) if isinstance(allow_from ...[truncated 2161 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/openclaw_to_hermes.py:256
Finding

Migrated credentials may be written to a permissively readable .env file

Content
View full analysis
None: ensure_parent(path) lines = [f"{key}={value}" for key, value in data.items()] path.write_text( "\n".join(lines) + ("\n" if lines else ""), encoding="utf-8", ) ``` ### Technical Analysis The migration stores API keys and messaging tokens as plaintext environment variables. When `.env` does not already exist, `Path.write_text` creates it using permissions derived from the process umask. The function does not explicitly create the file with owner-only permissions or verify its final mode. Under a permissive umask, the file can be created with permissions such as `0644`, making it readable by other local users. Existing files with insecure permissions also remain insecure because the function does not correct their mode. This affects values such as Telegram, Discord, Slack, OpenRouter, OpenAI, Anthropic, and TTS provider credentials when the corresponding migration paths execute. ### Attack Path 1. A user executes migration on a multi-user system or under a process with a permissive umask. 2. The target `~/.hermes/.env` does not exist, or already has overly broad permissions. 3. The migration writes plaintext credentials using `Path.write_text`. 4. The resulting file is readable by users or processes outside the intended account boundary. 5. A local attacker reads `.env` and reuses the exposed tokens or API keys. ### Impact Assessment A local user with filesystem read access could obtain all credentials stored in the Hermes `.env` file. Compromise may enable unauthorized API consumption, financial charges, bot impersonation, access to messaging integrations, or use of provider accounts within the permissions granted to each exposed credenti ...[truncated 147 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/openclaw_to_hermes.py:1360
Finding

Directory migration follows untrusted symlinks without path-containment checks

Content
View full analysis
None: if not source_root or not source_root.exists(): self.record(kind, None, destination_root, "skipped", "Source directory not found") return ignore_dir_names = ignore_dir_names or set() files = [ p for p in source_root.rglob("*") if p.is_file() and not any( part in ignore_dir_names for part in p.relative_to(source_root).parts[:-1] ) ] if not files: self.record(kind, source_root, destination_root, "skipped", "No files found") return copied = 0 skipped = 0 conflicts = 0 for source in files: rel = source.relative_to(source_root) destination = destination_root / rel if destination.exists(): if sha256_file(source) == sha256_file(destination): skipped += 1 continue if not self.overwrite: conflicts += 1 self.record( kind, source, destination, "conflict", "Destination file already exists", ) continue if self.execute: self.maybe_backup(destination) ensure_parent(destination) shutil.copy2(source, destination) copied += 1 ``` Related skill-copy paths use: ```python if final_destination == destination and destination.exists(): shutil.rmtree(destina ...[truncated 2425 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/openclaw_to_hermes.py:737
Finding

Unvalidated command approval patterns can weaken Hermes execution controls

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (19)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 62)May include surrounding context.

md
2. If that path fails, inspect the installed skill directory and resolve the script relative to the installed `SKILL.md`.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 100)May include surrounding context.

python
},
    "provider-keys": {
        "label": "Provider API keys",
        "description": "Import model provider API keys into Hermes .env (requires --migrate-secrets).",
    },
    "model-config": {
        "label": "Default model",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 817)May include surrounding context.

python
},
    "provider-keys": {
        "label": "Provider API keys",
        "description": "Import model provider API keys into Hermes .env (requires --migrate-secrets).",
    },
    "model-config": {
        "label": "Default model",

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The script merges sensitive values from OpenClaw configuration into Hermes .env, including tokens and API keys, and stores them in plaintext. In this skill context, credential movement is expected, but it is still dangerous because it broadens secret exposure, may overwrite existing values, and may place credentials into a less protected or more broadly used environment file.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 801)May include surrounding context.

python
return {}

    def merge_env_values(self, additions: Dict[str, str], kind: str, source: Path) -> None:
        destination = self.target_root / ".env"
        env_data = parse_env_file(destination)
        added: Dict[str, str] = {}
        conflicts: List[str] = []

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 863)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 874)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 887)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 896)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 919)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 977)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 998)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 1007)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 1077)May include surrounding context.

python
try:
                allow_data = json.loads(allowlist_path.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                self.record("messaging-settings", allowlist_path, self.target_root / ".env", "error", "Invalid JSON in Telegram allowlist file")
            else:
                allow_from = allow_data.get("allowFrom", [])
                if isinstance(allow_from, list):

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

Discord settings migration imports bot tokens into Hermes .env without additional per-secret confirmation or secret-store protections. In context, moving service bot credentials can enable account takeover or unauthorized messaging if the target environment is less trusted, backed up broadly, or accessible to other tools and users.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 941)May include surrounding context.

python
if additions:
            self.merge_env_values(additions, "discord-settings", self.source_root / "openclaw.json")
        else:
            self.record("discord-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No Discord settings found")

    def migrate_slack_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
        config = config or self.load_openclaw_config()

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

Slack settings migration can copy bot and app tokens into the target .env, increasing credential exposure and potentially enabling unauthorized API access if the file is leaked or shared. The migration context makes this understandable but still sensitive because these tokens commonly grant broad workspace capabilities.

Content

Scanner excerpt · scripts/openclaw_to_hermes.py (reported line 962)May include surrounding context.

python
if additions:
            self.merge_env_values(additions, "slack-settings", self.source_root / "openclaw.json")
        else:
            self.record("slack-settings", self.source_root / "openclaw.json", self.target_root / ".env", "skipped", "No Slack settings found")

    def migrate_whatsapp_settings(self, config: Optional[Dict[str, Any]] = None) -> None:
        config = config or self.load_openclaw_config()

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill instructs the agent to read from and write to sensitive filesystem locations such as ~/.openclaw and ~/.hermes, but it declares no explicit tool scope or permission boundaries. That mismatch can cause an agent runtime to grant broader file capabilities than users expect, increasing the risk of unintended modification or import of sensitive local data during migration.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 25)May include surrounding context.

hermes claw migrate # Full interactive migration hermes claw migrate --dry-run # Preview what would be migrated hermes claw migrate --preset user-data # Migrate without secrets hermes claw migrate --overwrite # Overwrite existing conflicts hermes claw migrate --source /custom/path/.openclaw # Custom source

text

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill metadata and option descriptions materially understate behavior: the script can import multiple channel tokens, provider API keys, config values, and archive additional raw files beyond the high-level description. In a migration skill, incomplete disclosure is security-relevant because users may authorize execution without realizing sensitive credentials and configuration will be copied into Hermes-managed locations.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The top-level docstring describes archiving selected unmapped docs for manual review, implying archival is limited to unmapped source documents. However, the code also writes overflowed memory entries to separate overflow files when character limits are exceeded, which is a different archival/output behavior not reflected by that documentation. This is a real intent/documentation mismatch, though low impact.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.