Back to skill

Security audit

ChatGPT Exporter

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed ChatGPT conversation exporter, but its relay exporter can still write sensitive exports through pre-existing child symlinks despite stronger safety claims.

Install only if you are comfortable exporting highly sensitive ChatGPT history to local plaintext. Prefer a fresh private destination, avoid reusing directories you do not fully control, avoid the bookmarklet for full-content exports if your Downloads folder is synced, and delete exports promptly with the provided purge command.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/export-conversations.ts:106
Finding
Symlink Following Allows Sensitive Export Disclosure and Arbitrary File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export-conversations.ts:106-109, 394-409, 428-431, 443-475, 509` **Vulnerability Type**: Symlink following / improper filesystem object validation **Risk Level**: High ### Vulnerable Code ```ts function writePrivate(path: string, contents: string): void { writeFileSync(path, contents, { mode: 0o600 }); chmodSync(path, 0o600); } ``` ```ts mkdirSync(outputDir, { recursive: true, mode: 0o700 }); chmodSync(outputDir, 0o700); if (lstatSync(outputDir).isSymbolicLink()) { throw new Error( `Refusing to export to ${outputDir} — the destination itself is a symbolic link.`, ); } const settledDir = realpathSync(outputDir); const settledObjection = destinationObjection(settledDir); if (settledObjection) { throw new Error( `Refusing to export to ${outputDir} — after creation it resolves to ${settledDir}, ` + `which is unacceptable: ${settledObjection}.`, ); } mkdirSync(join(outputDir, "conversations"), { recursive: true, mode: 0o700 }); chmodSync(join(outputDir, "conversations"), 0o700); ``` The following calls consequently write through any existing symbolic links: ```ts writePrivate( join(outputDir, MANIFEST_NAME), JSON.stringify(/* manifest data */), ); writePrivate( join(outputDir, "index.json"), redact(JSON.stringify(items, null, 2), redactSecrets), ); writePrivate( join(outputDir, "conversations", `${item.id}.json`), redact(JSON.stringify(conversation, null, 2), redactSecrets), ); writePrivate(join(outputDir, "conversations", `${item.id}_${slug}.md`), md); writePrivate(join(outputDir, "summary.md"), summary); ``` ### Technical Analysis The exporter validates only the top-level `outputDir` against symbolic links. It does not validate the fixed output files or the `conversations` child directory before opening them. Node.js `writeFileSync()` follows a symbolic link at the destination. Its default behavior also truncates the linked target. Likewise, `chmodSync ...[truncated 3602 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Reject every pre-existing symbolic-link component and output object.** - Validate the `conversations` child with `lstatSync()` and reject it if it is a symbolic link or is not a real directory. - Validate every existing fixed-name output file with `lstatSync()` and reject symbolic links and non-regular files. - Revalidate the canonical path of every child against the canonical export directory. 2. **Use no-follow, exclusive filesystem operations.** - Open output files with `fs.openSync()` using `O_NOFOLLOW` where supported. - Prefer `O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW` for newly generated files. - For replacement of existing files, write to a securely created temporary file in the same validated directory, apply mode `0600`, and atomically rename it after validating the destination. - Do not rely on `writeFileSync(path, ...)` followed by `chmodSync(path, ...)`, because both path operations can follow links and are subject to races. 3. **Use directory-descriptor-relative operations where the platform permits.** - Open the validated export directory once. - Create and open children relative to that trusted directory descriptor. - Apply no-follow semantics so an attacker cannot replace ancestors between validation and use. 4. **Secure reused directories.** - Reject reused output directories unless ownership, type, and permissions are verified. - Ensure the output directory and all ancestors relevant to the export are owned by the current user and are not writable by group or others. - If a `conversations` directory already exists, verify it is a real directory, has the expected owner, and resolves strictly below the validated export root. 5. **Treat canonical containment checks as defense in depth.** - After opening or creating an object, verify its resolved location remains inside the canonical export root. - Do not use path-string canonicalization alone as the primary pr ...[truncated 468 chars]
Vulnerability Patterns
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The documentation makes strong safety claims about vetted private destinations, no access-token handling, and explicit confirmation controls, but the described bookmarklet behavior bypasses several of those protections by writing plaintext exports to the browser Downloads folder and potentially using a bearer-token fallback. That mismatch is dangerous because users and downstream agents may trust the safer description and expose highly sensitive conversation history to synced folders or broader browser-managed storage without realizing it.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
| `scripts/bookmarklet.js` (browser) | your browser's Downloads folder | one `chatgpt-export-<date>.json` |

Directories are `0700` and files `0600` — enforced on **every** run, not only when they are first
created, so reusing an export directory that is already world-readable cannot leave your
conversations exposed. You can choose the destination, but one that looks synced (Dropbox, Drive,
OneDrive, iCloud, Nextcloud…), sits inside a git repository, or resolves outside your home
directory is **refused, with no override**.
Confidence
91% confidence
Finding
The skill acknowledges that the bookmarklet writes a plaintext export directly to the browser Downloads folder, which is commonly synced, shared, or less tightly permissioned than the vetted private directory used by the relay path. That creates a real confidentiality risk for highly sensitive conversation history, especially because browser download locations typically do not get the same symlink, repo, or permission checks described elsewhere.

Static analysis

No suspicious patterns detected.