Back to skill

Security audit

ETH24

Security checks for vulnerabilities and agentic risk

Overview

ETH24 is a coherent daily social-media digest skill whose network calls, local outputs, and optional Typefully draft creation match its stated purpose, though users should understand the external services and review generated drafts.

Install and run this only in an environment where outbound calls to X, xAI, Anthropic, RSS feeds, and Typefully are acceptable. Review any Typefully draft before publishing, and prefer pinned dependency versions or a virtual environment if reproducibility matters.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T01 · Skill Instruction Hijacking

Warning
Location
rank.py:72
Finding
Indirect Prompt Injection Through Untrusted Tweet and RSS Content<![CDATA[ ## Vulnerability Details **File Location**: `rank.py:72-94`, `rank.py:129` **Vulnerability Type**: Indirect prompt injection and insufficient output validation **Risk Level**: Medium ### Vulnerable Code ```python # Build context with actual tweet text and metrics parts = [] for i, t in enumerate(tweets): parts.append( f"TWEET {i + 1}: @{t['handle']} ({t.get('author_name', '')})\n" f"URL: {t['url']}\n" f"Text: {t['text'][:500]}\n" f"Engagement: {t.get('likes', 0)} likes, " f"{t.get('retweets', 0)} RTs, " f"{t.get('replies', 0)} replies " f"(score: {t.get('engagement_score', 0):.0f})" ) for a in rss: parts.append( f"RSS [{a['feed']}]: {a['title']} - {a.get('summary', '')[:300]}\n" f"{a.get('link', '')}" ) context = "\n\n---\n\n".join(parts) ``` The untrusted context is subsequently inserted into the model instruction: ```python RAW DATA: {context[:12000]}""" ``` ### Technical Analysis Tweet text, RSS titles, and RSS summaries originate from external, potentially attacker-controlled sources. The application places this content directly into the same user message that contains the ranking and output instructions. There is no strong instruction boundary stating that content inside `RAW DATA` is untrusted evidence and must never be interpreted as instructions. Consequently, a source item could contain text such as instructions to ignore the ranking rules, alter commentary, select an attacker-controlled story, or return manipulated JSON. The code parses the model's JSON response but does not validate that: - Every returned `tweet_url` belongs to the crawled candidate set. - Every returned handle corresponds to the original URL. - Commentary and highlights satisfy expected length and content constraints. - The number of returned stories is within the configured limit. - The model did not introduce an unrelated or attacker-controlled URL. In tweet mode, manipulate ...[truncated 1759 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Explicitly isolate untrusted content** - Place crawled items in a structured JSON object rather than free-form prose. - State in the highest-priority supported message that tweet and RSS content is untrusted data. - Explicitly instruct the model never to follow commands, policies, or formatting requests found inside source content. 2. **Use separate message roles where supported** - Put application rules in a system or developer message. - Supply crawled data separately as structured input. - Do not concatenate source content into the same instruction block. 3. **Validate model output against an allowlist** - Build a set of valid `(tweet_url, handle)` pairs from `crawl_data["tweets"]`. - Reject every story whose URL is not an exact member of that set. - Derive the handle from the accepted source record rather than trusting the model. - Reject duplicate stories and enforce `max_tweets`. 4. **Validate generated fields** - Enforce maximum lengths for commentary and highlights. - Reject unexpected object keys and invalid field types using a JSON Schema. - Permit only canonical `https://x.com/<handle>/status/<id>` URLs. - Remove control characters and unexpected markup. 5. **Require review before external submission** - Preserve the existing draft-only behavior. - Present a clear diff or source comparison before creating the Typefully draft. - Consider requiring an explicit confirmation flag before any network submission to Typefully. 6. **Add adversarial tests** - Test tweets and RSS summaries containing phrases such as “ignore previous instructions.” - Verify that injected URLs, extra stories, modified handles, and oversized fields are rejected deterministically. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned and Mutable Third-Party Installation Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-3`; installation guidance at `README.md:59`, `README.md:69` **Vulnerability Type**: Unpinned dependency and mutable installer execution **Risk Level**: Medium ### Vulnerable Code `requirements.txt` contains dependencies without version or integrity constraints: ```text feedparser Pillow httpx ``` The documented installation commands are: ```bash npx clawhub@latest install eth24 ``` ```bash pip install -r requirements.txt ``` ### Technical Analysis The Python requirements do not pin reviewed versions and do not provide cryptographic hashes. Each installation can therefore resolve to a different package release. This weakens reproducibility and permits future compromised, malicious, or incompatible releases to enter the installation. The `npx clawhub@latest` command similarly resolves and executes the version currently associated with the mutable `latest` npm tag. Because the resolved code can change after this Skill has been reviewed, the effective installation behavior is not fixed by the audited project. No evidence was found that the currently named dependencies are malicious or that dependency confusion is actively occurring. The vulnerability is the unrestricted trust in mutable third-party artifacts during installation. ### Attack Path 1. A user follows the installation instructions. 2. `npx` resolves `clawhub@latest`, or `pip` resolves the newest compatible versions of the unpinned Python packages. 3. An upstream account, release pipeline, package registry, or future package release is compromised or becomes malicious. 4. The package manager downloads the changed artifact because no immutable version and, for Python dependencies, no expected hash is enforced. 5. Package-controlled installation or runtime code executes under the privileges of the user running the command. 6. The compromised dependency can access files, environment variables, network resources, and other ca ...[truncated 715 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Pin Python dependencies** - Specify exact reviewed versions, for example with `package==version`. - Regenerate pins deliberately after testing rather than resolving the newest release on every installation. 2. **Require cryptographic hashes** - Produce a locked requirements file using a tool such as `pip-tools`. - Install with `pip install --require-hashes -r requirements.lock`. - Review hashes whenever dependencies are upgraded. 3. **Pin the npm installer** - Replace `npx clawhub@latest` with a specific reviewed version. - Prefer an immutable package digest or verified release artifact where supported. 4. **Separate direct and transitive dependency management** - Record all resolved transitive packages in a lock file. - Use automated vulnerability scanning and dependency-update review. 5. **Harden installation guidance** - Recommend installation in a virtual environment. - Avoid running installation commands with root or administrator privileges. - Document the exact supported Python and package versions. 6. **Minimize dependencies** - Confirm that each dependency is required by the declared functionality. - In particular, keep image-generation dependencies optional if users only run the crawl and ranking pipeline. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (28)

Tainted flow: 'api_key' from os.environ.get (line 52, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
}

    try:
        resp = httpx.post(
            "https://api.x.ai/v1/responses",
            headers={
                "Authorization": f"Bearer {api_key}",
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'social_set_id' from os.environ.get (line 133, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
filename = Path(image_path).name

    # Request upload slot
    resp = httpx.post(
        f"{TYPEFULLY_BASE}/v2/social-sets/{social_set_id}/media/upload",
        headers=headers,
        json={"file_name": filename},
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'social_set_id' from os.environ.get (line 133, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
def create_draft(social_set_id, posts):
    """Create a draft on Typefully and return the API response."""
    resp = httpx.post(
        f"{TYPEFULLY_BASE}/v2/social-sets/{social_set_id}/drafts",
        headers=_headers(),
        json={
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'api_key' from os.environ.get (line 45, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
api_key = os.environ.get("ANTHROPIC_API_KEY")
    if not api_key:
        return None
    resp = httpx.post(
        "https://api.anthropic.com/v1/messages",
        headers={
            "x-api-key": api_key,
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'api_key' from os.environ.get (line 45, credential/environment) → httpx.post (network output)

Critical
Category
Data Flow
Content
api_key = os.environ.get("XAI_API_KEY")
    if not api_key:
        return None
    resp = httpx.post(
        "https://api.x.ai/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {api_key}",
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The README instructs users to run `npx clawhub@latest install eth24`, which fetches and executes the latest package version at install time. Because the version is not pinned, a compromised upstream package, malicious update, or dependency confusion event could cause arbitrary code execution on the user's machine during installation.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The top-level docstring states the file 'Crawl[s] Ethereum tweets via hybrid discovery,' which describes only tweet crawling. However, the implementation also retrieves RSS feed content in crawl_rss() and persists both tweets and RSS data to output/crawled.json, so the documentation understates and mischaracterizes the skill's actual behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
SCRIPT_DIR = Path(__file__).parent
CONFIG = json.loads((SCRIPT_DIR / "config.json").read_text())

X_API_BASE = "https://api.x.com/2"


# ---------------------------------------------------------------------------
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The query builder appends `lang:en`, which restricts results to English-language tweets by default. This is a natural-language policy concern because the skill forces a specific language/locale choice without offering configuration or documenting a justified regional limitation.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The manifest defines very generic trigger phrases such as "daily digest," "top tweets," and "daily briefing," which could match unrelated user requests and cause this skill to activate unexpectedly. In an agent ecosystem, overly broad invocation phrases can hijack routing away from more appropriate skills and may lead to unnecessary network activity or untrusted content retrieval without clear user intent.

Tainted flow: 'slot' from httpx.post (line 43, network input) → httpx.put (network output)

Medium
Category
Data Flow
Content
# PUT binary to presigned URL
    with open(image_path, "rb") as f:
        httpx.put(
            slot["upload_url"],
            content=f.read(),
            timeout=60,
Confidence
89% confidence
Finding
The code takes upload_url from a network response and immediately performs an unauthenticated PUT of local file contents to that URL without validating the host or scheme. If the upstream response is compromised, misrouted, or malicious, this becomes a server-side request forgery/data exfiltration primitive that can send arbitrary local file content to an attacker-controlled endpoint.

External Transmission

Medium
Category
Data Exfiltration
Content
api_key = os.environ.get("ANTHROPIC_API_KEY")
    if not api_key:
        return None
    resp = httpx.post(
        "https://api.anthropic.com/v1/messages",
        headers={
            "x-api-key": api_key,
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
api_key = os.environ.get("ANTHROPIC_API_KEY")
    if not api_key:
        return None
    resp = httpx.post(
        "https://api.anthropic.com/v1/messages",
        headers={
            "x-api-key": api_key,
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
api_key = os.environ.get("ANTHROPIC_API_KEY")
    if not api_key:
        return None
    resp = httpx.post(
        "https://api.anthropic.com/v1/messages",
        headers={
            "x-api-key": api_key,
Confidence
70% 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
95% confidence
Finding
The function sends tweet text, handles, URLs, engagement metrics, and RSS-derived content to Anthropic without any consent gate, redaction step, or prominent disclosure beyond a stderr progress message. In a skill context, this can unintentionally transmit third-party or sensitive input data to an external processor, creating privacy, compliance, and data-governance risk.

External Transmission

Medium
Category
Data Exfiltration
Content
if not api_key:
        return None
    resp = httpx.post(
        "https://api.anthropic.com/v1/messages",
        headers={
            "x-api-key": api_key,
            "anthropic-version": "2023-06-01",
Confidence
60% 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
95% confidence
Finding
The same unannounced data-sharing issue exists for xAI: collected tweet and RSS content is forwarded to a third-party model API without a clear warning or consent mechanism. Even if the source data is often public, bundling and transmitting it externally may violate operator expectations or policy requirements, especially if feeds can contain non-public or licensed content.

External Transmission

Medium
Category
Data Exfiltration
Content
if not api_key:
        return None
    resp = httpx.post(
        "https://api.x.ai/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if not api_key:
        return None
    resp = httpx.post(
        "https://api.x.ai/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
Confidence
60% 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

Low
Confidence
86% confidence
Finding
This markdown file explains that each run writes multiple artifacts under `output/YYYY-MM-DD/`, including `crawled.json` with raw tweet data and derived output files. While file output is part of the skill's behavior, there is no explicit warning or note that running the skill persists collected data locally, which may matter for user expectations around storage and cleanup.

Missing User Warnings

Low
Confidence
92% confidence
Finding
The skill instructs the agent to fetch data from external services and write results into local output files, but it does not warn the user about these side effects. This is dangerous because users may unknowingly trigger network access and filesystem writes, which can create privacy, operational, or compliance issues in restricted environments.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The script creates an output directory and writes a JSON file containing collected tweet and RSS data, but there is no prior warning, confirmation, or explanatory comment/docstring near the write operation. For a code file, file writes should have some visible disclosure unless clearly covered elsewhere; here the save only becomes apparent at execution time.

Unpinned Dependencies

Low
Category
Supply Chain
Content
feedparser
Pillow
httpx
Confidence
97% confidence
Finding
The dependency 'feedparser' is unpinned, which makes builds non-reproducible and can silently introduce vulnerable or incompatible versions over time. In a supply-chain context, this weakens reviewability and makes it impossible to verify whether a safe version is consistently installed.

Unverifiable Dependency: feedparser has 10 known advisory(ies) (CVE-2011-1157 (feedparser Cross-site Scripting vulnerability); CVE-2009-5065 (feedparser Cross-site Scripting vulnerability); CVE-2011-1158 (feedparser Cross-site Scripting vulnerability) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
The manifest references 'feedparser' without a version even though the package has multiple known advisories in its history. Because no version is specified, it is impossible to determine from this file whether deployment will use a fixed release or one affected by historical vulnerabilities.

Unpinned Dependencies

Low
Category
Supply Chain
Content
feedparser
Pillow
httpx
Confidence
97% confidence
Finding
The dependency 'Pillow' is unpinned, so installations may resolve to different versions across environments or over time. Because Pillow has had multiple security issues historically, leaving it unpinned increases the chance of unintentionally pulling an affected release.