Back to skill

Security audit

calibre-metadata-apply

Security checks for vulnerabilities and agentic risk

Overview

The skill fits its Calibre metadata-editing purpose, but its credential handling, automatic host probing, and unsanitized persistent HTML comments need careful review before installation.

Install only if you are comfortable reviewing each dry-run carefully and using it in a trusted workspace. Prefer explicit Calibre endpoint settings, avoid workspace .env files from untrusted projects, use a limited Calibre account/password, and do not feed untrusted analysis JSON or raw comments_html into apply mode.

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/calibredb_apply.mjs:214
Finding
Calibre Credentials Exposed Through Process Arguments and Automatic Fallback-Host Probing<![CDATA[ ## Vulnerability Details **File Location**: `scripts/calibredb_apply.mjs:214-239, 286-290, 320-338` **Vulnerability Type**: Credential exposure and excessive network trust **Risk Level**: Medium ### Vulnerable Code ```js const extraHosts = [ ...splitList(args['server-hosts']), ...SERVER_HOSTS_ENV_KEYS.flatMap(k => splitList(process.env[k])), ...discoverWslHostCandidates(), 'host.docker.internal', ] .map(normalizeHostToken) .filter(Boolean); for (const c of baseCandidates) { const normalized = normalizeWithLibrary(c.value, libraryId); expanded.push({ source: c.source, value: normalized }); if (!isHttpUrl(normalized)) continue; const p = parseHttpUrlParts(normalized); if (!p) continue; const curHost = normalizeHostToken(p.host); for (const h of extraHosts) { if (!h || h === curHost) continue; expanded.push({ source: `${c.source}:host=${h}`, value: replaceHttpHost(normalized, h) }); } } ``` ```js function probeRemoteLibrary(withLibrary, auth) { const cmd = [ 'calibredb', 'list', '--for-machine', '--fields', 'id', '--limit', '1', '--with-library', withLibrary ]; if (auth.username) cmd.push('--username', String(auth.username)); if (auth.password) cmd.push('--password', String(auth.password)); return run(cmd); } ``` ```js function resolveAuth(args) { const envUser = (process.env.CALIBRE_USERNAME || '').trim(); const username = args.username ? String(args.username) : (envUser || null); let password = args.password ? String(args.password) : ''; const passwordEnv = args['password-env'] ? String(args['password-env']) : 'CALIBRE_PASSWORD'; if (!password && passwordEnv) { password = process.env[passwordEnv] || ''; } return { username, password, usedPasswordEnv: passwordEnv }; } function commonArgs(args, auth) { const r = ['--with-library', String(args['with-library'])]; if (auth.username) r.push('--username', String(auth.username)); if (auth.pas ...[truncated 2951 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove implicit fallback candidates such as WSL resolver addresses and `host.docker.internal`. 2. Use only explicitly configured hosts by default. If fallback is needed, require an explicit allowlist and separate user approval. 3. Bind authentication credentials to an exact scheme, host, port, and library identifier. Do not reuse them after automatic host substitution. 4. Require HTTPS for non-loopback remote servers and validate the server certificate. 5. Remove support for `--password <plain>` from the wrapper interface. 6. Avoid placing plaintext secrets in process arguments. Use a protected file descriptor, standard input, an OS credential facility, or another secret-passing mechanism supported by `calibredb`. 7. If no secure credential-passing mechanism is available, document the process-list exposure and isolate execution under a dedicated operating-system account. 8. Keep redaction for logs and errors, but do not treat redaction as protection for the live argument vector. 9. Add tests confirming that authentication is never attempted against a host not present in an explicit allowlist. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/calibredb_apply.mjs:373
Finding
Stored HTML Injection Through Unsanitized Analysis and Comments Input<![CDATA[ ## Vulnerability Details **File Location**: `scripts/calibredb_apply.mjs:373-410, 425-434` **Vulnerability Type**: Stored HTML injection **Risk Level**: Medium ### Vulnerable Code ```js function renderAnalysisHtml(bookId, analysis, defaultLang='ja') { const summary = String(analysis?.summary || '').trim(); const highlights = splitMulti(analysis?.highlights || []); const tags = splitMulti(analysis?.tags || []); const reread = Array.isArray(analysis?.reread) ? analysis.reread : []; const generatedAt = String(analysis?.generated_at || '').trim() || new Date().toISOString(); const sourceHash = String(analysis?.file_hash || '').trim(); let lang = String(analysis?.lang || defaultLang).toLowerCase(); if (!I18N[lang]) lang = I18N[defaultLang] ? defaultLang : 'en'; const tr = I18N[lang]; const lines = [ OC_START, '<div class="openclaw-analysis">', `<h3>${tr.title}</h3>` ]; if (summary) { lines.push(`<p><strong>${tr.summary}:</strong> ${summary}</p>`); } if (highlights.length) { lines.push(`<h4>${tr.key_points}</h4><ul>`); for (const h of highlights) lines.push(`<li>${h}</li>`); lines.push('</ul>'); } if (reread.length) { lines.push(`<h4>${tr.reread}</h4><ul>`); for (const item of reread) { if (!item || typeof item !== 'object') continue; const section = String(item.section || '').trim(); const page = String(item.page || '').trim(); const chunk = String(item.chunk_id || '').trim(); const reason = String(item.reason || '').trim(); const parts = [ section ? `${tr.section}: ${section}` : '', page ? `${tr.page}: ${page}` : '', chunk ? `${tr.chunk}: ${chunk}` : '', reason ].filter(Boolean); if (parts.length) lines.push(`<li>${parts.join(' | ')}</li>`); } lines.push('</ul>'); } const meta = [`${tr.generated_at}: ${generatedAt}`]; if (sourceHash) meta.push(`${tr.file_hash}: ${sourceHash}`); ...[truncated 3453 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. HTML-escape every text value before inserting it into generated markup, including summaries, list entries, metadata values, and reread fields. 2. Treat `comments_html` as untrusted. Prefer removing direct raw-HTML support and accepting structured text only. 3. If raw HTML is a required feature, process it through a mature sanitizer configured with a strict allowlist. 4. Reject or remove: - `<script>`, `<iframe>`, `<object>`, `<embed>`, and similar active elements. - Inline event handlers such as `onclick` and `onerror`. - `javascript:`, unsafe `data:`, and other active URL schemes. - Remote images or styles unless explicitly required and approved. - Dangerous CSS and namespace-based active content. 5. Generate the OpenClaw analysis block solely from escaped structured fields. 6. Add a dry-run field indicating whether sanitization changed or rejected content. 7. Validate delegated-model and imported JSONL output against a strict schema before generating metadata. 8. Add tests containing HTML tags, quoted attributes, event handlers, unsafe URLs, and marker strings to verify that stored output remains inert. 9. Preserve the explicit user-approval gate, but do not rely on approval as the primary defense against injection. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (12)

Ae1

High
Category
analysis-evasion
Content
5. Save result JSON and run `scripts/handle_completion.mjs --run-id ... --result-json ...`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
function hydrateEnvFromDotEnv() {
  const candidates = [
    path.resolve(process.cwd(), '.env'),
    path.join(os.homedir(), '.openclaw', '.env'),
  ];
  const seen = new Set();
Confidence
83% confidence
Finding
The script automatically loads environment variables from `.env` files in the current working directory and the user's home configuration path, then uses them to supply Calibre connection details and passwords. In a hostile or untrusted workspace, a planted `.env` can silently redirect the tool to an attacker-controlled server or inject attacker-chosen credentials/targets, causing credential disclosure to external services or unintended metadata operations.

Credential Access

High
Category
Privilege Escalation
Content
function hydrateEnvFromDotEnv() {
  const candidates = [
    path.resolve(process.cwd(), '.env'),
    path.join(os.homedir(), '.openclaw', '.env'),
  ];
  const seen = new Set();
  for (const p of candidates) {
Confidence
83% confidence
Finding
The secondary automatic load from `~/.openclaw/.env` also introduces credential/config injection risk because sensitive connection parameters are consumed without integrity validation. If that file is modified by other local processes, shared account usage, or prior compromise, the script may unknowingly use attacker-controlled endpoints or secrets when invoking `calibredb`.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The skill documentation is written entirely in Japanese and does not indicate that other languages are supported or that the user can choose a preferred language. Under the stated policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
### ユーザーが先に実行すること(例: Ubuntu/WSL)

```bash
sudo apt update
sudo apt install -y calibre poppler-utils
```
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
### ユーザーが先に実行すること(例: Ubuntu/WSL)

```bash
sudo apt update
sudo apt install -y calibre poppler-utils
```
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill declares access to environment variables in metadata, including CALIBRE_PASSWORD, but does not define an explicit tool/permission scope such as allowed-tools or permissions. That creates a governance gap where the runtime may expose broader capabilities than intended, making secret access and command execution harder to constrain or audit for a skill that performs remote metadata writes.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
Read the workspace `AGENTS.md` `## Tools` section for Content server URL, library id, auth, and reading script.

Connection bootstrap:
- Do not ask the user for `--with-library` first.
- First use saved defaults with no explicit `--with-library`.
- Scripts auto-load `.env`.
- Non-SSL auth is Digest; do not pass auth-mode/auth-scheme flags.
Confidence
80% 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.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The documented dry-run and apply commands both hard-code `--lang ja`, which imposes a specific language/locale choice. The file does not offer a user choice or explain that the locale restriction is optional or region-specific, so this is a natural-language policy violation.

Unbounded Output

Medium
Category
Output Handling
Content
- If empty/failed, fallback to `pdftotext`.
- If both fail, switch to web-evidence-first mode.
- Use the Calibre `reading_script` in the workspace `AGENTS.md` `## Tools` section for Japanese/non-Latin sort fields.
- Default policy is full reading, no truncation.
- Ask once on first use only when the workspace `AGENTS.md` `## Tools` section lacks the configured reading script.

## Heavy Analysis
Confidence
79% confidence
Finding
The instruction 'full reading, no truncation' can lead to excessive extraction and transmission of entire document contents during analysis, especially when combined with deep pass and possible subagent delegation. This increases the risk of unintended disclosure of sensitive book/document content, token overuse, and denial-of-service style resource consumption from very large files or batches.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The argument parser sets `lang: 'ja'` as the default, and later enforces output to either Japanese or English. This creates a language policy concern because the skill forces a specific locale by default rather than asking the user to choose or clearly making it opt-in.

Missing User Warnings

Low
Confidence
91% confidence
Finding
The `remove` command deletes a run record from persistent state and writes the modified file back, but there is no confirmation prompt, warning message, or comment/docstring disclosing that stored state will be removed. For a code file, this is a file-modifying/destructive operation that lacks visible user disclosure in the file itself.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/calibredb_apply.mjs:97