Back to skill

Security audit

puzle-read

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a coherent Puzle reading-library integration, but it can route private or authenticated content into an external service too broadly.

Review before installing. Use this skill only when you intentionally want content sent to and stored by Puzle. Do not let it upload confidential, internal, regulated, or auth-protected documents unless you explicitly approve the exact source and destination. Prefer requiring confirmation before any file, pasted text, private page, or internal document is uploaded.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:291
Finding
Authenticated Internal Content Can Be Exported to an External Service Without Content-Specific Confirmation## Vulnerability Details **File Location**: `SKILL.md:291-304` and `SKILL.md:359` **Vulnerability Type**: Least-privilege violation and unauthorized trust-boundary bridging **Risk Level**: High ### Vulnerable Instructions ```text │ Step 2: Check for internal/specialized tools │ │ │ │ The URL might be an internal or auth-protected │ │ resource. Look in the environment for tools │ │ that can access it: │ │ - MCP servers for internal docs (Confluence, │ │ Notion, Google Docs, etc.) │ │ - API endpoints that can fetch internal content │ │ - Authenticated browser sessions │ │ │ │ Found a tool and got content? │ │ → Save content as a local file (.html / .md) │ │ → Call create_reading_from_file() │ │ ✅ Done │ ``` The associated method-selection table reinforces this behavior: ```text | **Internal link content already retrieved** | Content from Step 1, saved as file then uploaded | ``` ### Technical Analysis The Skill instructs the Agent to search its environment for privileged integrations, internal APIs, and authenticated browser sessions when a URL cannot be fetched publicly. It then directs the Agent to retrieve the protected content, save it locally, and upload it to Puzle. This bridges two separate trust domains: 1. An authenticated internal system, such as Confluence, Notion, Google Docs, or a private web application. 2. The external Puzle processing and storage service. Access to an internal resource does not inherently authorize disclosure of that resource to an external service. The workflow therefore exceeds minimum privilege by encouraging discovery and use of every available authenticated retrieval mechanism instead of restricting access to a source ...[truncated 2004 chars]
Remediation
## Remediation Suggestions 1. Remove instructions that automatically enumerate internal tools, APIs, and authenticated browser sessions. 2. Require the user to explicitly identify and authorize the protected source to be accessed. 3. Before retrieving protected content, explain that the source requires authenticated access and that its contents may be exported. 4. Immediately before upload, obtain separate confirmation that identifies: - The specific document or resource - The destination service and domain - That the content will be stored and processed externally - Any known retention or deletion implications 5. Do not infer upload authorization merely because the Agent has permission to read a resource. 6. Default to local analysis for internal or confidential content unless external storage is explicitly requested. 7. Add policy checks that prohibit uploading regulated, secret, or organization-restricted data. 8. Where supported, provide a preview of the exact content and metadata that will be transmitted. 9. Restrict authenticated retrieval to the minimum tool and document scope necessary for the user's explicit request.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:519
Finding
Plaintext Temporary Files Containing User Content Are Not Deleted## Vulnerability Details **File Location**: `SKILL.md:519-529` **Vulnerability Type**: Unsafe temporary-file handling and residual plaintext data **Risk Level**: Medium ### Vulnerable Instructions ```python import tempfile with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False) as f: f.write(user_provided_text) temp_path = f.name result = client.create_reading_from_file(temp_path) # Tell user: "Saved to your reading library: {result.web_url}" ``` ### Technical Analysis The documented workflow writes user-provided text to a temporary Markdown file with `delete=False`. This explicitly prevents automatic removal when the file handle is closed. No cleanup operation follows the upload, and no `try`/`finally` block ensures deletion when upload or processing fails. The Skill specifically describes this workflow for notes, memoranda, private documents, and other text material. Consequently, sensitive plaintext may remain in the system temporary directory beyond the lifetime of the Skill operation. Although `NamedTemporaryFile` generally creates files with restrictive permissions on supported platforms, file permissions alone do not eliminate residual-data risks. The file may remain accessible to the same account, later Agent operations, backup software, forensic recovery, or privileged local processes. ### Attack Path 1. A user provides private text, notes, or memorandum content. 2. The Agent follows the documented example and creates a temporary file with `delete=False`. 3. The complete user content is written to disk in plaintext. 4. The file is uploaded through `create_reading_from_file()`. 5. The workflow completes or fails without deleting the temporary file. 6. A later process operating under the same account, a privileged local process, backup software, or a subsequent Agent task locates and reads the residual file. The issue can occur during both successful and failed uploads because ...[truncated 683 chars]
Remediation
## Remediation Suggestions Delete the temporary file in a `finally` block so cleanup occurs after success, exceptions, and upload failures: ```python import tempfile from pathlib import Path temp_path = None try: with tempfile.NamedTemporaryFile( mode="w", suffix=".md", encoding="utf-8", delete=False, ) as f: f.write(user_provided_text) temp_path = f.name result = client.create_reading_from_file(temp_path) finally: if temp_path is not None: Path(temp_path).unlink(missing_ok=True) ``` Additional hardening measures: 1. Prefer `create_reading_from_html()` or another in-memory API when the source is already text, avoiding local persistence entirely. 2. Keep restrictive owner-only permissions on any unavoidable temporary file. 3. Store temporary data only for the shortest possible duration. 4. Ensure cleanup covers exceptions, interruptions, timeouts, and failed network requests. 5. Do not place sensitive filenames, user identifiers, or document titles in temporary filenames. 6. Document that deletion from ordinary filesystems may not guarantee secure erasure from snapshots, backups, or copy-on-write storage.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (11)

Tainted flow: 'file_bytes' from pathlib.Path.read_bytes (line 252, file read) → requests.put (network output)

High
Category
Data Flow
Content
file_key: str = upload_info["file_key"]

        # Step 2: upload binary to S3
        put_resp = requests.put(
            upload_url,
            data=file_bytes,
            headers={"Content-Type": "application/octet-stream"},
Confidence
80% confidence
Finding
File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill declares broad capabilities involving file read/write, network access, and shell usage but provides no explicit tool-scope or permission boundaries. In an agent environment, this increases the chance of over-privileged execution, making accidental data access, arbitrary file handling, or unexpected outbound requests more likely if the skill is triggered in the wrong context.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The activation guidance is overly broad, covering common requests like summarizing articles, uploading files, or saving content for later, which can cause the skill to activate in situations where the user did not clearly intend to send data to a third-party service. Because the skill uploads user content and may initiate authentication, broad triggers can lead to privacy-impacting actions from ambiguous prompts.

Session Persistence

Medium
Category
Rogue Agent
Content
## Token Management

Token is stored in `~/.config/puzle/config.json` with file permission `0o600` (owner read/write only).
The **only** way to obtain a token is through the device authorization code exchange flow.

**Token confidentiality rules:**
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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- "save this" / "bookmark" / "read it later" / "store this link"
- "save all of these" — batch-saving multiple links
- User uploads a file but doesn't ask for any analysis — "just store this PDF"
- User shares a link in passing without asking a question about its content
- "keep it for later"

### Mode B: Analyze now (background processing)
Confidence
95% confidence
Finding
The guidance explicitly authorizes saving content when a user merely shares a link 'without asking a question about its content,' which is an autonomous side effect without clear user consent. In a skill that performs network uploads and persists data remotely, this can result in unintended disclosure, storage, and processing of user-provided or internal resources.

Session Persistence

Medium
Category
Rogue Agent
Content
30–90 seconds. To avoid blocking the conversation:

1. **Create the reading and immediately give user the web link**
2. **Spawn a background task / subagent** to run `wait_for_reading()` + analysis
3. **When the background task completes**, present the result to the user

```python
Confidence
70% 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.

External Transmission

Medium
Category
Data Exfiltration
Content
PuzleAPIError: If the exchange request fails.
        """
        url = f"{BASE_URL}/auth/device/token"
        resp = requests.post(url, json={"code": code})
        if not resp.ok:
            raise PuzleAPIError(code=resp.status_code, msg=resp.text)
        body: dict[str, Any] = resp.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The save-html workflow sends full HTML and text content to the remote service without an explicit privacy/transmission warning. Because pre-fetched content may include private page data, scraped content, or sensitive text assembled elsewhere by an agent, silent transmission creates a meaningful confidentiality risk.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The save-file workflow reads arbitrary local file contents and uploads them to remote storage/service without an explicit warning, consent gate, or prominent disclosure at the action point. In an agent/skill setting, this increases the risk of users unintentionally transmitting sensitive local documents because the operation is framed as 'create a reading' rather than clearly as an external upload.

Session Persistence

Medium
Category
Rogue Agent
Content
sub.add_parser("status", help="Check whether a valid token is configured")

    # save-url -----------------------------------------------------------
    p_url = sub.add_parser("save-url", help="Create a reading from a URL")
    p_url.add_argument("url", help="Web article URL to save")

    # save-file ----------------------------------------------------------
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.

Vague Triggers

Low
Confidence
87% confidence
Finding
The save-mode examples include vague phrases such as 'save this' or treating a shared link 'in passing' as sufficient to store content remotely. This weakens consent boundaries and can cause the agent to transmit user data or third-party URLs to the external service without an explicit request to do so.

Static analysis

No suspicious patterns detected.