Back to skill

Security audit

Conversation Archive

Security checks for vulnerabilities and agentic risk

Overview

The skill is meant to save conversation history, but it overstates its privacy and retrieval features and contains unsafe local file-write behavior that could preserve sensitive excerpts or write outside its archive folder.

Install only if you are comfortable with automatic local persistence of conversation-derived content. Before broad use, the skill should validate archive paths, add explicit consent and disable/delete controls, implement redaction and retention as documented, restrict file permissions, and either implement or remove the claimed search and memory-integration behavior.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/archive_session.py:102
Finding
Path Traversal Through Unvalidated Session and Date Identifiers## Vulnerability Details **File Location**: `scripts/archive_session.py:102-113, 134-135` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code ```python def archive_session(session_id, messages, channel="webchat", date=None): """归档一个 session""" if date is None: date = datetime.now().strftime("%Y-%m-%d") year_month = date[:7] # YYYY-MM # 确保目录存在 session_dir = os.path.join(ARCHIVE_DIR, "sessions", year_month) os.makedirs(session_dir, exist_ok=True) archive_file = os.path.join(session_dir, f"{session_id}.json") # ... # 写入归档 with open(archive_file, "w", encoding="utf-8") as f: json.dump(archive, f, ensure_ascii=False, indent=2) ``` ### Technical Analysis The `date` and `session_id` parameters are incorporated into filesystem paths without format validation, canonicalization, or containment checks. A `session_id` containing `../` path segments can cause `archive_file` to resolve outside the intended session directory. An absolute `session_id` is especially dangerous because `os.path.join()` discards preceding path components when a later component is absolute. Similarly, the first seven characters of `date` are treated as a directory name without verifying that the value follows the expected `YYYY-MM-DD` format. The destination is opened with mode `"w"`, so an existing writable file is truncated before the archive JSON is written. Exploitation depends on an attacker being able to influence arguments supplied to `archive_session()`. The current script does not expose a command-line invocation for this function, but the Skill documentation presents it as an externally callable archival tool. ### Attack Path 1. An attacker gains control over, or influences, the `session_id` or `date` passed to `archive_session()`. 2. The attacker supplies a traversal identifier such as `../../ta ...[truncated 1120 chars]
Remediation
## Remediation Suggestions 1. Enforce a strict allowlist for session identifiers, such as `^[A-Za-z0-9_-]{1,128}$`. 2. Parse dates with `datetime.strptime(date, "%Y-%m-%d")` and derive the directory from the parsed date rather than slicing untrusted input. 3. Resolve the archive root and destination with `pathlib.Path.resolve()`. 4. Verify that the resolved destination is a descendant of the intended session directory before opening it. 5. Reject absolute paths, path separators, `.` components, and `..` components in identifiers. 6. Consider atomic writes through a securely created temporary file followed by `os.replace()`. 7. If archives must not overwrite existing sessions, use exclusive creation mode (`"x"`) or explicitly handle duplicates. 8. Add tests covering absolute paths, traversal sequences, mixed separators, malformed dates, and symbolic-link edge cases. Example hardening approach: ```python from datetime import datetime from pathlib import Path import re SESSION_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,128}$") def get_archive_path(session_id, date): if not SESSION_ID_PATTERN.fullmatch(session_id): raise ValueError("Invalid session identifier") parsed_date = datetime.strptime(date, "%Y-%m-%d") archive_root = ( Path(ARCHIVE_DIR) / "sessions" / parsed_date.strftime("%Y-%m") ).resolve() archive_root.mkdir(parents=True, exist_ok=True) destination = (archive_root / f"{session_id}.json").resolve() if destination.parent != archive_root: raise ValueError("Archive path escapes the archive directory") return destination ```

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/archive_session.py:37
Finding
Sensitive Conversation Data Is Persisted Without the Documented Redaction## Vulnerability Details **File Location**: `SKILL.md:207-209`; `scripts/archive_session.py:37-95, 118-143` **Vulnerability Type**: Plaintext persistence of potentially sensitive data **Risk Level**: Medium ### Vulnerable Code The Skill documentation claims that sensitive information is excluded and messages are scanned and redacted: ```markdown **安全清理:** - 不存 API key、token、密码等敏感信息 - messages 字段在归档时做敏感信息扫描和脱敏 ``` However, message content is copied directly into derived archive fields: ```python def extract_decisions(messages): """提取决策类语句""" decisions = [] for msg in messages: content = msg.get("content", "") if isinstance(content, list): for c in content: if isinstance(c, dict) and c.get("type") == "text": content = c.get("text", "") break if isinstance(content, str): if any(kw in content for kw in ["好的", "同意", "开始", "就这个", "没问题"]): if len(content) < 200: decisions.append({"text": content[:100], "source": "user" if msg.get("role") == "user" else "assistant"}) return decisions[:5] def extract_feedback(messages): """提取反馈/纠正类语句""" feedback = [] for msg in messages: content = msg.get("content", "") if isinstance(content, list): for c in content: if isinstance(c, dict) and c.get("type") == "text": content = c.get("text", "") break if isinstance(content, str): if any(kw in content for kw in ["不对", "错了", "不是这样", "不要", "应该", "改成"]): feedback.append({"user": content[:150], "from": "user" if msg.get("role") == "user" else "assistant"}) return feedback[:5] def generate_summary(messages): """生成对话摘要""" user_msgs = [] assistant_msgs = [] for msg in messages: conte ...[truncated 3812 chars]
Remediation
## Remediation Suggestions 1. Implement a centralized redaction function and apply it before content enters summaries, decisions, feedback, archive files, logs, or the index. 2. Detect common credential formats, including bearer tokens, API keys, passwords, private keys, authorization headers, connection strings, and provider-specific secrets. 3. Prefer data minimization over pattern matching: avoid storing verbatim message excerpts unless explicitly required. 4. Apply restrictive permissions when creating archive directories and files, such as directory mode `0700` and file mode `0600`. 5. Avoid printing sensitive derived content to standard output. 6. Implement and test the documented retention policy, including deletion of obsolete sensitive content. 7. Handle existing archives by providing a migration utility that redacts or removes previously stored values. 8. Add tests with representative and encoded credentials to confirm that no sensitive values reach either session files or `index.json`. 9. Update `SKILL.md` so its security claims accurately reflect implemented behavior until redaction and retention controls are operational. Redaction should occur before extraction and again immediately before serialization as defense in depth. Pattern-based detection should be supplemented with entropy checks and explicit field-based filtering where structured message inputs are available.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description promises automatic session dialogue archiving with preservation of original records, retrieval support, and misunderstanding correction support. The actual code only writes a compact archive object containing session ID, date, channel, extracted topics/decisions/feedback, summary, message count, and timestamp, plus an index entry. Critically, it does not save the full original message history, so '保留原始记录' is not accurately represented. It also contains no retrieval/search API or command behavior beyond maintaining an index file, and no actual correction workflow—only heuristic extraction of feedback-like utterances. Finally, the claimed linkage with another memory component is not shown. The primary purpose is related, but several key declared capabilities are absent, so this is a description-behavior mismatch.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill describes capabilities that archive session data and write records to disk, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization gap where a memory-oriented skill may gain or imply broader file-write behavior than reviewers or users expect, increasing the risk of unbounded persistence of sensitive conversation data.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill explicitly describes automatic archiving of full session conversations, including messages and extracted metadata, but does not provide a clear user-facing privacy warning, consent mechanism, or opt-out flow. Because conversations commonly contain personal, confidential, or regulated data, silent persistence materially increases privacy and data-retention risk, especially with automatic triggers like inactivity, restart, or scheduled archival.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script persists conversation-derived metadata such as summaries, feedback, decisions, and topics to local disk automatically, with no consent, notice, retention control, or access restriction. Because conversation content may include sensitive personal, operational, or credential-like information, silent archival increases privacy and data exposure risk, especially on shared machines or multi-user environments.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The top-level description is entirely in Chinese and the invocation examples are framed around Chinese-only user phrases, but the file does not state that the skill is region-specific or that users may choose another language. This can be a natural-language locale policy issue when a skill implicitly forces one language without opt-in.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The trigger keyword table lists only Chinese phrases for activation behavior and provides no alternative language handling or statement that the skill is limited to Chinese-speaking contexts. Without documented locale scope or opt-in, this reads as a language restriction embedded in the skill behavior.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The file's natural-language description and operational comments are written entirely in Chinese, indicating the skill is intended to operate in a specific language context. There is no visible opt-in, language selection mechanism, or justification that this is a region-specific tool, so this may violate language/locale policy expectations.

Static analysis

No suspicious patterns detected.