Back to skill

Security audit

Personality Switcher

Security checks for vulnerabilities and agentic risk

Overview

The skill's personality-switching purpose is mostly disclosed, but its scripts contain clear path-validation flaws that can load or delete files outside the intended personality folder.

Review this skill carefully before installing. Its core idea is understandable, but the current implementation should be fixed to strictly validate personality names, reject traversal and absolute paths, constrain all resolved paths to direct children of the personality directory, and protect heartbeat restoration from malformed state before it is trusted with persistent assistant identity files.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/delete_personality.py:43
Finding
Directory Traversal Enables Recursive Deletion Outside the Personality Store<![CDATA[ ## Vulnerability Details **File Location**: `scripts/delete_personality.py`, lines 43-48 and 72-73 **Vulnerability Type**: Unrestricted path traversal leading to recursive directory deletion **Risk Level**: High ### Vulnerable Code ```python # Verify personality exists if not personality_exists(personalities_dir, personality_name): return { "status": "error", "message": f"Personality '{personality_name}' not found.", "code": "personality_not_found" } # Delete personality folder try: personality_folder = personalities_dir / personality_name shutil.rmtree(personality_folder) except Exception as e: return { "status": "error", "message": "Failed to delete personality folder.", "error_detail": str(e), "code": "deletion_failed" } ``` The validation helper also constructs the path without enforcing containment: ```python def personality_exists(personalities_dir, name): """Check if personality folder exists and is valid.""" personality_folder = personalities_dir / name is_valid, _ = verify_personality_folder(personality_folder) return is_valid ``` ### Technical Analysis `delete_personality()` does not call `validate_personality_name()` and does not verify that the resolved target remains beneath the personalities directory. Python's `pathlib` accepts `..` components and absolute paths when joining paths. The only prerequisite imposed by `personality_exists()` is that the resulting directory contain readable `SOUL.md` and `IDENTITY.md` files. It does not establish that the directory is an actual personality directory. Consequently, a value such as `..` resolves from: ```text ~/.openclaw/workspace/personalities/.. ``` to: ```text ~/.openclaw/workspace ``` The workspace is expected to contain `SOUL.md` and `IDENTITY.md`, so it can satisfy the existence check. `shutil.rmtree()` may then recursively delete the entire workspace. Absolute paths or traversal p ...[truncated 1559 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply `validate_personality_name()` before performing any lookup or deletion: ```python is_valid, error = validate_personality_name(personality_name) if not is_valid: return { "status": "error", "message": f"Invalid personality name: {error}", "code": "invalid_name" } ``` 2. Enforce resolved-path containment: ```python root = personalities_dir.resolve() candidate = (personalities_dir / personality_name).resolve(strict=True) if not candidate.is_relative_to(root): raise ValueError("Personality path escapes the personality directory") ``` 3. Require the target to be a direct child: ```python if candidate.parent != root: raise ValueError("Nested or external personality paths are not allowed") ``` 4. Reject absolute paths, path separators, `.` and `..` explicitly. 5. Do not recursively delete symlinked personality directories. Use `lstat()` or equivalent checks and reject symbolic links before destructive operations. 6. Perform the same centralized validation in every command that accepts or reads a personality name. 7. Add regression tests for `..`, `../target`, absolute paths, repeated separators, Unicode separator variants, and symlink-based paths. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/rename_personality.py:38
Finding
Unvalidated Rename Source Can Move External Directories into the Personality Store<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rename_personality.py`, lines 38-42 and 68-69 **Vulnerability Type**: Path traversal in file-system rename operation **Risk Level**: Medium ### Vulnerable Code ```python # Verify old personality exists if not personality_exists(personalities_dir, old_name): return { "status": "error", "message": f"Personality '{old_name}' not found.", "code": "personality_not_found" } # Validate new name is_valid, error_msg = validate_personality_name(new_name) if not is_valid: return { "status": "error", "message": f"Invalid new name: {error_msg}", "code": "invalid_name" } ``` ```python # Rename folder try: old_folder = personalities_dir / old_name new_folder = personalities_dir / new_name old_folder.rename(new_folder) except Exception as e: return { "status": "error", "message": "Failed to rename personality folder.", "error_detail": str(e), "code": "rename_failed" } ``` ### Technical Analysis The destination name is validated, but the source name is not. `old_name` can therefore contain traversal components or be an absolute path. The source is accepted whenever the selected directory contains `SOUL.md` and `IDENTITY.md`. The subsequent `Path.rename()` operation can move that external directory to a validated destination beneath the personalities directory. This is an incomplete validation pattern: validating only the destination does not protect the source-side file-system boundary. ### Attack Path 1. A user-accessible directory outside `personalities/` contains `SOUL.md` and `IDENTITY.md`. 2. An attacker invokes: ```bash python3 scripts/rename_personality.py ../external-directory imported-name ``` 3. `personality_exists()` follows the traversal path and accepts the external directory as a personality. 4. `new_name` passes normal validation. 5. `old_folder.rename(new_folder)` move ...[truncated 913 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate both `old_name` and `new_name` using the same strict naming function. 2. Resolve and constrain both paths before renaming: ```python root = personalities_dir.resolve() old_folder = (root / old_name).resolve(strict=True) new_folder = root / new_name if not old_folder.is_relative_to(root) or old_folder.parent != root: raise ValueError("Source path is outside the personality directory") ``` 3. Reject symbolic links and require the source to be a real directory directly beneath the personality root. 4. Use a centralized `resolve_personality_path()` helper for create, read, rename, switch, restore, and delete operations. 5. Recheck the source immediately before the rename to reduce time-of-check/time-of-use risk. 6. Add tests verifying that relative traversal, absolute paths, nested paths, and symlink sources are rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/switch_personality.py:27
Finding
Traversal-Based Personality Activation Can Persist External Instructions Through Heartbeat Restoration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/switch_personality.py`, lines 27-32; `scripts/restore_personality.py`, lines 25-32 **Vulnerability Type**: Unrestricted path traversal and persistent loading of external agent instruction files **Risk Level**: High ### Vulnerable Code From `scripts/switch_personality.py`: ```python workspace = get_workspace() personalities_dir = get_personalities_dir(workspace) target_folder = personalities_dir / personality_name # Check target exists if not personality_exists(personalities_dir, personality_name): return { "status": "error", "message": f"Personality '{personality_name}' not found.", "code": "personality_not_found" } ``` The unvalidated value is subsequently persisted: ```python try: write_state(workspace, personality_name, previous_personality=current_active) except Exception as e: # Critical failure - restore everything restore_backup(workspace, backup_location) return { "status": "error", "message": "Failed to update personality state.", "error_detail": str(e), "code": "state_write_failed", "previous_personality": current_active, "backup_restored": True } ``` From `scripts/restore_personality.py`: ```python active_personality = read_state(workspace) if not active_personality: active_personality = "default" # Get personality folder personality_folder = personalities_dir / active_personality # Verify folder exists and is valid is_valid, error_msg = verify_personality_folder(personality_folder) ``` The selected files are then copied into the workspace: ```python if not copy_personality_to_workspace(personality_folder, workspace): raise Exception("Failed to copy files") ``` ### Technical Analysis `switch_personality()` never applies `validate_personality_name()` to the supplied name. A relative traversal path or absolute path can therefore select a directory outside the personality ...[truncated 2699 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate personality names in `switch_personality()` before constructing a path: ```python is_valid, error = validate_personality_name(personality_name) if not is_valid: return { "status": "error", "message": f"Invalid personality name: {error}", "code": "invalid_name" } ``` 2. Validate state values after reading them. Treat malformed, absolute, nested, or traversal-containing values as invalid and fall back safely to `default`. 3. Enforce canonical containment: ```python root = personalities_dir.resolve() folder = (root / personality_name).resolve(strict=True) if not folder.is_relative_to(root) or folder.parent != root: raise ValueError("Personality path escapes the personality directory") ``` 4. Reject symbolic links for personality directories and personality files, or require resolved files to remain beneath the approved personality folder. 5. Store only validated logical identifiers in `_personality_state.json`, never arbitrary paths. 6. Write the state file atomically with restrictive permissions: - Write to a temporary file in the same directory. - Flush and synchronize it. - Replace the old state with `os.replace()`. - Limit write permissions to the owning user. 7. Before each heartbeat restoration, revalidate: - The state schema. - The personality name. - Directory containment. - File type and symlink status. - Expected file size limits. 8. Add regression tests for malicious state values and switches using `..`, `../name`, absolute paths, nested paths, and symlinked files. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description presents a full personality management skill focused on creating personas, listing them, switching between them, persistence across sessions, heartbeat restoration, and atomic backup/rollback safeguards. The actual code chunk does not implement those user-facing capabilities. Instead, it performs backup maintenance only: it inspects backup metadata, deletes old backups according to --keep and --days parameters, and outputs a JSON cleanup summary. While backup handling is tangentially related to the declared system, this code's primary purpose is materially different and narrower than the declared functionality, so this is a clear description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description presents a broad personality management and switching feature set, with strong guarantees around persistence, restoration, and safe switching. The actual code chunk is narrowly scoped to creating a personality folder and validating naming constraints. While folder creation is consistent with part of '/create-personality', the major advertised behaviors—switching, listing, auto-generating persona files, persistence, heartbeat restoration, and backup/rollback—are absent from this code. Therefore the declared description does not accurately represent what this supplied code chunk actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
This code chunk's primary purpose is deleting a personality, not creating or switching among personalities as described. While it does perform a limited switch to 'default' when deleting the active personality, that switch is incidental to deletion. The declared description emphasizes creation, activation, persistence, heartbeat restoration, and backup/rollback safeguards, none of which are implemented here. The undeclared destructive capability—removing personality folders from disk—is materially different and should be flagged as a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description promises substantive personality lifecycle functionality: listing/activating personalities, creating personas with auto-filled attributes, persistence across sessions, heartbeat restoration, and atomic switching with backup/rollback. The supplied code does none of that. Its only behavior is to open a local gateway config file and add Telegram custom command metadata entries. While registering commands could support a larger personality system, this code chunk by itself is only configuration plumbing and has a materially different primary purpose. It also performs a Telegram-specific config modification that is not disclosed in the description, and registers rename/delete commands that are absent from the declared purpose. Therefore this is a clear description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description promises a broad personality lifecycle feature centered on creating, listing, activating, persisting, and safely switching personalities. The actual code chunk has a materially different and much narrower purpose: renaming a personality directory. While updating state and attempting rollback are loosely related to personality management, they do not implement the declared core behaviors. The code also exposes an undeclared capability—renaming personalities—which is not mentioned in the description. Therefore, this is a clear description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The code is broadly consistent with the switching portion of the description: it does atomic switching, creates a backup before switching, persists current changes, updates state, verifies integrity, and rolls back on failure. However, the declared purpose describes a larger skill that can also create personalities, list/activate them via commands, and restore across session boundaries and conversation compacting with heartbeat restoration. None of those additional capabilities are present in this code chunk. Also, the code includes an undeclared operational capability to delete old backups during cleanup. Because the provided code materially implements only a subset of the advertised functionality and includes some undeclared behavior, this is a description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description promises a personality-switching system with persona creation, persistence across sessions, heartbeat restoration, and safe atomic switching with backup/rollback. The actual code does none of that. Its primary and only behavior is to open a local config file under ~/.openclaw/openclaw.json and remove several Telegram custom command entries. This is a materially different purpose and resource access pattern from the declared functionality. Therefore this is a clear description-behavior mismatch.

Memory Manipulation

High
Category
Memory Poisoning
Content
Ready: Use /personality sage to activate
```

**After Creation:** The new personality is ready to use immediately. Edit SOUL.md and IDENTITY.md in the personality folder to refine further if desired.

**Technical:** Agent chooses name to keep personality references concise (1-2 words). Name is validated for uniqueness and format automatically.
Confidence
94% confidence
Finding
This skill’s core purpose is to replace SOUL.md and IDENTITY.md, which are effectively prompt/persona control files for the assistant, and to make those changes immediately active and persistent. That is a memory/prompt-manipulation capability with significant security implications because a crafted personality can alter the assistant’s behavior, safety boundaries, or trust model across sessions.

Memory Manipulation

High
Category
Memory Poisoning
Content
"code": "personality_not_found"
        }
    
    # Validate new name
    is_valid, error_msg = validate_personality_name(new_name)
    if not is_valid:
        return {
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Memory Manipulation

High
Category
Memory Poisoning
Content
"code": "personality_not_found"
        }
    
    # Validate new name
    is_valid, error_msg = validate_personality_name(new_name)
    if not is_valid:
        return {
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Memory Manipulation

High
Category
Memory Poisoning
Content
fi
fi

# Delete state file completely (it's part of this skill's state)
STATE_FILE="${PERSONALITIES_DIR}/_personality_state.json"
if [ -f "$STATE_FILE" ]; then
    rm "$STATE_FILE"
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill describes file read/write behavior across the workspace, modifies root personality files, and maintains state/backups, but does not declare any explicit tool scope or permissions boundary. This increases risk because a host may grant broader filesystem access than users expect, and the skill’s behavior includes persistent modification of sensitive prompt/configuration files.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: personality-switcher
description: Create and switch between AI assistant personalities. Use /personality to list and activate saved personalities. Use /create-personality to design new personas with auto-filled SOUL and IDENTITY. Personalities persist across session boundaries and conversation compacting with automatic heartbeat restoration. Atomic switching with backup and rollback safeguards. Always backs up current state before switching.
---

# Personality Switcher Skill
Confidence
93% confidence
Finding
The skill explicitly persists personality state across session boundaries and restores it automatically on heartbeat, extending the lifetime of any malicious or unsafe persona modification. Persistence makes prompt-level compromise more dangerous because unwanted behavior can survive restarts, compaction, and operator attempts to reset conversational state.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill advertises automatic deletion of old backups but does not prominently warn that this is irreversible or could remove the only recoverable copies of prior personality states. Because the skill manages persistent agent state files, silent retention cleanup can cause unintended loss of auditability and rollback capability.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
Backups are stored in a dedicated folder (not scattered at workspace root). When you switch personalities:
- A timestamped backup of the previous personality is created
- **Automatic cleanup runs** — keeps the 10 most recent backups by default
- Old backups are automatically deleted to prevent clutter

**Manual Cleanup:**
```bash
Confidence
88% confidence
Finding
Automatic deletion decisions are made by the system without an explicit per-run user confirmation, and the action affects persistent stored state. In a skill that modifies identity/personality files and backups, autonomous cleanup can unexpectedly remove recovery points and amplify damage from mistakes or malicious state changes.

File System Enumeration

Medium
Category
Data Exfiltration
Content
```bash
# List available backups
ls -la ~/.openclaw/workspace/personalities/backups/

# Copy backup files back to workspace root if needed
cp ~/.openclaw/workspace/personalities/backups/current_<timestamp>/SOUL.md ~/.openclaw/workspace/SOUL.md
Confidence
60% confidence
Finding
Code scans file system directories looking for sensitive files. This could be reconnaissance for credential theft.

Session Persistence

Medium
Category
Rogue Agent
Content
BACKUPS_DIR="${PERSONALITIES_DIR}/backups"
HEARTBEAT_FILE="${WORKSPACE}/HEARTBEAT.md"

# Create personalities and backups directories
mkdir -p "$PERSONALITIES_DIR"
mkdir -p "$BACKUPS_DIR"
Confidence
84% confidence
Finding
This skill is explicitly designed to persist personalities across sessions, and the installer creates persistent directories and state to support that behavior. While persistence is aligned with the feature, it still creates long-lived state that can retain prompts, identities, or behavioral configuration beyond a single session, increasing the risk of unauthorized reuse, tampering, or privacy issues.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script creates directories, copies SOUL.md and IDENTITY.md into persistent storage, and overwrites state-related files without any confirmation or preview. Silent modification of user workspace state is risky because it can unexpectedly alter behavior, duplicate sensitive content into new locations, and make rollback difficult if the install was unintended or the copied files contain private data.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The installer modifies HEARTBEAT.md to inject an executable command that will later run a Python restoration script. Persistently inserting executable behavior into a recurring workflow creates an implicit autorun mechanism, which increases the blast radius of the skill and can be abused if the referenced script is modified or compromised.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The installer invokes a Telegram command registration script even though the stated skill purpose is local personality switching and persistence. Adding an external integration during install expands the trust boundary, may create bot-facing capabilities or networked side effects, and is not transparently disclosed or necessary for the core feature shown in this file.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code performs a recursive folder deletion with shutil.rmtree, which is a destructive and irreversible operation. While the module docstring says it deletes a personality, there is no confirmation prompt or user-facing warning immediately before the deletion itself.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The script edits global skill-management state by deleting its metadata file, removing its directory, and mutating the shared clawhub lock.json. These actions affect the broader installation registry and can cause inconsistent or unauthorized state changes if paths or assumptions are wrong, especially because the skill's stated purpose is personality switching rather than package-manager maintenance.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The uninstall script invokes an external command-unregistration helper for Telegram, which reaches beyond simple local cleanup of personality files. In the context of a personality-switching skill, modifying external bot command state is a broader side effect that could remove commands unexpectedly, and the action is performed without visible validation that the commands belong exclusively to this skill.

Static analysis

No suspicious patterns detected.