Back to skill

Security audit

Junyi Doc Reader

Security checks for vulnerabilities and agentic risk

Overview

The skill has a real document-archiving purpose, but it has consent and output-state flaws that can expose or mix private document content.

Review carefully before installing. Use a fresh output directory for each document, do not rely on --dry-run as a no-write preview, and avoid insights mode or direct enricher.py use until consent enforcement and endpoint validation are fixed. If used, expect it to read specified local documents, read Feishu app credentials for Feishu mode, call Feishu APIs, and write archive files into the chosen output directory.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/enricher.py:78
Finding

Standalone LLM Enrichment Bypasses Document-Disclosure Consent Controls

Content
View full analysis
6000: text = text[:6000] + "\n\n[... truncated for analysis ...]" payload = json.dumps({ "model": model, "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": USER_PROMPT_TEMPLATE.format(text=text)}, ], "temperature": 0.2, "max_tokens": 800, }).encode("utf-8") req = urllib.request.Request( api_url, data=payload, headers={ "Content-Type": "application/json", "Authorization": f"Bearer {api_key}", }, method="POST", ) resp = urllib.request.urlopen(req, timeout=60) ``` ```python def enrich(chunks_path, output_path): """Main enrichment logic with resume support.""" api_key, api_url, model = get_api_config() if not api_key: print("ERROR: DOC_READER_API_KEY not set. Cannot run enrichment.") print("Set the environment variable or use archive-only mode.") sys.exit(1) ``` ### Technical Analysis The declared security model requires all of the following before document chunks are externally transmitted: 1. An API key. 2. An explicitly configured API endpoint. 3. An explicitly conf ...[truncated 2087 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pipeline.py:138
Finding

Feishu Dry-Run Mode Writes Fetched Document Content and Metadata

Content
View full analysis
tuple: """Main entry: fetch, normalize, and write converted.md + source_meta.json. Returns (md_path, source_meta_dict). Skips fetch if existing source_meta.json has matching content_hash. """ os.makedirs(output_dir, exist_ok=True) md_path = os.path.join(output_dir, "converted.md") meta_path = os.path.join(output_dir, "source_meta.json") # Fetch document print(f"Fetching Feishu doc: {doc_token} (account: {account})") result = fetch(doc_token, account) print(f" Title: {result['title']}") # Normalize md_content = normalize(result["raw_content"]) # Compute content hash content_hash = "sha256:" + hashlib.sha256(md_content.encode("utf-8")).hexdigest() # Generate meta source_meta = generate_source_meta(result, content_hash, account, md_content) # Write files with open(md_path, "w", encoding="utf-8") as f: f.write(md_content) print(f" Written: {md_path} ({source_meta['chars']} chars, {so ...[truncated 2134 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/pipeline.py:267
Finding

Reused Output Directories Can Mix Stale Enrichment Data Across Documents

Content
View full analysis
Remediation
View remediation
-ch-0001 ``` 5. Store enrichment metadata in a sidecar file containing the source hash, chunk-file hash, model, endpoint, and completion status. 6. Open the enrichment output in write mode for a new source. Append mode should be used only after validating compatible resume metadata. 7. Before assembly, verify that the enriched file contains exactly the expected current chunk IDs and corresponding text hashes. 8. Prefer unique per-job working directories followed by an atomic publication step into the requested output directory. 9. Add regression tests that process two different documents into the same directory and verify that no content from the first appears in the second result. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (13)

Tainted flow: 'req' from os.environ.get (line 101, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The request destination is taken from DOC_READER_API_URL and used directly in urllib.request.urlopen, so document chunks and the Authorization bearer token can be sent to an attacker-controlled endpoint if that variable is mis-set or influenced. In this skill, the risk is heightened because the script processes document content and explicitly transmits chunk text off-host, making SSRF-style egress and secret exfiltration possible through configuration.

Content

Scanner excerpt · scripts/enricher.py (reported line 111)May include surrounding context.

python
method="POST",
    )

    resp = urllib.request.urlopen(req, timeout=60)
    body = json.loads(resp.read().decode("utf-8"))

    # Extract content from OpenAI-compatible response

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description presents a broader end-to-end archival/retrieval pipeline, but the supplied code only performs format conversion of local files into Markdown/plain text. Supported inputs in the code are limited to local .docx/.pdf/.txt/.md paths; there is no code for Feishu APIs, cloud document access, Obsidian vault writes beyond a generic output file path, document indexing, chunking, retrieval, or LLM-based processing. While conversion is one subset of the declared functionality, the actual behavior of this chunk is materially narrower than the declared purpose, so this is a mismatch.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The documentation says enrichment requires DOC_READER_ALLOW_EXTERNAL=true as an explicit consent flag, but the implementation only checks for DOC_READER_API_KEY before transmitting document chunks. In a document-archival skill, this is dangerous because sensitive document text may be exfiltrated contrary to the stated opt-in privacy controls.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises capabilities that include environment access, file read/write, network, and shell execution, but it does not declare an explicit tool scope such as permissions or allowed-tools. That omission weakens reviewability and policy enforcement because the effective privilege boundary is implicit rather than machine-verifiable, increasing the risk of overbroad execution if the agent runtime honors undeclared defaults.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger list includes broad natural-language phrases such as reading PDFs, archiving documents, indexing documents, and storing documents to Obsidian. Overbroad triggers can cause the skill to activate in contexts where the user did not intend file access, network retrieval, or bulk document processing, which is especially sensitive because this skill can read local files, access credentials for Feishu mode, and write large outputs.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The 'When to Use' section is broad enough to overlap with ordinary reading or summarization requests, without clear boundaries for when this more powerful archival pipeline should run. Because the skill can invoke shell tooling, process local files, call external services, and write to disk, ambiguous activation criteria increase the chance of unnecessary privileged actions on sensitive documents.

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/converter.py (reported line 24)May include surrounding context.

python
if not shutil.which("pandoc"):
        print("ERROR: pandoc is not installed. Install with: brew install pandoc")
        sys.exit(1)
    result = subprocess.run(
        ["pandoc", "-f", "docx", "-t", "markdown", "--wrap=none", "-o", output_path, input_path],
        capture_output=True, text=True,
    )

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/converter.py (reported line 42)May include surrounding context.

python
"""Convert .pdf to markdown via pdftotext."""
    warnings = []
    if shutil.which("pdftotext"):
        result = subprocess.run(
            ["pdftotext", "-layout", input_path, "-"],
            capture_output=True, text=True,
        )

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The disclosure states that no production endpoint is hardcoded, but the code defines a concrete OpenAI fallback URL and uses it when DOC_READER_API_URL is unset. This mismatch can cause users to believe no external transmission will occur without explicitly choosing an endpoint, while the script will still send document content externally if only an API key is present.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
import urllib.error
import urllib.request

DEFAULT_API_URL = "https://api.openai.com/v1/chat/completions"
DEFAULT_MODEL = "claude-haiku-4-5-20251001"
MAX_RETRIES = 2
RETRY_DELAY = 2.0

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/enricher.py (reported line 55)May include surrounding context.

python
import urllib.error
import urllib.request

DEFAULT_API_URL = "https://api.openai.com/v1/chat/completions"
DEFAULT_MODEL = "claude-haiku-4-5-20251001"
MAX_RETRIES = 2
RETRY_DELAY = 2.0

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

Because a hardcoded external API URL is used as fallback, setting only DOC_READER_API_KEY is sufficient to cause outbound transmission of chunk text, despite the disclosure promising the user must also choose an endpoint. That creates a privacy and data-handling risk, especially for large local or Feishu documents that users may expect to stay offline unless they fully opt in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code generates the index table header using Chinese labels only ("日期", "标题", "字数"). For an all-file-types language policy check, this is a natural-language locale choice embedded in the skill without any user opt-in or documented justification.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.