Back to skill

Security audit

Book-PDF:书籍级PDF手册生成器

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real PDF-book workflow, but its local scripts have unsafe input handling and mutable external dependencies that users should review before installing.

Install only if you are comfortable with a Chinese-localized, shell-based local PDF workflow. Run it in a dedicated project directory, avoid untrusted titles/metadata/update messages, pin Playwright with a lockfile before use, and consider removing remote font imports or blocking network access during PDF rendering.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
templates/update.sh:49
Finding
Arbitrary JavaScript Execution Through the Changelog Message<![CDATA[ ## Vulnerability Details **File Location**: `templates/update.sh`, lines 13 and 49-60 **Vulnerability Type**: User-controlled data interpolated into executable JavaScript **Risk Level**: High ### Vulnerable Code ```bash MESSAGE="${2:-无描述}" # Write CHANGELOG (non-build updates only) if [ "$BUMP_TYPE" != "build" ]; then node -e " const fs = require('fs'); let log = fs.readFileSync('$CHANGELOG', 'utf-8'); const entry = '\n## [$NEW_VERSION] $TODAY — $MESSAGE\n\n- $MESSAGE\n'; const firstEntry = log.indexOf('\n## ['); if (firstEntry !== -1) { log = log.slice(0, firstEntry) + entry + log.slice(firstEntry); } else { log += entry; } fs.writeFileSync('$CHANGELOG', log); " echo "📝 CHANGELOG 已更新" fi ``` ### Technical Analysis The second command-line argument is assigned to `MESSAGE` and then interpolated directly into JavaScript source passed to `node -e`. It is placed inside single-quoted JavaScript string literals without escaping quotes, backslashes, line terminators, or other JavaScript syntax. An attacker who can influence the update message can terminate the JavaScript string and inject additional statements. The injected code executes in Node.js with the same operating-system privileges and environment as the user running `update.sh`. The message is inserted twice, which may require a payload designed to preserve valid syntax at both insertion points, but this does not prevent exploitation. Quotes, comments, and embedded line breaks can be combined to produce valid injected JavaScript. ### Attack Path 1. The attacker supplies or persuades a user to supply a crafted second argument to `update.sh`. 2. The script stores the argument in `MESSAGE`. 3. Shell parameter expansion inserts the message into the source code passed to `node -e`. 4. The crafted content terminates the intended JavaScript string and introduces attacker-controlled statements. 5. Node.js executes those statements as the invoking user. 6. The injected code can use built-in ...[truncated 797 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Never construct executable JavaScript source by interpolating user-controlled data. 1. Pass the message as a positional process argument or through standard input. 2. Read it using `process.argv` and use it only as a data value. 3. Pass filenames and version values as arguments as well, rather than interpolating them into JavaScript. 4. Validate version numbers against a strict semantic-version expression. 5. Add tests using messages containing quotes, backslashes, newlines, and JavaScript comment characters. A safer pattern is: ```bash node - "$VERSION_FILE" "$CHANGELOG" "$NEW_VERSION" "$TODAY" "$MESSAGE" <<'NODE' const fs = require('fs'); const [, , versionFile, changelog, version, today, message] = process.argv; let log = fs.readFileSync(changelog, 'utf8'); const entry = `\n## [${version}] ${today} — ${message}\n\n- ${message}\n`; const firstEntry = log.indexOf('\n## ['); if (firstEntry !== -1) { log = log.slice(0, firstEntry) + entry + log.slice(firstEntry); } else { log += entry; } fs.writeFileSync(changelog, log); NODE ``` This keeps the message outside the JavaScript source and prevents it from changing the program’s syntax. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
templates/build.js:12
Finding
Arbitrary File Write Through Path Traversal in the Book Title<![CDATA[ ## Vulnerability Details **File Location**: `templates/build.js`, lines 12-17 and 146 **Vulnerability Type**: Path traversal caused by an unsanitized filename component **Risk Level**: High Additional affected locations include `templates/build-pdf.js`, lines 13-16 and 29-30, and `scripts/init-project.sh`, lines 14 and 49-65. ### Vulnerable Code ```js const FRAGMENTS_DIR = path.join(__dirname, 'fragments'); const OUTPUT_DIR = path.join(__dirname, 'output'); const CSS_FILE = path.join(__dirname, 'styles.css'); const VERSION_FILE = path.join(__dirname, 'version.json'); const versionData = JSON.parse(fs.readFileSync(VERSION_FILE, 'utf-8')); const OUTPUT_FILE = path.join(OUTPUT_DIR, `${versionData.title}-v${versionData.version}.html`); ``` The resulting path is later written without containment validation: ```js fs.writeFileSync(OUTPUT_FILE, html, 'utf-8'); ``` The PDF builder uses the same unsafe title: ```js const versionData = JSON.parse(fs.readFileSync(path.join(__dirname, 'version.json'), 'utf-8')); const HTML_FILE = path.join(__dirname, 'output', `${versionData.title}-v${versionData.version}.html`); const PDF_FILE = path.join(__dirname, 'output', `${versionData.title}-v${versionData.version}.pdf`); ``` ### Technical Analysis The `title` value from `version.json` is incorporated directly into output filenames. No validation rejects directory separators, `..` path components, control characters, or platform-specific path syntax. A title containing traversal sequences such as `../` causes `path.join()` to normalize the generated path outside the intended `output` directory. The HTML builder then writes content to that path. The PDF builder similarly reads and writes using paths derived from the title. The title originates from the initialization script’s second argument and can also be modified directly in `version.json`. Consequently, a malicious or compromised project configuration can redirect build output. ### Attack Path 1. An attacker ...[truncated 1345 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not use the display title directly as a filename. 2. Generate a separate filename-safe slug that permits only a conservative character set, such as letters, digits, hyphens, and underscores. 3. Reject titles or slugs containing `/`, `\`, `..`, NUL characters, or control characters. 4. Resolve the final path and verify that it remains beneath the intended output directory. 5. Apply the same validation to HTML output, HTML input, PDF output, and version archive paths. 6. Escape the title correctly before writing it into `version.json` during initialization. Example containment check: ```js function safeOutputPath(outputDir, title, version, extension) { const slug = title .normalize('NFKC') .replace(/[^a-zA-Z0-9_-]+/g, '-') .replace(/^-+|-+$/g, ''); if (!slug) { throw new Error('The title does not produce a valid output filename'); } const base = path.resolve(outputDir); const candidate = path.resolve(base, `${slug}-v${version}.${extension}`); if (!candidate.startsWith(base + path.sep)) { throw new Error('Output path escapes the output directory'); } return candidate; } ``` Keep the original title solely as display metadata and use the validated slug for filesystem operations. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
templates/build.js:38
Finding
Unescaped Metadata Injection Into the Playwright Rendering Context<![CDATA[ ## Vulnerability Details **File Location**: `templates/build.js`, lines 38-52 and 61-74 **Vulnerability Type**: HTML and active-content injection **Risk Level**: Medium Related rendering locations are `templates/fragments/00-cover.html`, lines 13-23; `templates/fragments/99-backpage.html`, lines 1-22; and `templates/build-pdf.js`, lines 20-27. ### Vulnerable Code ```js const replace = (template, data) => { let result = template; // Process conditional blocks {{#KEY}}...{{/KEY}} result = result.replace(/\{\{#(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, (match, key, inner) => { const value = data[key]; if (!value) return ''; // Replace internal {{KEY}} return inner.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value); }); // Process simple placeholders {{KEY}} Object.keys(data).forEach(key => { result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), data[key] || ''); }); return result; }; ``` The replacement data is taken directly from project metadata: ```js const backpageData = { BOOK_TITLE: versionData.title || '', BOOK_SUBTITLE: author.subtitle || '', AUTHOR_NAME: author.name || '', AUTHOR_BIO: author.bio || '', AUTHOR_QR_IMAGE: author.qrImage || '', AUTHOR_LINK_URL: author.linkUrl || '', AUTHOR_LINK_TEXT: author.linkText || '', AUTHOR_SOCIAL: author.social || '', VERSION: versionData.version || '1.0.0', YEAR: new Date().getFullYear() }; ``` For example, the values are placed directly into HTML element and attribute contexts: ```html <div class="back-page-info"> <strong>{{AUTHOR_NAME}}</strong><br> {{AUTHOR_BIO}} </div> <a href="{{AUTHOR_LINK_URL}}" class="back-page-link">{{AUTHOR_LINK_TEXT}}</a> ``` The generated document is then opened by Playwright: ```js const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto(`file://${HTML_FILE}`, { waitUntil: 'networkidle', timeout: 60000 }); ``` ### Technical Analysis The custom template replace ...[truncated 2317 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. HTML-escape every field intended to contain text. 2. Apply attribute-specific encoding to values placed inside HTML attributes. 3. Validate URLs using the `URL` API and allow only explicitly required schemes, normally `https:` and optionally `http:`. 4. For local images, resolve paths against an approved asset directory and enforce directory containment. 5. Treat trusted HTML as a separate data type. Do not use ordinary string fields as raw HTML. 6. Sanitize any intentionally supported HTML with a maintained allowlist-based sanitizer. 7. Disable JavaScript during PDF generation if the document does not require it. 8. Block unnecessary network requests during rendering. Example text escaping: ```js function escapeHtml(value) { return String(value) .replaceAll('&', '&amp;') .replaceAll('<', '&lt;') .replaceAll('>', '&gt;') .replaceAll('"', '&quot;') .replaceAll("'", '&#39;'); } ``` Example URL validation: ```js function validateWebUrl(value) { if (!value) return ''; const parsed = new URL(value); if (!['https:', 'http:'].includes(parsed.protocol)) { throw new Error(`Unsupported URL scheme: ${parsed.protocol}`); } return parsed.href; } ``` Playwright can also disable script execution and restrict requests: ```js const context = await browser.newContext({ javaScriptEnabled: false }); const page = await context.newPage(); await page.route('**/*', route => { const url = route.request().url(); if (url.startsWith('file://')) { return route.continue(); } return route.abort(); }); ``` If remote fonts are required, download and pin them as local assets instead of permitting unrestricted rendering-time network access. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:17
Finding
Unpinned Playwright Installation Creates Supply-Chain Exposure<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 17 **Vulnerability Type**: Mutable and unpinned third-party executable dependency **Risk Level**: Medium The same installation commands are repeated in `scripts/init-project.sh`, lines 129-133, and `templates/build-pdf.js`, line 6. ### Vulnerable Code ```markdown - Node.js >= 16 - Playwright: `npm install playwright && npx playwright install chromium` ``` The initialization script recommends the same commands: ```bash echo " ⚠️ 未找到 Playwright — 请运行:" echo " npm install playwright" echo " npx playwright install chromium" ``` ### Technical Analysis The documented installation procedure does not specify an exact Playwright version and the project contains no reviewed lockfile or integrity-pinned dependency manifest. Running `npm install playwright` therefore resolves the package version from mutable registry metadata at installation time. The subsequent `npx playwright install chromium` command executes the installed Playwright CLI and downloads a browser build selected by that package version. Consequently, the effective dependency and browser payload can differ between installations even when the audited project files remain unchanged. This is a supply-chain hardening weakness rather than evidence that the current Playwright package is malicious. The risk arises because future, compromised, or unexpectedly incompatible package content would be installed and executed without being represented in the audited repository. ### Attack Path 1. A user follows the project’s dependency installation instructions. 2. npm resolves the current version of `playwright` rather than an exact reviewed version. 3. npm downloads the package and processes any applicable package installation behavior. 4. The user runs the Playwright CLI through `npx`. 5. The CLI downloads and installs a Chromium build selected by the resolved package. 6. If the registry package, package account, distribution ...[truncated 683 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a `package.json` that specifies an exact reviewed Playwright version rather than a range. 2. Commit a generated `package-lock.json` containing integrity hashes. 3. Require `npm ci` for reproducible installation. 4. Avoid relying on an unqualified `npx` command that could resolve unexpected packages. 5. Run the locally installed CLI explicitly, for example `npx --no-install playwright install chromium`. 6. Record and verify the expected Playwright browser revision. 7. Use automated dependency scanning and controlled update reviews. 8. Perform dependency installation and rendering in an isolated, least-privileged environment. Example: ```json { "private": true, "dependencies": { "playwright": "1.55.0" } } ``` Installation instructions should then use: ```bash npm ci npx --no-install playwright install chromium ``` The exact version shown should be replaced with the organization’s reviewed and supported release and updated through a controlled process. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
描述强调的是一个端到端的“从调研到规划到多Agent写作再到合并构建和PDF生成”的完整书籍级手册生产能力。但实际代码片段只负责初始化一个本地项目骨架,包括创建 fragments/output/versions/research 目录、复制 build/build-pdf/update 等模板、写入版本与计划模板,并检查 Playwright 依赖。它没有执行调研、没有调用任何多Agent机制、没有生成内容、没有合并构建HTML或渲染PDF,甚至输出中明确要求用户后续手动编辑 PROJECT.md、build.js 和 fragments 内容。因此当前代码行为与声明的主要能力存在实质性不一致;实际更像“书籍PDF项目脚手架初始化器”,而不是完整的书籍级PDF手册生成技能。

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
声明描述的是一个端到端的“从调研到成书”的完整书籍级 PDF 手册生成技能,而提供的代码片段只负责将已存在的合并后 HTML 文件转为 PDF。代码明确写明前置条件是“先运行 node build.js 生成 HTML”,说明它不承担调研、写作、规划、合并生成等核心职责。虽然声明中提到最终会使用 Playwright 渲染 PDF,这与代码一致,但该代码的实际主功能明显窄于声明的整体能力范围,且更像是独立的 PDF 渲染子模块。因此,描述与该代码片段的实际行为存在实质性不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
声明描述的是一个端到端的“从零到书籍级PDF手册”的复杂生成系统,核心能力包括研究、写作编排、版本管理和Playwright导出PDF。实际代码块只覆盖其中很小的一部分:基于version.json和固定片段顺序构建HTML,并做少量模板变量替换。这属于支持性的构建步骤,而不是声明中的主要功能。尤其是代码没有任何网络检索、内容生成、多代理协作、任务编排或PDF输出逻辑,因此实际行为与声明的主要目的存在明显不一致。

Vague Triggers

High
Confidence
97% confidence
Finding
The trigger phrases include very broad everyday language such as '做一本书' and similar generic requests, which can cause accidental activation in unrelated contexts. Because this skill includes shell usage, initialization scripts, dependency installation, and file-generation workflows, overbroad triggering increases the chance of unintended tool execution and workspace modifications.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill instructs use of shell commands (`bash`, `node`, `npx playwright`) but declares no explicit tool scope or permission boundary. In an agent environment, this can cause the skill to be invoked with broader shell capability than intended, increasing the risk of unintended command execution or file-system changes if the workflow is triggered on untrusted input.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
Using `npx playwright` without pinning a version introduces supply-chain and reproducibility risk because the resolved package version can change over time. If a compromised or unexpected version is fetched, the skill may execute different code than reviewed, including during browser install steps.

Natural-Language Policy Violations

Medium
Confidence
86% confidence
Finding
The file title and core instruction text are written as a required reference for a writing agent, and the guidance assumes Chinese output conventions while embedding English only as fixed subtitle fields. There is no indication that the user can opt into another language or locale, which can violate language-choice policy for general-purpose skills.

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.

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.

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.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The template hard-codes Chinese placeholder and metadata text such as the title, subtitle, and document fields, which imposes a specific language by default. There is no accompanying note that this fragment is intended only for a Chinese-language or region-specific workflow, nor any indication that users can select another locale.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The natural-language content embedded in comments and generated labels such as '核心建议', '注意', '推荐', and '不推荐' indicates the skill is designed to produce Chinese-language presentation by default. The policy requires not forcing a specific language or locale without user opt-in, and no opt-in or alternative locale path is documented in this file.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This shell script includes its usage instructions and operational messages in Chinese only, which imposes a specific language on users without any opt-in or stated regional justification. The policy specifically calls for flagging language or locale constraints when the skill forces a language without user choice.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The script's comments, usage strings, prompts, and status messages are all written in Chinese, which effectively fixes the skill's user-facing language. Under the policy, locale or language constraints should either be optional for the user or clearly documented as justified; this file provides neither.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The file's header comments and runtime console messages are written in Chinese, which imposes a specific language on users without any opt-in or documented locale constraint. This matches the policy category for language or locale restrictions expressed in natural language.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The script formats build time using the fixed locale 'zh-CN' and timezone 'Asia/Shanghai'. This is a natural-language/locale policy concern because it forces a specific locale choice without offering user selection or explaining why that locale is required.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The alt text uses Chinese ("二维码") directly in the template, which imposes a specific language in user-facing output. The policy allows fixed locale behavior only when it is opt-in or clearly documented as region-specific, which is not evident in this file.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
This line contains a full Chinese disclaimer presented to end users, but the template provides no indication that Chinese is optional or that the skill is intentionally limited to that locale. That creates a natural-language locale policy concern under the language-choice rule.

Context-Inappropriate Capability

Low
Confidence
93% confidence
Finding
The stylesheet imports Google Fonts from a remote origin, which causes network access during rendering and leaks metadata such as IP address, timing, and document-generation behavior to a third party. In a local HTML/PDF generation skill, this is unnecessary for core functionality and weakens privacy, reproducibility, and offline safety.

Missing User Warnings

Low
Confidence
90% confidence
Finding
Using an external font service silently transmits requests to Google without disclosure, which creates an avoidable privacy issue and may violate strict offline or regulated environments. Because this is a PDF-rendering template rather than a web app feature, the third-party dependency is not justified by the immediate task.

Static analysis

No suspicious patterns detected.