Back to skill

Security audit

md-web

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to do its stated Markdown-to-public-link job, but it needs Review because it stores bucket credentials in plaintext and can make bucket-wide lifecycle changes.

Install only if you are comfortable publishing selected Markdown publicly to your own bucket. Use a dedicated bucket and least-privilege object tokens where possible, avoid expire_days: 0 unless you intend to clear the bucket lifecycle configuration, protect ~/.md-web/config.json, and host the viewer on a dedicated domain with no sensitive cookies or apps sharing that origin.

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

Warning
Location
docsify-server/index.html:75
Finding

Cross-Origin Markdown Route Can Inject Active HTML into the Viewer Origin

Content
View full analysis

Vulnerability Details

File Location: docsify-server/index.html:75-80; docsify-server/assets/docsify.min.js:1
Vulnerability Type: Cross-origin active-content injection
Risk Level: Medium

Relevant code snippets:

From docsify-server/index.html:75-80:

javascript
search: {
  maxAge: 0,
  paths: [location.hash.replace(/^#/, "").split("?")[0] || "/"],
  placeholder: "Search",
  noData: "No results found",

Relevant portions of the bundled Docsify implementation in docsify-server/assets/docsify.min.js:1:

javascript
function R(e){return/(:|(\/{2}))/g.test(e)}
javascript
e=R(e)?e:q(a,e)
javascript
function X(t,e,n){
  ...
  r=new XMLHttpRequest,
  ...
  r.open("GET",t),
  ...
  r.send()
}
javascript
sanitize:!1
javascript
function w(e,n){
  return e=v.createElement(e),n&&(e.innerHTML=n),e
}

Technical Analysis

The page derives a Docsify document path from the URL fragment. The bundled router recognizes paths containing a scheme or protocol-relative syntax as remote paths and preserves them rather than constraining them to the deployed bucket.

Docsify subsequently retrieves that path with XMLHttpRequest. If the remote server permits cross-origin reads through CORS, an unrelated content provider can supply the Markdown document rendered by the viewer.

The bundled Markdown configuration has sanitization disabled (sanitize:!1) and uses HTML DOM insertion. Consequently, active HTML accepted by the Markdown renderer, including event-handler attributes, can execute in the origin of the deployed viewer. This is distinct from ordinary publication of user-selected Markdown: the triggering URL can identify content hosted and controlled by an independent attacker.

No evidence indicates that the project author intentionally introduced this behavior for attack purposes. It is therefore classified as an exploitable c ...[truncated 1601 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject absolute and protocol-relative Docsify routes before document retrieval. Permit only normalized, same-origin paths.
  2. Restrict document routes to the expected /md-web/ object prefix and reject schemes, backslashes, encoded traversal sequences, and paths outside that prefix.
  3. Enable robust HTML sanitization with an allowlist-based sanitizer before inserting rendered Markdown into the DOM.
  4. Prefer disabling raw HTML in Markdown if the feature is not required.
  5. Apply a restrictive Content Security Policy that disallows inline scripts and event handlers, for example by omitting unsafe-inline from script-src.
  6. Add regression tests covering absolute URLs, protocol-relative URLs, encoded route variants, raw HTML, event-handler attributes, and dangerous URL schemes.
  7. Consider isolating the document viewer on a dedicated origin that does not host authenticated applications or sensitive browser storage.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • 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 (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose says this skill should publish markdown as a public web page/link via S3. The supplied code does something entirely different: it parses rendered markdown text, counts Chinese/English words, estimates reading time, and inserts a small UI element into the page. There is no network access, no bucket interaction, no upload logic, and no link-sharing functionality. This is a clear description-behavior mismatch with a materially different primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill is for turning markdown into a shareable web page or public link via upload to an S3 bucket. The provided code does none of that. It is a minified Docsify search module that parses markdown content, builds a search index, caches it in localStorage, fetches documentation pages, and renders a search box/results UI in the sidebar. There is no evidence of S3 access, upload logic, link creation, or public sharing. This is a clear description-behavior mismatch with a materially different primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill is for publishing markdown as a shareable public web page by uploading to S3. The supplied code does nothing related to S3, networking, storage, public link generation, or publishing. Instead, it is purely front-end JavaScript for Docsify that creates and updates a table of contents sidebar from existing page headings. This is a materially different primary purpose, so the description does not accurately represent the code.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says the skill should publish markdown as a public webpage/link by uploading to S3. The supplied code does something entirely different: it enhances images on a Docsify-rendered page with zoom-in/overlay behavior. It queries image elements in '.markdown-section', injects CSS, attaches click/keyup/scroll/resize listeners, and manages open/close state for enlarged images. There is no network logic, no S3 API usage, no URL generation, and no markdown publishing pipeline. This is a clear description-behavior mismatch with a materially different primary purpose.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

Bucket lifecycle administration is not necessary for the core purpose of converting markdown into a shareable link, yet the skill requests and uses that capability. This violates least privilege and increases blast radius: compromise or misuse of the skill can alter bucket retention policy, impacting availability and governance beyond the single uploaded document.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code issues PUT/DELETE requests to the bucket lifecycle configuration, which is a bucket-wide administrative control rather than an object-scoped upload action. If misused or run with high-privilege credentials, it can change retention behavior for objects under the configured prefix and in the DELETE case remove the existing lifecycle policy entirely.

Content

No source excerpt is available for this finding.

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · README.md (reported line 20)May include surrounding context.

text

Or install manually — clone this repo and copy (or symlink) `skills/md-web/` into your agent's
skills directory: `~/.claude/skills/` for Claude Code, `~/.agents/skills/` for the cross-runtime
convention Codex and Gemini CLI also read, or wherever your agent keeps them.

## Quick Start

Session Persistence

Medium
Category
Rogue Agent
Confidence
95% confidence
Finding

The README explicitly states that S3 credentials are stored in plaintext in ~/.md-web/config.json, creating a real secret-at-rest exposure. Any local compromise, accidental backup leakage, or inadvertent file sharing could expose bucket write credentials and enable unauthorized publication, tampering, or deletion of hosted content; the documented need for Admin Read & Write in some cases increases the blast radius.

Content

Scanner excerpt · README.md (reported line 93)May include surrounding context.

md
- **Anything you upload is publicly accessible** at the returned URL — never upload secrets, API keys, PII, or other sensitive content.
- Your S3 credentials are stored **in plaintext** in `~/.md-web/config.json`. Keep this file private — don't commit it or share it.
- **Use a dedicated bucket.** Uploads are namespaced under the `md-web/` key prefix and the auto-expiry rule is scoped to it, so the skill only ever expires its own files. One exception, and it is why the dedicated bucket matters: `expire_days: 0` does not remove just this skill's rule, it deletes the bucket's **entire** lifecycle configuration, including rules you set yourself.
- **Automatic expiry needs an Admin Read & Write token.** With an Object-Read-&-Write-only token the upload still succeeds and the rule is simply not set — a warning goes by in the output, whichever way `expire_days` is set. Set the rule in the dashboard instead if you keep the narrower token.

## File Structure

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
85% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · README.md (reported line 103)May include surrounding context.

md
├── upload.js             # Upload script (pure Node.js, zero dependencies)
├── README.md             # This document
├── README.zh.md          # Chinese documentation
└── docsify-server/       # Docsify server files (auto-deployed on first upload)
    ├── index.html
    ├── README.md
    ├── .nojekyll

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The trigger phrases are broad everyday expressions like '做成网页', '预览 README.md', and '分享为链接', which can overlap with ordinary conversation and cause the skill to activate unexpectedly. In this skill's context, accidental activation is more dangerous because its core action publishes content to a public URL, creating a real risk of unintended data disclosure.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill explicitly instructs the agent to perform networked uploads to an S3-compatible endpoint, yet the manifest does not declare any tool scope or allowed-tools restriction. Missing capability scoping weakens least-privilege controls and makes it harder for a platform to enforce or review the skill's network behavior.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
82% confidence
Finding

The skill instructs the agent to write markdown content to a temporary file before upload, which creates local persistence of potentially sensitive user content outside the conversation. Temp directories may be readable by other local processes or left behind after failure, increasing exposure of data that the user only intended to publish remotely or transiently process.

Content

Scanner excerpt · SKILL.md (reported line 42)May include surrounding context.

md
### Step 2: Prepare the markdown file

Either use an existing `.md` file, or write the content to a temporary file. Choose the temp path based on the current platform (e.g., `/tmp/` on Linux/macOS, `%TEMP%\` on Windows — note Git Bash does not expand `%TEMP%`, so use `$TEMP` there). Use whichever path works in the current shell environment.

**Only the `.md` file itself is uploaded.** Relative image references (`![](diagram.png)`) will be broken on the page — tell the user to use absolute image URLs for documents that need pictures.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
90% confidence
Finding

The skill can automatically modify bucket lifecycle settings and explicitly notes that expire_days: 0 may delete the bucket's entire lifecycle configuration. Even though this is documented, allowing an agent-driven workflow to alter destructive retention settings on shared infrastructure can cause unintended data retention loss or deletion policy changes beyond the uploaded files.

Content

Scanner excerpt · SKILL.md (reported line 78)May include surrounding context.

  • public_url: public access URL. If the user has a custom domain bound to the bucket, use that (e.g., https://docs.example.com); otherwise use the default R2.dev URL (e.g., https://pub-XXXX.r2.dev). Recommend custom domain for production use — R2.dev URLs have rate limits.
  1. Ask about optional settings:
    • region: S3 region. Use auto for Cloudflare R2, or the actual region for AWS S3 (e.g., us-east-1). Default is auto.
    • expire_days: how many days before uploaded markdown files are automatically deleted from the bucket. Default is 30. Set to 0 to keep files forever — note that this deletes the bucket's entire lifecycle configuration, including rules you set yourself, which is why a dedicated bucket is recommended. The script sets an S3 lifecycle rule scoped to the md-web/ key prefix, so only this skill's own uploads are affected — Docsify server files and any other objects in the bucket are never touched. Note: this requires the API token to have Admin Read & Write permission (not just Object Read & Write). If the token lacks permission, the script will warn but still upload normally — the user can set the lifecycle rule manually in the Cloudflare Dashboard instead.
  2. Write the config to ~/.md-web/config.json (create the ~/.md-web/ directory if it doesn't exist). Use the user's home directory ($HOME on Unix, %USERPROFILE% on Windows):
json

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill claims to upload markdown as a shareable page, but it also deploys and updates shared bucket-hosted web assets such as index.html and bundled server files. That expands scope from per-document upload to modifying shared hosting state, which can affect all published pages and create integrity risks if the skill is triggered unexpectedly or with altered bundled assets.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

该文件整体内容均以中文编写,未在正文中说明这是面向中文用户的本地化文档,也未提示用户可选择其他语言版本。按照语言/区域策略,若技能说明强制单一语言而无用户选择,可能构成自然语言层面的策略问题。

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The code sets language:"english" in defaultOptions, which imposes a specific language/locale by default. Although Chinese is also supported later in the code, the file itself defaults to English without any user-facing opt-in or choice mechanism.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.