Back to skill

Security audit

Apify Substack Scraper

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it advertises, but its example commands put an Apify token in URLs where it may be logged or exposed.

Review before installing. Use only public or approved Substack URLs, prefer a narrowly scoped Apify token, avoid command logging or shell tracing, and replace the URL token examples with header-based authorization where supported.

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

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:27
Finding

API Token Exposed in URL Query Strings

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 27-40
Vulnerability Type: Credential exposure through URL query parameters
Risk Level: Medium

Vulnerable Code

bash
RESULT=$(curl -s -X POST "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.substack.com"], "maxArticles": 20}')
echo "$RESULT" | jq '.'
bash
RUN_ID=$(curl -s -X POST "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.substack.com"], "maxArticles": 100}' | jq -r '.data.id')
curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID?token=$APIFY_TOKEN" | jq -r '.data.status'
curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID/dataset/items?token=$APIFY_TOKEN" | jq '.'

Technical Analysis

The Skill expands the APIFY_TOKEN environment variable directly into the query string of every authenticated API URL. Authentication with Apify is necessary for the declared scraping workflow, and the credential is sent only to the declared api.apify.com service. However, placing a secret in a URL creates a broader exposure surface than necessary.

Complete URLs may be captured by shell tracing, process or command telemetry, HTTP proxies, access logs, monitoring platforms, debugging output, and error reports. HTTPS protects the request while it is in transit but does not prevent endpoints or intermediary infrastructure from recording the URL. A recorded URL would contain the reusable token in plaintext.

The Skill also sends user-provided Substack URLs and scraping parameters to a third-party hosted Actor. This external processing is consistent with the declared functionality, but users should not submit private URLs or confidential parameters without understanding Apify's data-handling boundaries.

Attack Path

...[truncated 1239 chars]

Remediation
View remediation

Remediation Suggestions

Remove APIFY_TOKEN from all URL query strings and provide it through an authorization header instead:

bash
RESULT=$(curl -s -X POST \
  "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://example.substack.com"],"maxArticles":20}')

Apply the same header-based authentication pattern to Actor launch, run-status, and dataset requests. In addition:

  • Use a narrowly scoped Apify token with only the permissions required for this Actor and its datasets.
  • Avoid enabling shell tracing, verbose command logging, or diagnostic collection around authenticated commands.
  • Redact authorization headers and credentials from application and infrastructure logs.
  • Rotate any token that may previously have been recorded in logs or telemetry.
  • Document that requested URLs and scrape parameters are processed by a third-party hosted Actor.
  • Warn users not to submit confidential URLs or parameters unless the third-party processing and retention policy is acceptable.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (7)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The description says to use the skill when the user asks to "search Substack," "find newsletter posts," "extract Substack content," or "monitor Substack publications," but it does not define clear boundaries or exclusions for when the skill should or should not activate. Several of these phrases are broad enough to overlap with general browsing, research, or summarization requests, increasing the risk of unintended invocation.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This command transmits user-supplied scraping targets and the APIFY_TOKEN to an external third-party service. While external API use is expected for this skill, sending secrets in the URL query string is risky because URLs may be logged by shells, proxies, monitoring tools, or server logs, exposing the token.

Content

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

Step 2: Run the Actor

bash
RESULT=$(curl -s -X POST "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.substack.com"], "maxArticles": 20}')
echo "$RESULT" | jq '.'

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This command transmits user-supplied scraping targets and the APIFY_TOKEN to an external third-party service. While external API use is expected for this skill, sending secrets in the URL query string is risky because URLs may be logged by shells, proxies, monitoring tools, or server logs, exposing the token.

Content

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

Step 2: Run the Actor

bash
RESULT=$(curl -s -X POST "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.substack.com"], "maxArticles": 20}')
echo "$RESULT" | jq '.'

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This asynchronous run command sends data to Apify and includes APIFY_TOKEN directly in the request URL. Even in a legitimate integration, placing credentials in URLs increases the chance of accidental disclosure through logs, browser history equivalents, process inspection, or telemetry systems.

Content

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

Step 3: Poll and fetch (if async)

bash
RUN_ID=$(curl -s -X POST "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.substack.com"], "maxArticles": 100}' | jq -r '.data.id')
curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID?token=$APIFY_TOKEN" | jq -r '.data.status'

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This asynchronous run command sends data to Apify and includes APIFY_TOKEN directly in the request URL. Even in a legitimate integration, placing credentials in URLs increases the chance of accidental disclosure through logs, browser history equivalents, process inspection, or telemetry systems.

Content

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

Step 3: Poll and fetch (if async)

bash
RUN_ID=$(curl -s -X POST "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.substack.com"], "maxArticles": 100}' | jq -r '.data.id')
curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID?token=$APIFY_TOKEN" | jq -r '.data.status'

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

Polling run status with the token in the URL continues the same credential-exposure issue across follow-up requests. Repeated use of URL query authentication broadens the surface for token leakage into logs and observability systems, especially in automated agent environments.

Content

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

RUN_ID=$(curl -s -X POST "https://api.apify.com/v2/acts/BULaGFURBV7WG3K81/runs?token=$APIFY_TOKEN"
-H "Content-Type: application/json"
-d '{"urls": ["https://example.substack.com"], "maxArticles": 100}' | jq -r '.data.id') curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID?token=$APIFY_TOKEN" | jq -r '.data.status' curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID/dataset/items?token=$APIFY_TOKEN" | jq '.'

text

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

Fetching dataset items with the token in the URL exposes credentials during data retrieval, and the response may also contain scraped content from third-party sources. In this skill context, external transmission is expected, but the combination of secret-in-URL and content export to a third party makes the pattern materially risky.

Content

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

-H "Content-Type: application/json"
-d '{"urls": ["https://example.substack.com"], "maxArticles": 100}' | jq -r '.data.id') curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID?token=$APIFY_TOKEN" | jq -r '.data.status' curl -s "https://api.apify.com/v2/actor-runs/$RUN_ID/dataset/items?token=$APIFY_TOKEN" | jq '.'

text

### Step 4: Present results