Back to skill

Security audit

persona-creator

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent persona-generation purpose, but it handles private conversation history and persona files with unsafe scoping and file handling that warrants review before installation.

Install only if you are comfortable letting the skill read local memory files, send sampled conversation text through the model for style analysis, and store a reusable persona profile. Before wider use, the publisher should validate usernames, enforce persona-directory containment, replace predictable /tmp files with secure temporary handling or in-memory transfer, clean up raw message data, require explicit consent before profiling, and disclose or restrict real-person style imitation.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • 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/save_persona.py:89
Finding
Path Traversal Through Username Allows Writes Outside the Persona Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/save_persona.py:89-108` **Vulnerability Type**: Path traversal and arbitrary JSON file write **Risk Level**: High ### Vulnerable Code ```python user_name = meta['user'] output = { "_comment": "Auto-generated by persona-creator skill. Do not edit manually.", "user_id": user_name, "display_name": user_name, "created_at": now, "updated_at": now, "version": 1, "statistics": { "analyzed_messages": len(meta.get('messages', [])), "date_range": date_range, "memory_files_scanned": file_names }, "persona": analysis } output_path = persona_dir / f"{user_name}.json" # If it already exists, retain created_at if output_path.exists(): with open(output_path, 'r', encoding='utf-8') as f: existing = json.load(f) output['created_at'] = existing.get('created_at', now) output['version'] = existing.get('version', 1) + 1 with open(output_path, 'w', encoding='utf-8') as f: json.dump(output, f, ensure_ascii=False, indent=2) ``` The username originates from metadata generated from the `--user` command-line argument in `scripts/analyze.py`: ```python meta = { "user": args.user, "messages": messages, "memory_files": [str(f) for f in memory_files], "persona_dir": args.persona_dir, "template_path": template_path } ``` ### Technical Analysis The user-controlled username is interpolated directly into a filesystem path without an allowlist, canonicalization, or containment check: ```python output_path = persona_dir / f"{user_name}.json" ``` `pathlib.Path` does not prevent traversal components such as `../`. A username such as `../config/profile` therefore produces a destination equivalent to `persona_dir/../config/profile.json`. Because the code opens the destination in write mode, an existing JSON file outside the intended persona directory can be replaced with attacker-influenced persona data. Creation outside the ...[truncated 1187 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict usernames to a conservative allowlist, for example: ```python if not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", user_name): raise ValueError("Invalid username") ``` 2. Explicitly reject absolute paths, path separators, `.` and `..`. 3. Resolve both the base directory and destination before writing, then enforce containment: ```python base = Path(meta["persona_dir"]).resolve() destination = (base / f"{user_name}.json").resolve() if destination.parent != base: raise ValueError("Persona path escapes the configured directory") ``` 4. Consider mapping external usernames to generated internal identifiers rather than using display names as filenames. 5. Avoid following symlinks when the threat model includes other local users. 6. Write to a secure temporary file in the destination directory and atomically replace the target only after validation. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/forget.py:24
Finding
Path Traversal Allows Files Outside the Persona Directory to Be Reset or Deleted<![CDATA[ ## Vulnerability Details **File Location**: `scripts/forget.py:24-55` **Vulnerability Type**: Unrestricted filesystem access through path traversal **Risk Level**: High ### Vulnerable Code ```python persona_path = Path(args.persona_dir) / f"{args.user}.json" template_path = Path(args.persona_dir) / "yourself.json" if not persona_path.exists(): print(f"INFO: {persona_path} does not exist; no action is needed.") sys.exit(0) if args.reset: # Reset to the template if template_path.exists(): with open(template_path, 'r', encoding='utf-8') as f: template = json.load(f) template['user_id'] = args.user template['display_name'] = args.user template['created_at'] = None template['updated_at'] = None template['statistics'] = { "analyzed_messages": 0, "date_range": [None, None], "memory_files_scanned": [] } with open(persona_path, 'w', encoding='utf-8') as f: json.dump(template, f, ensure_ascii=False, indent=2) print(f"Reset the style profile for {args.user}.") else: print(f"ERROR: Template file {template_path} does not exist.", file=sys.stderr) sys.exit(1) else: # Back up and then delete backup_path = persona_path.with_suffix( f".bak.{datetime.utcnow().strftime('%Y%m%d%H%M%S')}.json" ) shutil.copy2(persona_path, backup_path) os.remove(persona_path) print(f"Deleted the style profile for {args.user}; backup: {backup_path.name}") ``` ### Technical Analysis The `--user` argument is concatenated directly into `persona_path`. No validation ensures that the resolved target remains inside `--persona-dir`. In reset mode, the traversed target is opened in write mode and replaced by the persona template. In delete mode, the target is copied to a predictable backup name and then removed with `os.remove()`. Deletion is especially exposed because exploitation only requir ...[truncated 1290 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply the same strict username allowlist to all create, refresh, reset, load, and delete operations. 2. Resolve and validate the canonical target before checking its existence or modifying it: ```python base = Path(args.persona_dir).resolve() target = (base / f"{args.user}.json").resolve() if target.parent != base: raise ValueError("Target is outside the persona directory") ``` 3. Reject usernames containing `/`, `\`, `..`, NUL characters, or platform-specific drive and path syntax. 4. Ensure the canonical target shown to the user during confirmation is the exact target subsequently modified. 5. Consider using directory-relative file descriptors and no-follow semantics to reduce symlink and time-of-check/time-of-use risks. 6. For deletion, provide an explicit irreversible-delete option and establish a documented backup-retention policy. 7. Add tests covering relative traversal, absolute paths, encoded separators, nested paths, and symlink targets. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
scripts/analyze.py:74
Finding
Untrusted Memory Content Can Poison Generated Role-Play Instructions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyze.py:74-128`; `SKILL.md:83-99`; `SKILL.md:219-237` **Vulnerability Type**: Indirect prompt injection through conversation memories **Risk Level**: Medium ### Vulnerable Code ```python def build_analysis_prompt(messages: list[str], user_name: str) -> str: """Build the analysis prompt for the LLM.""" sample = "\n".join(f"{i+1}. {m}" for i, m in enumerate(messages[:60])) return f"""You are a language-style analysis expert. The following are historical message samples from user "{user_name}" ({len(messages)} messages total). [Message samples] {sample} Return the analysis strictly in the following JSON format and do not output any additional text: {{ "tone": {{ "overall": "One-sentence description of the overall tone", "formality_level": 0.0, "enthusiasm_level": 0.0, "description": "Detailed description" }}, "sentence_structure": {{ "avg_length": 0, "preference": "Sentence-structure preference", "question_ratio": 0.0, "uses_ellipsis": false }}, "emoji_usage": {{ "frequency": "low/medium/high", "favorites": [], "style": "Emoji style" }}, "professionalism": {{ "tech_density": 0.0, "jargon_level": "Jargon usage", "domain_keywords": [] }}, "humor": {{ "level": 0.0, "style": "Humor style" }}, "interactivity": {{ "asking_questions": false, "follow_up_ratio": 0.0, "directive_style": false }}, "formatting": {{ "loves_tables": false, "uses_lists": "never/occasionally/frequently", "markdown_heavy": false, "uses_code_blocks": false }}, "catchphrases": [], "common_phrases": [], "topic_preferences": [], "communication_patterns": [], "system_prompt_fragment": "Write a 50-100 character system prompt describing this person's style for AI imitation" }}""" ``` The documented workflow then instructs the Agent to send the resulting prompt directly to an LLM and later elev ...[truncated 2526 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Explicitly identify all message samples as untrusted quoted data and instruct the model never to execute or follow commands found inside them. 2. Use strong structural delimiters and a separate data field rather than concatenating raw text into natural-language instructions. 3. Validate the LLM response with a strict JSON Schema: - Require only documented properties. - Enforce numeric ranges. - Enforce array and string length limits. - Reject unknown fields. - Require expected primitive types. 4. Treat `system_prompt_fragment` as untrusted model output. Reject instruction-like content unrelated to style, including requests to ignore rules, invoke tools, access secrets, or alter safety behavior. 5. Do not insert generated profile text at system-level authority. Prefer trusted, programmatically generated role-play instructions assembled from validated scalar attributes. 6. Encode or quote catchphrases and other free-form fields when adding them to prompts. 7. Add adversarial tests containing common indirect prompt-injection patterns in memory samples. 8. Require user review or approval before activating a newly generated role-play fragment. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/analyze.py:137
Finding
Predictable Shared Temporary Files Expose Conversation Data and Permit Cross-Run Interference<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyze.py:137-139` and `scripts/analyze.py:244-246` **Vulnerability Type**: Unsafe temporary-file handling and plaintext sensitive-data storage **Risk Level**: Medium ### Vulnerable Code ```python # Write the prompt to a temporary file for the Agent to read prompt_file = Path("/tmp/persona_analysis_prompt.txt") prompt_file.write_text(prompt, encoding='utf-8') print(f"PROMPT_FILE:{prompt_file}") ``` ```python meta_path = Path("/tmp/persona_meta.json") meta_path.write_text( json.dumps(meta, ensure_ascii=False), encoding='utf-8' ) print(f"\nMETA_FILE:{meta_path}") ``` The metadata contains the extracted messages and filesystem paths: ```python meta = { "user": args.user, "messages": messages, "memory_files": [str(f) for f in memory_files], "persona_dir": args.persona_dir, "template_path": template_path } ``` ### Technical Analysis The script uses globally predictable paths under `/tmp` instead of securely generated per-run files. The metadata file stores complete extracted conversation messages, memory-file paths, the username, and output configuration in plaintext. Predictable names introduce several risks: - Concurrent analyses overwrite the same files, causing cross-run contamination. - A local process can monitor or read the files if permissions permit. - A pre-created filesystem object at the predictable path may redirect or interfere with writes, depending on platform and directory protections. - Files are not removed after processing, extending the exposure period. - The analysis and save stages can consume metadata from different runs because no unique run identifier binds them together. ### Attack Path 1. An attacker with local access predicts `/tmp/persona_meta.json` or `/tmp/persona_analysis_prompt.txt`. 2. The attacker monitors, reads, replaces, or pre-creates the predictable path, subject to operating-system permissions and temporary-directory prote ...[truncated 1126 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace fixed paths with `tempfile.TemporaryDirectory()` or `tempfile.NamedTemporaryFile()` using unique, unpredictable names. 2. Create files with owner-only permissions, such as mode `0600`. 3. Pass the generated metadata path explicitly between analysis and save operations. 4. Bind metadata and analysis results to a cryptographically random run identifier. 5. Validate file ownership and permissions before reading metadata. 6. Avoid following symbolic links and use exclusive creation where available. 7. Remove temporary data in a `finally` block immediately after successful or failed processing. 8. Minimize stored data: avoid placing complete message histories in temporary metadata when only counts, filenames, or an in-memory transfer are required. 9. Prefer direct in-memory transfer between workflow stages when supported. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
A second description/behavior mismatch is present: the skill promises persona creation, refresh, reset, and role-play support, but the analyzed behavior does not implement those flows as described. Such inconsistencies can hide unintended data processing paths or cause the agent to operate under false assumptions about state and user authorization.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
A second description/behavior mismatch is present: the skill promises persona creation, refresh, reset, and role-play support, but the analyzed behavior does not implement those flows as described. Such inconsistencies can hide unintended data processing paths or cause the agent to operate under false assumptions about state and user authorization.

Ssd 4

High
Confidence
96% confidence
Finding
The role-play mode constructs a hidden impersonation workflow that tells the model to imitate a named user's style while explicitly not revealing the imitation. This enables deceptive impersonation, increasing the risk of social engineering, fraudulent messages, reputational harm, and misuse of a profile derived from private historical conversations.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill instructs the agent to read from memory/ and write persona artifacts and temporary files, but it declares no explicit tool scope or permissions. This creates unnecessary ambiguity about what file operations are authorized and increases the chance of over-broad access or unsafe execution in hosts that infer capabilities implicitly.

Ssd 3

Medium
Confidence
89% confidence
Finding
The skill directs analysis of historical conversations to derive and persist a user-specific persona profile. Even if intended as a feature, this is privacy-sensitive profiling because it transforms prior chats into a reusable behavioral dossier, which may exceed user expectations unless informed consent, minimization, and retention controls are in place.

Vague Triggers

Medium
Confidence
94% confidence
Finding
Using a broad everyday trigger like "persona" can activate the skill in unrelated conversations. In this skill, accidental activation is more sensitive because it may prompt inspection of historical memory and creation of a user profile, causing unintended privacy-affecting behavior.

Ssd 3

Medium
Confidence
86% confidence
Finding
Passing prompts built from memory-derived content directly back into the LLM creates a prompt-injection and privacy boundary issue. Historical messages may contain adversarial instructions or sensitive content that the model then reinterprets during analysis, potentially leading to unsafe output, leakage, or manipulation of the generated profile.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The standalone trigger term "refresh" is overly vague and may match routine user language. Because refresh here can initiate memory-based analysis and profile updates, accidental triggering could lead to unintended processing of conversation history.

Natural-Language Policy Violations

Medium
Confidence
87% confidence
Finding
The JSON template hard-codes the emoji frequency field to Chinese values ("低|中|高") rather than using a locale-neutral schema or offering language choice. This can violate language/locale policy because it imposes a specific language in a general-purpose persona file with no documented opt-in or regional justification.

Ssd 3

Medium
Confidence
92% confidence
Finding
The prompt builder forwards user messages verbatim into the LLM input, which means private historical content is reproduced and sent onward for processing. In a persona-generation skill this is functionally related to the feature, but it still increases privacy risk because unnecessary verbatim retention and broad prompt inclusion can leak sensitive content to logs, downstream systems, or model providers.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The function writes the full prompt, containing conversation-derived content, to a fixed path under /tmp. Fixed temporary filenames are prone to unintended cross-process access, stale data exposure, and accidental reuse, especially on shared systems or multi-tenant agent runners.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill writes the full LLM prompt with sampled user messages to /tmp without informing the user. This is a privacy and data-handling issue because the stored artifact contains directly readable excerpts of historical conversations that may be sensitive.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
In normal operation, the script prints the full analysis prompt to stdout between ANALYSIS_PROMPT_START and ANALYSIS_PROMPT_END, and that prompt embeds up to 60 historical user messages verbatim. In many agent/runtime environments, stdout is logged, surfaced to operators, or captured by other tools, so this creates an unnecessary disclosure path for private conversation content.

Ssd 3

Medium
Confidence
97% confidence
Finding
The script serializes the full extracted message list into /tmp/persona_meta.json for later processing. Persisting raw conversation text in a temporary file broadens the attack surface and retention window for sensitive user data beyond what is required to generate the persona.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script persists raw extracted messages and metadata to /tmp/persona_meta.json without any user-facing disclosure or consent signal. Because the file includes message content and file paths, it expands the exposure of personal data beyond what users would reasonably expect from a style-analysis feature.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module docstring, argparse descriptions, and operational messages are written entirely in Chinese, which imposes a specific language on users. Under the policy, locale constraints should either be optional for the user or clearly documented as justified for a region-specific tool, which is not present here.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code file contains natural-language strings such as the module docstring, argument descriptions, and status messages entirely in Chinese. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This Python file contains natural-language documentation and interface text entirely in Chinese, including the module docstring and CLI help strings, with no indication that the skill is China-specific or that another language is available. That creates a locale policy issue because the skill imposes a specific language on users without opt-in.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The script prints errors, success messages, and summary output in Chinese, which continues the language-locking behavior throughout runtime. Because the file does not offer a language choice or justify a region-specific requirement, this is a natural-language policy violation.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The module description, CLI descriptions, runtime messages, and generated prompt are all written in Chinese, which imposes a specific language on users without any opt-in or selection mechanism. Under the stated policy, forcing a specific language without user choice is a natural-language policy concern.

Static analysis

No suspicious patterns detected.