Back to skill

Security audit

calibre-catalog-read

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Calibre catalog reader and one-book analysis helper that writes local cache/state files but does not show hidden metadata edits, exfiltration, or destructive behavior.

Install only if you are comfortable with a skill exporting selected books and extracted text into local private cache/state directories for analysis. Use a dedicated Calibre password environment variable, point it only at the intended library or server, and route any real metadata updates through a separate apply/review workflow.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (21)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The description emphasizes searching/viewing a Calibre catalog and preparing/validating one-book analysis without changing metadata. The code does avoid Calibre metadata edits, but its primary behavior is different: it creates and manages its own SQLite database of analysis records, persists summaries/highlights/tags/coverage, updates existing entries, reports analysis status, and searches that analysis store. There is some partial alignment with 'one-book analysis' handling, but the undeclared persistent write/update capability and the focus on an analysis cache rather than catalog viewing make this a material description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The code chunk does not show Calibre catalog browsing or one-book analysis validation logic directly. Instead, it exercises an analysis_db module that manages a SQLite cache of analysis records, including upsert, status lookup, and text search. While this could be a supporting internal component for an analysis feature, the declared purpose is narrowly framed around searching/viewing a Calibre catalog and preparing/validating analysis without metadata changes. The actual code’s primary purpose in this chunk is database cache testing, which is a materially different behavior and undeclared capability/resource (local SQLite cache management). There is no evidence of metadata editing, but the main functionality shown still does not match the declared description.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
Precedence: --with-library, CALIBRE_WITH_LIBRARY / CALIBRE_LIBRARY_URL / CALIBRE_CONTENT_SERVER_URL, then config.with_library. A server URL must select #LIBRARY_ID; use --library-id, CALIBRE_LIBRARY_ID, or config.library_id when the URL lacks it. libraries is read-only discovery on the specified server, not network scanning. Do not pass the browser's #library_id= URL.

Config defaults to ~/.config/calibre-catalog-read/config.json; override with --config or CALIBRE_CATALOG_CONFIG. Keys: with_library, library_id, username, password_env, reading_script (human-only legacy hint). Unknown keys fail. No implicit .env loading or credential persistence. Credentials come from the existing environment; --password-env names the password variable. Raw --password is rejected. Passwords never enter child argv or diagnostics. Non-SSL server authentication is handled by Calibre (Digest); do not invent auth-mode/auth-scheme flags.

Local library directories must already contain metadata.db. Explicit endpoints never fail over to DNS/WSL/Docker/localhost guesses. Never start calibre-server from chat.

Memory Manipulation

High
Category
Memory Poisoning
Confidence
90% confidence
Finding

Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Content

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

md
node "{baseDir}/scripts/handle_completion.mjs" --run-id RUN_ID \
         --analysis-json /absolute/analysis.json --proposal-jsonl /absolute/NEW-proposal.jsonl

   Completion criterion: status=proposal_created and an existing JSONL proposal. Completion is offline: no re-export, network lookup, or metadata write. Missing/duplicate runs do no work; failed/corrupt state remains for inspection. An existing lock requires checking live work before manual recovery, never automatic lock deletion.

5. Present the analysis, source identity, and full/partial coverage honestly. Coverage validates reported parts against prepared files/hashes/counts; it does not prove that a model understood the text. Preserve partial coverage in user-facing output. If the user also wants comments/tags updated, route the generated proposal through calibre-metadata-apply and its plan/apply gates; never apply it from this skill.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill invokes Node, Python, calibredb, reads environment variables for connection details, uses local state/cache directories, and can contact a Calibre content server, yet it does not declare an explicit tool scope such as permissions or allowed-tools. This increases the risk of over-broad execution because a host agent may grant more shell, file, or network capability than the skill actually needs, making misuse or prompt-injection fallout more severe.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

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

md
The record binds the manifest bytes and target. Do not replace/reuse a failed or completed run ID. Optional --state selects a separate state file.

3. Use references/subagent-analysis.prompt.md and both JSON schemas. The analysis worker reads the prepared parts and emits only analysis JSON. Use the currently available isolated worker/tool schema; no fixed agent ID, model, two-turn rule, or assumed completion mode. Keep the runtime handle and follow its accepted wait/collection contract. Main owns user responses and every metadata-write decision.

4. Validate and produce a local proposal:

Autonomous Decision Making

Medium
Category
Excessive Agency
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.

Content

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

md
- User state: CALIBRE_CATALOG_STATE_DIR, else XDG_STATE_HOME/calibre-catalog-read or ~/.local/state/calibre-catalog-read.
- User cache: CALIBRE_CATALOG_CACHE_DIR, else XDG_CACHE_HOME/calibre-catalog-read or ~/.cache/calibre-catalog-read.
- Exported originals, extracted text, parts, plans, and summaries are private. Keep them outside the skill package; do not publish or automatically delete them.
- Legacy package/state, old run formats, and old SQLite caches are not automatically migrated. Re-prepare active legacy work under a new run/state path; preserve originals. analysis_db.py is an optional local index, never completion authority. See README.md for test/migration details.
- Python >=3.11 is needed for analysis/cache helpers; Node >=20 and calibredb suffice for catalog lookup. No uv/network dependency install is required.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This JSON schema constrains the lang field to only ja or en, which is a natural-language locale restriction. Because the file does not indicate that this limitation is user-selectable or justified as region-specific, it may violate the policy against forcing a specific language or locale without opt-in.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/analysis_db.py (reported line 14)May include surrounding context.

python
SCHEMA_VERSION = 3
SCHEMA = """
CREATE TABLE IF NOT EXISTS analyses (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  book_id INTEGER NOT NULL,
  library_id TEXT NOT NULL,

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code hard-rejects any record whose lang is not ja or en, which imposes a locale/language policy in the implementation. Because the file does not present this as an optional user choice or explain a justified region-specific constraint, it matches the policy-violation category for forced language restrictions.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest emphasizes read-oriented catalog access and preparation/validation without changing library metadata, while this file implements an upsert operation that inserts or updates analysis content and timestamps in a persistent local database. Although it does not alter Calibre metadata directly, it performs stateful writes beyond a purely read/view or validation role.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The status function defaults lang to ja, and the CLI also defaults --lang to ja, which biases behavior toward a specific language without explicit user opt-in. This is a natural-language policy concern because the skill selects a locale by default rather than prompting or remaining neutral.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The argument parser sets --lang choices to ja and en with a default of ja, which enforces both a narrow language set and a specific default locale. This can violate organizational language/locale policy when users are not offered a neutral default or explicit opt-in.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/run_analysis_pipeline.py (reported line 113)May include surrounding context.

python
try:
        # Spool output, rather than buffering arbitrarily large converter logs in memory.
        with tempfile.TemporaryFile() as out, tempfile.TemporaryFile() as err:
            result = subprocess.run(args, input=(password + "\n").encode() if password else b"", stdout=out, stderr=err, env=env, timeout=timeout)
            if out.tell() > MAX_OUTPUT or err.tell() > MAX_OUTPUT:
                error("subprocess output exceeded 16 MiB; refusing truncated output")
            out.seek(0); err.seek(0)

Tainted flow: 'password' from os.environ.get (line 129, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/run_analysis_pipeline.py (reported line 113)May include surrounding context.

python
try:
        # Spool output, rather than buffering arbitrarily large converter logs in memory.
        with tempfile.TemporaryFile() as out, tempfile.TemporaryFile() as err:
            result = subprocess.run(args, input=(password + "\n").encode() if password else b"", stdout=out, stderr=err, env=env, timeout=timeout)
            if out.tell() > MAX_OUTPUT or err.tell() > MAX_OUTPUT:
                error("subprocess output exceeded 16 MiB; refusing truncated output")
            out.seek(0); err.seek(0)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest validator only accepts lang values of ja or en, and the CLI later defaults --lang to ja. This imposes a language/locale constraint in the skill behavior without offering broader user choice or documenting a clear region-specific justification.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_workflow.py (reported line 66)May include surrounding context.

python
def cli(self, name, *args, env=None):
        command = [sys.executable if name.endswith('.py') else 'node', str(SCRIPT / name), *map(str, args)]
        return subprocess.run(command, env={**self.env, **(env or {})}, cwd=self.root, capture_output=True, text=True, timeout=20)

    def prepare(self, run="run1", **control):
        if control:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_workflow.py (reported line 178)May include surrounding context.

python
p = self.prepare(); f = self.root / "analysis.json"; f.write_text(json.dumps(self.analysis(p)))
        self.assertEqual(self.cli("run_state.mjs", "upsert", "--run-id", "run1", "--input-json", p["prepared_input"], "--state", self.state).returncode, 0)
        args = ['node', str(SCRIPT/'handle_completion.mjs'), '--run-id','run1','--analysis-json',str(f),'--proposal-jsonl',str(self.root/'one.jsonl'),'--state',str(self.state)]
        workers = [subprocess.Popen(args,env=self.env,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True) for _ in range(2)]
        outputs = [w.communicate(timeout=20) for w in workers]
        created = sum(w.returncode == 0 and json.loads(o[0]).get('status') == 'proposal_created' for w,o in zip(workers,outputs))
        self.assertEqual(created,1)

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The lang field only permits ja or en, which is a natural-language locale restriction embedded in the schema. Under the policy, forcing a specific language set without explicit user opt-in or justification is a potential locale-policy violation.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code creates directories and initializes a SQLite schema for storing analyses, which is a persistence capability not expressed in the manifest description of searching/viewing a catalog or preparing/validating one-book analysis. This is a semantic expansion of behavior even if limited to local state.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The manifest frames this skill as searching/viewing a Calibre catalog or preparing one-book analysis without metadata changes. This entrypoint invokes an external interpreter via child_process.spawnSync, which is a broader execution capability than is clearly justified by a nominally read-oriented catalog skill and increases the skill's operational scope.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.dynamic_code_execution

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/lib/calibre_common.mjs:213

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_cache.py:9

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_workflow.py:15