Back to skill

Security audit

Sync Discord Identity

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent, but it writes sensitive and free-form Discord profile data into a persistent agent identity file without enough scoping or sanitization.

Review before installing. Use only with a dedicated workspace and Discord bot token you control, inspect the IDENTITY.md diff before reuse, avoid storing email or bio there, and do not pass --config or --identity paths outside the intended workspace. The skill is not clearly malicious, but it should be tightened before broad use.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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 (2)

T02 · Agent Memory Poisoning

Error
Location
scripts/sync_discord_identity.py:91
Finding
Persistent Agent Identity Poisoning Through Unescaped Discord Profile Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sync_discord_identity.py`, lines 91–100 and 209 **Vulnerability Type**: T02: Agent Memory Poisoning **Risk Level**: High ### Vulnerable Code ```python def ensure_discord_block_lines(data: Dict[str, Any]) -> List[str]: lines: List[str] = [] for key in ("username", "locale", "email", "bio"): value = data.get(key) if value is None: continue if isinstance(value, str) and not value.strip(): continue lines.append(f" - {key}: {value}") return ["- **Discord:**", *lines] if lines else [] ``` The resulting lines are written into the persistent identity file: ```python lines = upsert_discord_block(lines, ensure_discord_block_lines(profile)) ``` ### Technical Analysis The Discord API response is external, potentially attacker-controlled input. Fields such as `username`, `email`, and particularly `bio` are inserted directly into `IDENTITY.md` without: - Removing carriage returns or newline characters - Escaping Markdown syntax - Restricting values to a single line - Enforcing field-specific formats - Applying length limits - Requiring confirmation before persistent storage A multiline profile value can escape the intended nested bullet and introduce new Markdown sections, identity attributes, or instruction-like content. Because `IDENTITY.md` is persistent agent identity or state content, injected text may be loaded into future agent sessions and interpreted as trusted context. The vulnerability does not require the Skill itself to contain malicious instructions. It creates a data flow from an externally controlled Discord profile into persistent agent state without a sufficient trust-boundary check. ### Attack Path 1. An attacker gains the ability to edit the selected Discord bot profile, or compromises an account with that ability. 2. The attacker places multiline Markdown or instruction-like content in a synchronized field, most pl ...[truncated 1235 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every Discord profile field as untrusted external data. 2. Reject values containing `\r`, `\n`, null bytes, or other control characters before constructing Markdown. 3. Apply strict, field-specific validation: - Validate `locale` against a conservative locale pattern or allowlist. - Validate `email` with a bounded single-line format. - Restrict `username` to a safe length and single line. - Exclude `bio` by default because it is free-form content. 4. If biography synchronization is required, make it an explicit opt-in option and require a user-reviewed diff before writing. 5. Enforce conservative maximum lengths for every stored field. 6. Escape Markdown metacharacters or store synchronized metadata in a structured, inert format that is not interpreted as agent instructions. 7. Build the Discord block from validated scalar values only; do not interpolate arbitrary objects or multiline strings. 8. Consider recording the data under a clearly delimited “external metadata” section that the agent is instructed not to interpret as operational instructions. 9. Write changes atomically only after all external fields pass validation. Example defensive validation: ```python def safe_single_line(value: Any, max_length: int) -> str: if not isinstance(value, str): raise ValueError("Expected a string value") if any(ch in value for ch in ("\r", "\n", "\x00")): raise ValueError("Multiline or control-character content is not allowed") value = value.strip() if len(value) > max_length: raise ValueError("Profile field exceeds the permitted length") return value ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/sync_discord_identity.py:166
Finding
Workspace Boundary Bypass Through Arbitrary and Symlinked File Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sync_discord_identity.py`, lines 166–168 and 199–219 **Vulnerability Type**: T09: Insecure Skill Coding Practices **Risk Level**: Medium ### Vulnerable Code The command-line options may resolve to arbitrary paths without a workspace containment check: ```python workspace = Path(args.workspace).expanduser().resolve() config_path = Path(args.config).expanduser().resolve() if args.config else workspace / "openclaw.json" identity_path = Path(args.identity).expanduser().resolve() if args.identity else workspace / "IDENTITY.md" ``` The selected paths are subsequently used for backup creation and file writes: ```python avatar_url = build_static_avatar_url(bot_id, avatar_hash) avatar_dest = workspace / "avatars" / f"discord-{sanitize_filename(username)}.png" download_file(avatar_url, avatar_dest) lines = ensure_identity_lines(identity_path) backup_path: Optional[Path] = None if identity_path.exists(): backup_path = identity_path.with_suffix(identity_path.suffix + ".bak") shutil.copy2(identity_path, backup_path) lines = upsert_avatar(lines, avatar_url, args.force_avatar) lines = upsert_discord_block(lines, ensure_discord_block_lines(profile)) identity_path.parent.mkdir(parents=True, exist_ok=True) identity_path.write_text("\n".join(lines) + "\n", encoding="utf-8") ``` ### Technical Analysis The Skill documentation states that it operates on the current workspace only. However, the implementation allows `--config` and `--identity` to point anywhere accessible to the current process. Resolving an absolute path normalizes it but does not establish that it is a descendant of the selected workspace. Consequently, the script may: - Read an `openclaw.json` file outside the intended workspace - Use a Discord token belonging to another workspace - Overwrite an arbitrary writable file supplied through `--identity` - Create a `.bak` file adjacent to that external target - Follow filesystem links unle ...[truncated 2577 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require every configurable project file path to remain beneath the resolved workspace root. 2. Reject `--config` and `--identity` values outside the workspace rather than merely resolving them. 3. Validate the identity target, backup destination, avatar directory, and avatar destination separately. 4. Reject symbolic links for writable targets and sensitive parent directory components where practical. 5. Use race-resistant file operations and atomic replacement: - Create a temporary file inside the validated destination directory. - Set restrictive permissions. - Flush and synchronize it. - Atomically replace the destination. 6. Avoid overwriting arbitrary existing files. Confirm that the identity target is either absent or a regular file. 7. Ensure the backup is also a regular file beneath the workspace and avoid silently overwriting an existing backup. 8. If external config or identity paths are a legitimate requirement, document that capability explicitly and require a separate opt-in flag with user confirmation. Example containment check: ```python def require_within_workspace(path: Path, workspace: Path, label: str) -> Path: resolved_workspace = workspace.resolve(strict=True) resolved_path = path.resolve(strict=False) try: resolved_path.relative_to(resolved_workspace) except ValueError as exc: raise RuntimeError( f"{label} must be located inside the selected workspace" ) from exc return resolved_path ``` Apply this validation to all read and write destinations before loading the configuration, downloading the avatar, creating a backup, or modifying the identity file. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill performs file reads, file writes, and network access but does not declare any explicit tool scope or permissions boundary. In systems that rely on manifest-declared capabilities for review or sandboxing, this creates an authorization gap and increases the chance the skill can access more than reviewers or users expect.

Session Persistence

Medium
Category
Rogue Agent
Content
`workspace/avatars/discord-<discord-username-or-bot-id>.png`

7. **If `**Avatar:**` is missing, set it.**
   Default behavior: write the static Discord CDN URL into `**Avatar:**`.

8. **If `**Avatar:**` already exists and differs, do not silently overwrite it.**
   Ask the user whether they want to replace the existing avatar value.
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.

Ssd 3

Medium
Confidence
98% confidence
Finding
The skill explicitly instructs copying Discord profile fields including email into IDENTITY.md, a workspace file that may be committed, shared, indexed, or exposed to other tools. Persisting sensitive account data beyond its operational need increases privacy risk and can lead to unintended disclosure of personal or account-linked information.

Ssd 3

Medium
Confidence
97% confidence
Finding
The example output normalizes writing an email address into IDENTITY.md, which encourages downstream implementations and users to persist sensitive Discord account data in plain workspace content. Examples strongly shape behavior, so this materially increases the likelihood of privacy-impacting misuse.

Ssd 3

Medium
Confidence
98% confidence
Finding
The success criteria require propagating non-empty email fields into IDENTITY.md, making sensitive-data copying a required behavior rather than an optional edge case. This codifies privacy-invasive persistence into the skill's expected operation and raises the chance of routine exposure of user or bot account metadata.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The manifest describes syncing a Discord bot profile into IDENTITY.md and saving an avatar, but this code recursively scans the entire configuration structure for any enabled object whose path contains 'discord' and has a token. That generic credential-discovery behavior is broader than a narrowly scoped profile-sync utility and is not obviously required if the intended token source were explicit.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script copies profile fields including locale, email, and bio from the Discord API response into IDENTITY.md. Writing unnecessary account metadata into a local identity file can expose sensitive or personal information, especially if the workspace is shared, committed to version control, or later published.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This code uses a Discord bot token to fetch profile metadata from Discord and then persists fields such as username, locale, email, and bio into IDENTITY.md and downloads an avatar to disk. While the script name and argument help mention syncing Discord metadata, there is no explicit warning, confirmation, or inline disclosure that personal/profile data and file modifications will occur.

Static analysis

No suspicious patterns detected.