Back to skill

Security audit

Chat Lift

Security checks for vulnerabilities and agentic risk

Overview

This is a local chat archive tool, but it needs review because crafted archive data can make it write HTML outside the intended folder and inject content into generated pages.

Review before installing. Use only exports you trust, store the archive in a private non-synced directory, do not publish the generated web folder without redaction, and avoid running the archive generator on edited or third-party JSON until filename validation and full HTML escaping are fixed.

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
chat_archive.py:205
Finding
Path Traversal Through Unvalidated Conversation Identifier<![CDATA[ ## Vulnerability Details **File Location**: `chat_archive.py`, lines 205–207 **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code ```python safe_id = conv['id'] with open(self.output_dir / f'{safe_id}.html', 'w', encoding='utf-8') as f: f.write(html_content) ``` ### Technical Analysis Conversation records are loaded from JSON files under the archive directory. The attacker-controlled `id` field is subsequently used as part of an output path without validation or normalization. Although the variable is named `safe_id`, no sanitization is performed. A conversation identifier containing parent-directory components, such as `../../public/page`, causes the resulting path to escape `self.output_dir`. An absolute identifier can also cause `pathlib` path composition to discard the intended parent directory. The forced `.html` suffix limits the filename extension but does not prevent writing outside the archive directory. ### Attack Path 1. An attacker supplies a crafted conversation export or causes a malicious JSON file to be placed in `chat-archive/json`. 2. The JSON conversation contains an identifier such as: ```json { "id": "../../public/attacker-page", "title": "Crafted conversation", "source": "chatgpt", "messages": [] } ``` 3. The victim runs: ```bash python3 chat_archive.py ``` 4. `_load_conversations()` accepts the JSON record without schema validation. 5. `_generate_conversation_page()` constructs the path from the malicious identifier. 6. The generated HTML is written outside `chat-archive/web`, provided the process has filesystem permission to write to the target location. ### Impact Assessment The vulnerability allows creation or overwrite of `.html` files anywhere writable by the current operating-system user. It does not directly grant privileges beyond those of the process, but it can: - Overwrite user-owned HTML pages. - Modify co ...[truncated 332 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not use imported identifiers directly as filesystem names. 1. Normalize identifiers using a strict allowlist: ```python raw_id = str(conv.get('id', '')) safe_id = re.sub(r'[^a-zA-Z0-9_-]', '-', raw_id).strip('-_') if not safe_id: raise ValueError("Conversation ID does not produce a valid filename") ``` 2. Resolve the destination and verify containment: ```python output_root = self.output_dir.resolve() output_path = (output_root / f'{safe_id}.html').resolve() if output_root not in output_path.parents: raise ValueError("Conversation output path escapes archive directory") with open(output_path, 'w', encoding='utf-8') as f: f.write(html_content) ``` 3. Prefer generating filenames from a cryptographic digest or an application-generated identifier rather than trusting imported metadata. 4. Apply the same filename mapping when creating links in `index.html`, so page generation and navigation use one validated identifier. 5. Add tests covering `../`, absolute paths, Windows separators, empty identifiers, Unicode separators, and identifiers containing only rejected characters. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
chat_archive.py:122
Finding
Stored HTML Injection Through Unescaped Conversation Metadata<![CDATA[ ## Vulnerability Details **File Location**: `chat_archive.py`, lines 122–130 and 171–174 **Vulnerability Type**: Stored HTML injection / cross-site scripting in generated archive **Risk Level**: Medium ### Vulnerable Code Index-page generation: ```python html_content += f""" <div class="conversation-card" data-source="{conv.get('source', 'unknown')}" data-title="{html.escape(conv['title']).lower()}"> <a href="{conv['id']}.html"> <h3>{html.escape(conv['title'])}</h3> <div class="card-meta"> <span class="source source-{conv.get('source', 'unknown')}">{conv.get('source', 'unknown')}</span> <span class="date">{create_time}</span> <span class="count">{message_count} messages</span> </div> </a> </div> """ ``` Conversation-page metadata generation: ```python <div class="metadata"> <span class="source source-{conv.get('source', 'unknown')}">{conv.get('source', 'unknown')}</span> <span class="date">{self._format_timestamp(conv.get('create_time', ''))}</span> <span class="count">{len(conv.get('messages', []))} messages</span> </div> ``` ### Technical Analysis The generator correctly escapes conversation titles and message content in several locations, but it does not consistently escape other imported metadata. The following values are inserted directly into HTML: - `conv['id']` inside an `href` attribute. - `conv['source']` inside class attributes, data attributes, and text nodes. - Formatted creation and message timestamps inside text nodes. The timestamp formatter returns an invalid timestamp string unchanged when parsing fails. Consequently, a crafted string containing HTML markup remains attacker-controlled when inserted into the page. An identifier containing a quotation mark can terminate the `href` attribute and introduce another attribute, including an event handler. A malformed timestamp can inject an element directly into a text context. Be ...[truncated 1503 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every imported value according to its HTML context: ```python safe_title = html.escape(str(conv.get('title', 'Untitled')), quote=True) safe_source = html.escape(str(conv.get('source', 'unknown')), quote=True) safe_date = html.escape( str(self._format_timestamp(conv.get('create_time', ''))), quote=True ) ``` 2. Never use the raw conversation identifier in `href`. Generate a strictly normalized filename and escape the resulting attribute value: ```python safe_href = html.escape(f'{safe_id}.html', quote=True) ``` 3. Restrict `source` to known values such as `chatgpt`, `claude`, `gemini`, and `unknown` before using it in CSS classes or data attributes. 4. Reject malformed timestamps or render them only after HTML escaping. Do not return untrusted timestamp strings for direct insertion into HTML. 5. Apply escaping to message timestamps as well as conversation timestamps. 6. Add a restrictive Content Security Policy to generated pages as defense in depth, for example: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'"> ``` 7. Add regression tests using quotation marks, angle brackets, event-handler attributes, malformed timestamps, and malicious identifier values. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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 (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code chunk is an HTML archive generator, not a complete import/search/archive pipeline. It assumes conversations already exist as JSON files under chat-archive/json and simply renders them into static pages with CSS and JavaScript. The search functionality is limited to client-side filtering of conversation cards using title and source metadata; it does not search message bodies, so 'full-text search' is not supported by this code. There is also no code to ingest exports from ChatGPT, Claude, or Gemini, nor any source-format normalization beyond reading preexisting JSON objects. The 'static HTML archive' and 'no server required' parts are consistent, but the declared description overstates the implemented capabilities in this supplied chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code substantially matches the import/export portion of the description: it imports ChatGPT, Claude, and Gemini conversations and converts them into Markdown, HTML, and JSON locally with no server. However, key declared capabilities are missing from the supplied code chunk. There is no full-text search functionality, no real indexing mechanism, and no archive-level static site generation such as an index page or browsing/search interface. The HTML generation is limited to individual conversation pages. Therefore the description overstates the implemented functionality, making this a meaningful description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The supplied code is specifically a search component for an existing local archive. It performs full-text search over titles and messages, supports regex/case/date/source/role filtering, and computes basic statistics. Those behaviors partially match the declared 'search' functionality, but the broader declared description promises substantial additional capabilities not present in this code chunk: no code for importing data from ChatGPT/Claude/Gemini, no code for creating or updating archives, no static HTML generation, and no evident format-cleaning/indexing pipeline. Therefore the description overstates what this code actually does, creating a material description-behavior mismatch.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README encourages users to export, index, search, and generate static HTML/Markdown/JSON archives of AI conversations, but it does not prominently warn that those outputs may contain highly sensitive prompts, secrets, personal data, or proprietary information. Because static HTML and markdown archives are easy to copy, sync, publish, or serve accidentally, users may expose confidential content without realizing the risk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill clearly describes reading exported conversation files and writing Markdown, HTML, JSON, and archive outputs, but it does not declare any explicit tool scope such as allowed file operations or path restrictions. In an agent ecosystem, undeclared file read/write capability reduces transparency and can permit broader filesystem access than users expect, especially when handling sensitive chat exports.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill encourages users to import entire AI conversation exports and generate local Markdown, HTML, and JSON archives, but the main usage section does not prominently warn that this duplicates potentially sensitive prompts, credentials, personal data, or proprietary material onto disk. That increases the risk of accidental exposure through backups, shared directories, local search indexing, source control commits, or weak file permissions.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code generates a static web archive from conversation JSON and writes multiple output files containing chat content, which can affect user privacy if the conversations contain sensitive data. Although the script prints status messages, it does not disclose that private conversation contents will be materialized into browsable HTML files on disk.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code prints matched message context from archived conversations directly to the terminal, which can expose sensitive user data from chat history. Although the file documents that it searches archived conversations, it does not include any user-facing warning that results may display private conversation content.

Scope Creep

Low
Category
Excessive Agency
Content
**USE AT YOUR OWN RISK.**

- The author(s) are NOT liable for any damages, losses, or consequences arising from 
  the use or misuse of this software — including but not limited to financial loss, 
  data loss, security breaches, business interruption, or any indirect/consequential damages.
- This software does NOT constitute financial, legal, trading, or professional advice.
- Users are solely responsible for evaluating whether this software is suitable for
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Scope Creep

Low
Category
Excessive Agency
Content
**USE AT YOUR OWN RISK.**

- The author(s) are NOT liable for any damages, losses, or consequences arising from 
  the use or misuse of this software — including but not limited to financial loss, 
  data loss, security breaches, business interruption, or any indirect/consequential damages.
- This software does NOT constitute financial, legal, trading, or professional advice.
- Users are solely responsible for evaluating whether this software is suitable for
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Missing User Warnings

Low
Confidence
78% confidence
Finding
This is a code file, so SQP-2 applies to safety-relevant behaviors when they lack user disclosure. The file documents that metadata is included and that JSON preserves full conversation structure, but it does not explicitly warn that exports may contain sensitive timestamps/source details from user conversations, which could affect privacy if archives are shared or stored insecurely.

Static analysis

No suspicious patterns detected.