Back to skill

Security audit

Grazer

Security checks for vulnerabilities and agentic risk

Overview

Grazer is broadly coherent with its stated discovery-and-engagement purpose, but its install guidance, networking, credential storage, and content-posting paths need manual review before use.

Install only after reviewing the README's privileged APT steps, and prefer npm or pip from a trusted source if you proceed. Use least-privilege API keys, create ~/.grazer/config.json with restrictive permissions, avoid giving the skill unattended authority to post or reply, avoid untrusted podcast feed or Mastodon instance URLs, and do not use the LLM SVG helper with HTTP endpoints or sensitive prompts/tokens.

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

T09 · Insecure Skill Coding Practices

Error
Location
grazer/podcast_grazer.py:122
Finding

Unrestricted Podcast Feed URL Enables Server-Side Request Forgery

Content
View full analysis
List[Dict]: """Fetch recent episodes from a podcast RSS feed. Args: feed_url: The podcast's RSS feed URL limit: Maximum episodes to return Returns: List of episode dicts with title, description, audio_url, etc. """ resp = self.session.get(feed_url, timeout=self.timeout) resp.raise_for_status() eps = _parse_podcast_rss(resp.text) return eps[:limit] ``` The method is exposed through the main client: ```python def podcast_episodes(self, feed_url: str, limit: int = 10) -> List[Dict]: """Fetch recent episodes from a podcast RSS feed URL.""" return self._podcast.episodes(feed_url, limit=limit) ``` ### Technical Analysis `feed_url` is passed directly to `requests.Session.get()` without validating its scheme, destination hostname, resolved IP address, port, or redirect chain. The request library follows HTTP redirects by default. Consequently, an untrusted caller can direct the process to arbitrary HTTP services reachable from its execution environment. This includes loopback services, private network hosts, link-local addresses, container-management endpoints, and cloud instance metadata services. A timeout does not prevent SSRF. There is also no response-size limit, so a hostile endpoint may return an excessively large body before the code accesses `resp.text`. ### Attack Path 1. An attacker gains control over the `feed_url` argument, directly or through an agent workflow that treats externally supplied podcast URLs as trusted. 2. The attacker supplies a URL such as: - `http://127.0.0.1:8080/internal` - `http://169.254.169.254/latest/meta-data/` - A public HTTPS URL that redirects to a ...[truncated 912 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
grazer/mastodon_grazer.py:25
Finding

Caller-Controlled Mastodon Instance Enables Requests to Internal Services

Content
View full analysis
str: inst = (instance or self.instance).rstrip("/") if not inst.startswith("http"): inst = f"https://{inst}" return f"{inst}/api/v1" def discover( self, query: str = "AI", instance: Optional[str] = None, limit: int = 10, ) -> List[Dict]: """Search public posts on a Mastodon instance. Args: query: Free-text search query instance: Instance hostname (default: mastodon.social) limit: Maximum results (max 40) Returns: List of post dicts """ base = self._api_url(instance) params = { "q": query, "type": "statuses", "limit": min(limit, 40), } resp = self.session.get( f"{base}/search", params=params, timeout=self.timeout, ) ``` The same unrestricted base URL is used by the trending-tag, trending-post, and public-timeline methods. ### Technical Analysis The `instance` argument may contain a full caller-selected URL. `_api_url()` only checks whether the string begins with the characters `http`; it does not validate the actual scheme, destination, DNS resolution, address range, port, or redirects. Supporting federated Mastodon servers requires connecting to multiple external hosts, but it does not require access to loopback, private, link-local, or reserved network destinations. The implementation therefore grants broader network privileges than necessary for public Fediverse discovery. ### Attack Path 1. An attacker influences an agent command or SDK call containing the Mastodon `instance` parameter. 2. The attacker supplies an internal destination, for example `http://127.0.0.1:3000`. 3. Grazer constructs `http://127.0.0.1:3000/api/ ...[truncated 1014 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
grazer/imagegen.py:231
Finding

Image Prompt and Bearer Credential May Be Transmitted over Plaintext HTTP

Content
View full analysis
str: """Generate SVG using any OpenAI-compatible LLM endpoint. Args: prompt: Image description llm_url: OpenAI-compatible chat completions endpoint llm_model: Model name/ID llm_api_key: Optional API key (Bearer token) temperature: Creativity (0.0-1.0) timeout: Request timeout in seconds Returns: Raw SVG string ready for 4claw media field """ headers = {"Content-Type": "application/json"} if llm_api_key: headers["Authorization"] = f"Bearer {llm_api_key}" payload = { "model": llm_model, "messages": [ {"role": "system", "content": LLM_SVG_SYSTEM_PROMPT}, {"role": "user", "content": f"Create an SVG image: {prompt}"}, ], "temperature": temperature, "max_tokens": 2048, } resp = requests.post(llm_url, json=payload, headers=headers, timeout=timeout) resp.raise_for_status() ``` ### Technical Analysis The public `generate_llm_svg()` helper defaults to a hard-coded endpoint in the carrier-grade NAT/private networking range and uses unencrypted HTTP. When `llm_api_key` is present, the function places it in the `Authorization` header and sends it over that plaintext connection. The user-supplied image prompt is also included in the request body. The main `GrazerClient` does not enable LLM generation unless an endpoint is configured, which limits automatic exposure through that path. However, direct callers of the exported `gene ...[truncated 1415 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
grazer/imagegen.py:177
Finding

LLM-Generated and Caller-Supplied SVG Content Is Uploaded without Active-Content Sanitization

Content
View full analysis
str: """Validate and sanitize SVG for 4claw.""" svg = svg.strip() # Must start with SVG_MAX_BYTES: raise ValueError(f"SVG exceeds 4KB limit ({len(svg.encode('utf-8'))} bytes)") return svg ``` Untrusted LLM output is extracted and passed to that incomplete validator: ```python resp = requests.post(llm_url, json=payload, headers=headers, timeout=timeout) resp.raise_for_status() content = resp.json()["choices"][0]["message"]["content"].strip() # Extract SVG if wrapped in code fences svg_match = re.search(r'', content) if not svg_match: raise ValueError("LLM did not produce valid SVG output") return _validate_svg(svg_match.group(0)) ``` Caller-supplied raw SVG bypasses even `_validate_svg()` before upload: ```python body = {"title": title, "content": content, "anon": anon} # Attach SVG media if provided or generated if svg: body["media"] = svg_to_media(svg) elif image_prompt: result = self.generate_image(image_prompt, template=template, palette=palette) body["media"] = svg_to_media(result["svg"]) resp = self._rate_limited_post( f"https://www.4claw.org/api/v1/boards/{board}/threads", json=body, headers={ "Authorization": f"Bearer {self.fourclaw_key}", "Content-Type": "application/json", }, ...[truncated 2261 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
grazer/cli.py:17
Finding

Credential Configuration Permissions Are Claimed but Not Verified or Enforced

Content
View full analysis
dict: """Load config from ~/.grazer/config.json.""" config_path = Path.home() / ".grazer" / "config.json" if not config_path.exists(): print("⚠️ No config found at ~/.grazer/config.json") print("Using limited features (public APIs only)") return {} return json.loads(config_path.read_text()) ``` The Skill documentation states: ```markdown - **No post-install telemetry** — no network calls during pip/npm install - **API keys in local config only** — keys read from `~/.grazer/config.json` (chmod 600) ``` The CLI also writes idempotency state without setting an explicit mode: ```python def _save_idempotency_cache(cache: dict, path: Optional[Path] = None) -> None: cache_path = path or _idempotency_cache_path() cache_path.parent.mkdir(parents=True, exist_ok=True) cache_path.write_text(json.dumps(cache, indent=2, sort_keys=True)) ``` ### Technical Analysis The configuration can hold credentials for numerous platforms and for the optional LLM provider. However, `load_config()` does not verify: - File ownership - File type - Symbolic-link status - Group or world-readable permission bits - Security of the parent directory The package does not create the documented configuration file or apply `chmod 600`. Therefore, the documentation's permission assertion is not enforced by the implementation. If a user creates the file under a permissive umask or places it in an inadequately protected home-directory hierarchy, other local users may be able to read all configured credentials. ### Attack Path 1. A user creates `~/.grazer/config.json` according to the documentation. 2. The file receives permissive permissions, such as `0644`, because of the creatio ...[truncated 1009 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (40)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 46)May include surrounding context.

APT (Debian/Ubuntu)

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 47)May include surrounding context.

APT (Debian/Ubuntu)

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Chaining Abuse

High
Category
Tool Misuse
Confidence
95% confidence
Finding

Piping curl output directly into a sudo-privileged gpg command is a risky command chain because it combines remote content retrieval and privileged trust establishment in one step. If the endpoint, DNS, TLS, or hosting environment is compromised, an attacker could install a malicious signing key that grants long-term package trust.

Content

Scanner excerpt · README.md (reported line 46)May include surrounding context.

APT (Debian/Ubuntu)

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Chaining Abuse

High
Category
Tool Misuse
Confidence
84% confidence
Finding

Although this line is an echo piped to sudo tee rather than remote code execution, it still uses a privileged command chain to write persistent package source configuration non-interactively. That pattern lowers user scrutiny and helps a third-party repository become trusted system state with minimal review.

Content

Scanner excerpt · README.md (reported line 47)May include surrounding context.

APT (Debian/Ubuntu)

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Chaining Abuse

High
Category
Tool Misuse
Confidence
86% confidence
Finding

The chained sudo apt update && sudo apt install command encourages one-shot execution of repository refresh and package installation without review. In combination with the just-added third-party source, this reduces operator checkpoints and increases the chance of immediate installation of malicious or unreviewed packages if the repo is compromised.

Content

Scanner excerpt · README.md (reported line 48)May include surrounding context.

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Claude Code

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The SEO heartbeat and backlink features are unrelated to core content discovery and enable the library to programmatically create or refresh crawlable profiles and promotional links on a third-party relay. In an agent skill context, this can be abused for stealth marketing, reputation manipulation, or covert external signaling using provided credentials and URLs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The APT instructions add a third-party repository, import a signing key, and modify system package sources, but the README does not provide an explicit safety warning or trust guidance. This is dangerous because users may paste privileged commands that establish persistent software trust and future package installation authority for an external source.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
92% confidence
Finding

This command pipes network-fetched content directly into a privileged operation using sudo to write a trusted key into the system keyring. Even if intended as normal installation guidance, running remote-content processing as root increases the blast radius of tampering, MITM, or endpoint compromise.

Content

Scanner excerpt · README.md (reported line 46)May include surrounding context.

APT (Debian/Ubuntu)

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
90% confidence
Finding

This command uses sudo to write a new APT source file under /etc/apt/sources.list.d, which changes system-wide package trust behavior. While common in installation docs, it is still a privileged persistence step that can expose users to malicious or compromised packages from the configured repository.

Content

Scanner excerpt · README.md (reported line 47)May include surrounding context.

APT (Debian/Ubuntu)

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
88% confidence
Finding

Running apt update and apt install with sudo performs system-wide package changes from the newly added third-party repository. In the context of immediately preceding repository setup, this can rapidly convert a bad repository configuration into arbitrary code execution through package installation.

Content

Scanner excerpt · README.md (reported line 48)May include surrounding context.

bash
curl -fsSL https://bottube.ai/apt ⚠️ *currently offline*/gpg ⚠️ *currently offline* | sudo gpg --dearmor -o /usr/share/keyrings/grazer.gpg
echo "deb [signed-by=/usr/share/keyrings/grazer.gpg] https://bottube.ai/apt ⚠️ *currently offline* stable main" | sudo tee /etc/apt/sources.list.d/grazer.list
sudo apt update && sudo apt install grazer

Claude Code

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 74)May include surrounding context.

md
# Browse 4claw /crypto/ board
grazer discover -p fourclaw -b crypto

# Create a 4claw thread
grazer post -p fourclaw -b singularity -t "Title" -m "Content"

# Reply to a 4claw thread

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README explicitly states that package installs are reported to remote telemetry endpoints, but it does not clearly disclose what data is sent, whether reporting can be disabled, or obtain informed consent before installation. Silent or poorly disclosed telemetry is a supply-chain privacy risk because operators may install the tool in sensitive environments without realizing outbound reporting occurs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill advertises autonomous discovery, notifications, posting, and auto-responses across many third-party platforms, but the description does not prominently warn users that enabling the skill can cause outbound actions on external services. This is dangerous because users may invoke it assuming read-only behavior and unintentionally spam, impersonate, or trigger account actions using stored API credentials.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

The skill instructs users to persist multiple API keys and tokens in a long-lived config file under the home directory. Persistent credential storage increases risk of token theft through local compromise, backups, permissive permissions, or accidental inclusion in support bundles, and the skill's broad cross-platform write capabilities make those tokens more sensitive.

Content

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

Configuration

Create ~/.grazer/config.json:

json
{

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
## Security

- **No post-install telemetry** — no network calls during pip/npm install
- **API keys in local config only** — keys read from `~/.grazer/config.json` (chmod 600)
- **Read-only by default** — discovery and browsing require no write permissions
- **No arbitrary code execution** — all logic is auditable Python/TypeScript
- **Source available** — full source on GitHub for audit

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 · grazer/__init__.py (reported line 47)May include surrounding context.

python
"semantic_scholar": {"url": "https://api.semanticscholar.org/graph/v1/", "auth": False},
    "openreview":   {"url": "https://api2.openreview.net/",            "auth": False},
    "mastodon":     {"url": "https://mastodon.social/api/v1/",         "auth": False},
    "nostr":        {"url": "https://api.nostr.band/",                 "auth": False},
}

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The package exposes broad write-capable actions across many third-party services, including posting, commenting, liking, replying, connecting, hiring, and registration, which materially exceed a narrow discovery-only capability. In an agent environment, this expanded action surface increases the risk of account misuse, spam, unauthorized external actions, and credential abuse if the skill is granted secrets or invoked by untrusted workflows.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The module contains a hidden telemetry function that reports download/install metadata to an external service, which is outside the clearly advertised content-discovery purpose. Even though the payload is limited to platform, version, skill name, and timestamp, undisclosed outbound tracking violates user expectations and can create privacy/compliance risk when invoked automatically.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The download reporting function transmits installation metadata to an external endpoint without any visible user-facing disclosure in this file. Silent telemetry is risky because operators may unknowingly leak usage data to a vendor, creating privacy, trust, and policy issues even if no highly sensitive payload is sent.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
75% confidence
Finding

No manifest is available, so the only stated intent in this file is discovery/content browsing language. In contrast, the code implements active posting and commenting across multiple external platforms, which is a materially different capability than discovery and is not justified by the limited documentation present here.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The top-level CLI description says "Grazer - Content discovery for AI agents" and the module docstring is similarly discovery-oriented, which implies a primarily read-oriented tool. However, the CLI also exposes commands that publish comments and posts to multiple platforms and can save generated SVG output to disk, expanding behavior beyond discovery.

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 · grazer/__init__.py (reported line 43)May include surrounding context.

python
from typing import List, Dict, Optional


NEYNAR_API_BASE = "https://api.neynar.com/v2/farcaster"


class FarcasterGrazer:

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 · grazer/farcaster_grazer.py (reported line 11)May include surrounding context.

python
from typing import List, Dict, Optional


NEYNAR_API_BASE = "https://api.neynar.com/v2/farcaster"


class FarcasterGrazer:

External Transmission

Medium
Category
Data Exfiltration
Confidence
98% confidence
Finding

This code performs a network POST containing prompt data to an external endpoint, which is a true external data transmission path. In this skill's context, prompts are free-form user input for image generation and could easily include private or proprietary text, so sending them to a remote service—especially the default non-TLS endpoint—introduces exposure and potential man-in-the-middle modification risks.

Content

Scanner excerpt · grazer/imagegen.py (reported line 266)May include surrounding context.

python
"max_tokens": 2048,
    }

    resp = requests.post(llm_url, json=payload, headers=headers, timeout=timeout)
    resp.raise_for_status()

    content = resp.json()["choices"][0]["message"]["content"].strip()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The function sends the user prompt to an external LLM endpoint, and the default URL is a plain HTTP address on a specific host. That creates a real confidentiality risk because prompt content may contain sensitive data and is transmitted off-box without any disclosure or consent mechanism in this file; using HTTP also permits interception or tampering in transit.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.exposed_resource_identifier, suspicious.exposed_secret_literal

Plaintext HTTP endpoint targets a CGNAT/Tailscale-range address.

Critical
Code
suspicious.exposed_resource_identifier
Location
grazer/imagegen.py:233

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
grazer/__init__.py:155