Back to skill

Security audit

Ghost CMS

Security checks for vulnerabilities and agentic risk

Overview

Review before installing: the Ghost CMS access is mostly disclosed and purpose-related, but the package includes a script that can overwrite a hard-coded Ghost post and a theme download path bug that can write outside the intended directory.

Install only if you are comfortable granting full Ghost Admin access. Use a staging site or a dedicated integration key, avoid running scripts/update-teapot.js, review all publish/delete/theme commands before execution, and be cautious with theme downloads until the output-path validation is 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

Warning
Location
scripts/theme-manager.js:143
Finding
Output Directory Containment Check Can Be Bypassed Using Sibling Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/theme-manager.js`, lines 143–177 **Vulnerability Type**: Improper path containment validation leading to an arbitrary file write outside the intended directory **Risk Level**: Medium ### Vulnerable Code ```js // Validate output path to prevent arbitrary file write function validateOutputPath(outputPath) { if (!outputPath) { throw new Error('Output path is required'); } // Resolve to absolute path const absolutePath = resolve(outputPath); // Get the directory part const outputDir = dirname(absolutePath); // Ensure output directory is within current working directory // This prevents writing to system directories like /etc, ~/.ssh, etc. const cwd = process.cwd(); const resolvedOutputDir = resolve(outputDir); if (!resolvedOutputDir.startsWith(cwd)) { throw new Error(`Invalid output path: must be within current directory (${cwd})`); } // Prevent path traversal in filename const filename = basename(absolutePath); if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) { throw new Error('Invalid filename: path traversal not allowed'); } // Require .zip extension const ext = extname(filename).toLowerCase(); if (ext !== '.zip') { throw new Error('Invalid filename: must have .zip extension'); } // Additional safety: filename must not be empty after removing extension const nameWithoutExt = basename(filename, ext); if (!nameWithoutExt || nameWithoutExt.length === 0) { throw new Error('Invalid filename: name cannot be empty'); } return absolutePath; } ``` The accepted path is subsequently used for writing: ```js const fileStream = createWriteStream(validatedPath); res.pipe(fileStream); ``` ### Technical Analysis The function attempts to restrict theme downloads to the current working directory. However, it checks containment using a raw string-prefix comparison: ```js resolvedOutputDir ...[truncated 1968 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use path-aware containment rather than string-prefix matching: ```js import { basename, resolve, dirname, extname, relative, isAbsolute } from 'path'; function validateOutputPath(outputPath) { if (!outputPath || typeof outputPath !== 'string') { throw new Error('Output path is required'); } const cwd = resolve(process.cwd()); const absolutePath = resolve(outputPath); const outputDir = dirname(absolutePath); const relativeDir = relative(cwd, outputDir); if ( relativeDir === '..' || relativeDir.startsWith(`..${path.sep}`) || isAbsolute(relativeDir) ) { throw new Error(`Output path must be within the current directory (${cwd})`); } const filename = basename(absolutePath); if (extname(filename).toLowerCase() !== '.zip') { throw new Error('Output filename must have a .zip extension'); } return absolutePath; } ``` Additional hardening should include: 1. Open downloads with exclusive creation, such as `createWriteStream(path, { flags: 'wx' })`, to prevent silent overwrites. 2. Require explicit confirmation before replacing an existing file when overwrite behavior is necessary. 3. Resolve and validate the real path of the destination directory to reduce symbolic-link bypass risk. 4. Create downloads in a dedicated application-owned directory with restrictive permissions. 5. Add regression tests for sibling-prefix paths, traversal paths, absolute paths, symbolic links, and existing-file overwrites. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/update-teapot.js:465
Finding
Bundled Script Unconditionally Overwrites a Hard-Coded Ghost Post<![CDATA[ ## Vulnerability Details **File Location**: `scripts/update-teapot.js`, lines 465–483 **Vulnerability Type**: Undocumented destructive operation using full-access credentials **Risk Level**: Medium ### Vulnerable Code ```js const postId = '697f9a9f3aafe60001180def'; const updateData = { posts: [{ lexical: JSON.stringify(lexicalContent), updated_at: new Date().toISOString() }] }; console.log('Updating post with Lexical content...\n'); ghostApi(`/posts/${postId}/`, 'PUT', updateData) .then(result => { console.log('Success! Updated post:'); console.log(JSON.stringify(result, null, 2)); }) .catch(err => { console.error('Error:', err.message); process.exit(1); }); ``` The script obtains full-access credentials earlier in the file: ```js const configDir = path.join(process.env.HOME, '.config', 'ghost'); const apiKey = fs.readFileSync(path.join(configDir, 'api_key'), 'utf8').trim(); const apiUrl = fs.readFileSync(path.join(configDir, 'api_url'), 'utf8').trim(); ``` ### Technical Analysis Executing `scripts/update-teapot.js` immediately reads the user's Ghost Admin API credentials, generates an authenticated JWT, and sends an HTTP `PUT` request to a fixed post identifier. The request replaces the post's Lexical content with the large article embedded in the script. The script does not require a user-supplied post ID, display the existing post before modification, support a dry-run mode, request confirmation, or create a backup. It is also not presented as one of the primary tools in the Skill documentation. Consequently, invocation of what appears to be a development or example utility can modify real Ghost data without an explicit target-selection step. Ghost Admin API integration keys are documented as unscoped and full-access. The request is therefore authorized to alter the target post whenever that identifier exists on the configured Ghost instance. ### Attack Path 1. A user, automation process, or Agent in ...[truncated 1459 chars]
Remediation
<![CDATA[ ## Remediation Suggestions The preferred remediation is to remove `scripts/update-teapot.js` from the distributed Skill because it appears to be a development artifact with a hard-coded target and content. If equivalent functionality must be retained: 1. Require the post ID as an explicit command-line argument. 2. Require content to come from an explicit local input file rather than embedding site-specific content. 3. Default to dry-run mode and show the target URL, post ID, title, status, and intended changes. 4. Retrieve the current post before updating it and save a local backup. 5. Require interactive confirmation for destructive execution. 6. For non-interactive automation, require an explicit flag such as `--confirm-update`. 7. Validate the post identifier using the same strict identifier validation used in `ghost-crud.js`. 8. Check HTTP status codes and stop on all non-success responses. 9. Avoid printing full post responses where they may contain sensitive drafts or metadata. 10. Document the utility and clearly label it as destructive. 11. Prefer integration with the reviewed `ghost-crud.js` update workflow rather than maintaining a separate credential and request implementation. A safer interface would resemble: ```bash node scripts/update-post.js \ --post-id 697f9a9f3aafe60001180def \ --content ./content.json \ --dry-run ``` Actual modification should occur only after a separate explicit confirmation flag or interactive approval. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (96)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description promises a comprehensive Ghost CMS integration with broad operational coverage across content management, members, newsletters, moderation, analytics, and all Ghost Admin API operations. The supplied code does something much narrower: it authenticates to Ghost Admin API using a locally stored key and URL, validates an endpoint string, and issues a single API request from the command line. Although the helper function technically accepts method and data parameters, the exposed CLI path only uses the endpoint argument and performs a GET request. There is no implemented logic for creating or publishing posts, scheduling content, managing members or newsletters, moderating comments, or retrieving analytics-specific summaries. Therefore the description materially overstates the actual behavior and capabilities of this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The code accurately supports part of the description related to blog content creation, publishing, scheduling, and post management. However, the declared description significantly overstates scope: this script only interacts with Ghost post endpoints and provides CRUD plus publish/schedule actions for posts. There is no implementation for newsletters, members, subscription tiers, comments, analytics, or the full range of Ghost Admin API operations. Reading local API credentials and generating JWTs are supporting details, not mismatches. The material mismatch is that the declared purpose describes a comprehensive Ghost integration, while the actual behavior is a narrow post-management utility.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description presents this skill as a full-featured Ghost CMS integration supporting essentially all admin workflows. The supplied code chunk is much narrower: it is a content-formatting helper that builds Ghost Lexical documents and card payloads, plus a small CLI for converting text/JSON into Lexical JSON. While this supports Ghost content creation indirectly, it does not interact with Ghost APIs, authenticate, manage posts remotely, handle members, moderate comments, or retrieve analytics. Therefore the declared description materially overstates the implemented behavior and primary purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The description substantially overstates the skill's scope. The code is not a comprehensive Ghost CMS integration; it is a specialized snippet-extraction script. Its only Ghost interaction is reading a specific post via the Admin API. It does not create or manage posts, newsletters, members, comments, or analytics. Additionally, it writes extracted snippets to local JSON files, which is a concrete behavior absent from the declared purpose. This is a clear description-behavior mismatch in primary purpose and supported capabilities.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The description presents a broad, comprehensive Ghost CMS skill for managing content, newsletters, members, comments, and analytics across the Ghost Admin API. The supplied code does something much narrower: it is a standalone CLI script dedicated to theme management. Its actual API usage is limited to /themes endpoints for listing, uploading, activating, deleting, and downloading themes, plus writing downloaded ZIP files locally. This is a materially different primary purpose from the declared description, and most advertised capabilities are absent. Therefore the description does not accurately represent the code.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This is a clear description-behavior mismatch. The declared purpose describes a broad Ghost CMS integration focused on Admin API functionality for managing content, members, comments, newsletters, and analytics. The supplied code instead implements a command-line theme validator for local theme directories/ZIPs using gscan. Its inputs are filesystem paths and CLI flags; its outputs are console messages/JSON and process exit codes. There is no network communication, no authentication, no Ghost Admin API usage, and no CMS management capabilities. Theme validation is Ghost-related, but it is materially different from the declared primary purpose and omitted from the description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description describes a broad, general-purpose Ghost CMS skill with support for many Admin API domains. The supplied code is much narrower: it is a one-off script that authenticates to Ghost and updates a single hardcoded post using a PUT request. While it does interact with the Ghost Admin API, it does not expose or implement the wide range of capabilities claimed in the description. This is a material description-to-behavior mismatch due to overstating scope and functionality.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description says this skill is a broad Ghost CMS integration supporting creation, publishing, scheduling, management of content, newsletters, members, comments, analytics, and all Ghost Admin API operations. The supplied code does something much narrower and materially different: it manages local JSON snippet files on disk and can inject snippet cards into a Lexical document structure. Its operations are local file I/O (load/save/list/delete/copy snippets) plus content array insertion. There are no network calls, no Ghost API usage, no authentication, and no logic for posts, newsletters, members, comments, or analytics. This is a clear description-behavior mismatch because the primary purpose and capabilities are substantially different from what is declared.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The code chunk does not interact with Ghost CMS, network APIs, blog content, members, comments, or analytics at all. Its actual purpose is managing local snippet-library storage and configuration on disk, including reading environment variables, creating directories, saving a config JSON file, and migrating local snippet files. This is a materially different primary purpose from the declared comprehensive Ghost CMS integration, so the description does not accurately represent the code.

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/snippet-extractor.js my-snippets-post
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Tool Parameter Abuse

High
Category
Tool Misuse
Content
**Content Management:**
- **POST /posts/** - Create draft post
- **PUT /posts/:id/** - Update post (can publish if status changed!)
- **DELETE /posts/:id/** - Delete post permanently
- **POST /pages/** - Create page
- **PUT /pages/:id/** - Update page (can publish!)
- **DELETE /pages/:id/** - Delete page permanently
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
- **DELETE /posts/:id/** - Delete post permanently
- **POST /pages/** - Create page
- **PUT /pages/:id/** - Update page (can publish!)
- **DELETE /pages/:id/** - Delete page permanently

**🚨 CRITICAL:** Setting `status: "published"` makes content **immediately public**
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
**Organization:**
- **POST /tags/** - Create tag
- **PUT /tags/:id/** - Update tag
- **DELETE /tags/:id/** - Delete tag (affects all tagged posts)

**Members & Subscriptions:**
- **POST /members/** - Add member
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
**Members & Subscriptions:**
- **POST /members/** - Add member
- **PUT /members/:id/** - Update member (can change subscription status)
- **DELETE /members/:id/** - Delete member permanently
- **POST /tiers/** - Create pricing tier
- **PUT /tiers/:id/** - Update tier pricing/benefits
- **DELETE /tiers/:id/** - Archive tier
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Static analysis

Detected: suspicious.generated_source_template_injection

User-controlled placeholder is embedded directly into generated source code.

Critical
Code
suspicious.generated_source_template_injection
Location
references/analytics.md:240