Back to skill

Security audit

Paragraph

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Paragraph publishing integration, but it gives an agent broad authority to publish, manage subscribers, upload local files, and send credentials to a configurable API host.

Install only if you are comfortable giving the skill authority to publish immediately to your Paragraph account, manage subscribers, and transmit subscriber data to Paragraph. Do not set PARAGRAPH_API_BASE_URL unless you fully trust the destination, and do not allow agents to choose csvPath values from untrusted prompts or documents. Review all post content and subscriber CSV files yourself before invoking write tools, and disable welcome emails unless you have consent to contact recipients.

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

Error
Location
skill.js:9
Finding

Bearer Credential and Sensitive Request Data Can Be Sent to an Arbitrary Configured Host

Content
View full analysis
{ if (params[key] !== undefined && params[key] !== null) { url.searchParams.append(key, String(params[key])) } }) const headers = { "Authorization": `Bearer ${apiKey}` } let fetchBody = null if (body) { headers["Content-Type"] = "application/json" fetchBody = JSON.stringify(body) } if (options.rawBody) { fetchBody = options.rawBody Object.assign(headers, options.headers) } else if (options.formData) { fetchBody = options.formData // Don't set Content-Type; fetch will set boundary } // Set up abort controller for timeout const controller = new AbortController() const timeoutMs = options.timeout || 30000 // default 30 seconds (POSTs can be slow) const timeoutId = setTimeout(() => controller.abort(), timeoutMs) try { const response = await fetch(url.toString(), { method, headers, body: fetchBody, signal: controller.signal }) ``` The CSV import implementation also constructs its upload destination from the same unrestricted base URL and attaches the API credential: ```javascript const url = new URL(`${API_BASE}/v1/subscribers/import`) url.searchParams.append('sendWelcomeEmail', sendWelcomeEmail) const formData = new FormData() formData.append('file', csvBuffer, 'subscribers.csv') ...[truncated 3064 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
skill.js:440
Finding

Caller-Controlled CSV Path Allows Arbitrary Local Files to Be Read and Uploaded

Content
View full analysis
{ if (!csvPath) throw new Error("csvPath is required") const fs = await import('fs') const csvBuffer = fs.readFileSync(csvPath) // Build URL with query param const url = new URL(`${API_BASE}/v1/subscribers/import`) url.searchParams.append('sendWelcomeEmail', sendWelcomeEmail) const formData = new FormData() formData.append('file', csvBuffer, 'subscribers.csv') const response = await fetch(url.toString(), { method: "POST", headers: { "Authorization": `Bearer ${process.env.PARAGRAPH_API_KEY}` // Content-type (with boundary) set automatically by fetch when using FormData }, body: formData }) ``` ### Technical Analysis The `paragraph_importSubscribers` tool accepts a raw filesystem path from its caller and passes it directly to `fs.readFileSync`. It performs no path restriction, canonicalization, symbolic-link check, file-type validation, size limit, or CSV parsing before uploading the resulting bytes. Consequently, the operation is not limited to subscriber CSV files. Any file readable by the Node.js process can be selected and transmitted while being labeled `subscribers.csv`. In an Agent environment, tool arguments may be influenced by untrusted prompt content, imported documents, or indirect prompt injection. The issue is amplified by the unrestricted `PARAGRAPH_API_BASE_URL`: an attacker who can influence both the path argument and API base can cause a selected local file to be delivered directly to an attacker-controlled endpoint. Even with the official endpoint, the Skill unnecessarily reads and transmits unvalidated local files. The declared CSV import feature needs access to a user-selected CSV file, but it does no ...[truncated 1615 chars]
Remediation
View remediation
{ if (!Array.isArray(subscribers) || subscribers.length === 0) { throw new Error("subscribers must be a non-empty array") } const validatedCsv = buildValidatedSubscriberCsv(subscribers) // Upload only the newly constructed and validated ...[truncated 15 chars]
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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

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 · README.md (reported line 29)May include surrounding context.

md
## Prerequisites

1. **Paragraph Account**: Create an account at [paragraph.com](https://paragraph.com)
2. **API Key**: Generate one in Account Settings → Integrations
3. **Node.js 19+**: Required for native fetch (OpenClaw uses Node 24+)

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 · README.md (reported line 81)May include surrounding context.

Agent Prompt Examples

Create a blog post from research findings

text
You are a content creator. Use the paragraph skill to publish a new blog post
based on today's research notes. Title: "AI Trends 2026", markdown content

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The example prompt instructs the agent to publish a blog post from research notes without clearly emphasizing that paragraph_createPost publishes immediately and may create permanent public/onchain content. That omission is dangerous because users may interpret the action as a drafting step and unintentionally disclose sensitive or unreviewed material.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The subscriber add/import sections describe bulk ingestion of emails and wallets and optional welcome emails, but do not prominently warn about consent, privacy obligations, or the risk of contacting users without authorization. In an autonomous agent context, this can lead to unauthorized processing of personal data or unintended outreach at scale.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The README documents a workflow using published: false and paragraph_updatePost, while elsewhere it clearly states drafts and post updates are unsupported. In an agent setting, contradictory docs can cause the agent or operator to rely on nonexistent safety/review steps and accidentally publish content immediately, including irreversible public/onchain content.

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 · README.md (reported line 473)May include surrounding context.

md
1. Writer generates markdown draft
2. Publisher Agent calls `paragraph_createPost` with `published: false`
3. Review/approval step
4. Publish: `paragraph_updatePost` with `published: true` OR create with `published: true`
5. If tokenizing: capture coin ID from response, track via `paragraph_getCoin` and `paragraph_listCoinHolders`
6. Analytics: correlate engagement (views, holders) with content performance

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill enables creation of posts that are published onchain immediately, but the user-facing description does not prominently warn that this action can be effectively irreversible and permanently public. In an agent context, a user may authorize routine publishing without understanding that mistakes, sensitive data, or draft content could be immutably exposed and difficult or impossible to retract.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The subscriber-management section describes collecting and importing emails and wallet addresses, but does not present a clear privacy and data-handling warning. Because these identifiers are personal data and can link identity, contact information, and onchain activity, an agent could process or migrate subscriber data without adequate user awareness, consent, retention limits, or regulatory safeguards.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The subscriber import tool accepts an arbitrary local filesystem path and reads it directly with fs.readFileSync before uploading the contents to the remote Paragraph API. In an agent context, this creates a file-exfiltration primitive: a prompt or indirect instruction could cause the agent to read sensitive host files and transmit them off-box under the guise of CSV import, which is broader than expected for a normal API wrapper.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The import flow reads a local file and uploads subscriber data to an external service without any built-in consent gate, warning, or confirmation step. In an LLM/agent environment, this makes sensitive-file access and external transmission easier to trigger accidentally or through prompt injection, especially because the tool's purpose appears to be ordinary subscriber management rather than host file handling.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The doc comment explicitly states the feed endpoint is public and requires no authentication, yet paragraph_getFeed calls request(), which unconditionally requires PARAGRAPH_API_KEY and sends an Authorization header. This is an active contradiction between documentation and behavior, not merely an omitted detail.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
skill.js:9

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:222