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<![CDATA[ ## Vulnerability Details **File Location**: `grazer/podcast_grazer.py:122-139` **Vulnerability Type**: Server-Side Request Forgery through an unrestricted user-supplied URL **Risk Level**: High ### Vulnerable Code ```python def episodes( self, feed_url: str, limit: int = 10, ) -> 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
<![CDATA[ ## Remediation Suggestions 1. Permit only explicitly supported schemes, normally `https`. 2. Parse URLs with `urllib.parse.urlsplit()` and reject embedded credentials, malformed hosts, and unexpected ports. 3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6. 4. Disable automatic redirects or validate every redirect destination using the same policy. 5. Protect against DNS rebinding by ensuring that the validated address is the address used for the connection. 6. Set strict connection and read timeouts. 7. Stream the response and enforce a conservative maximum size before parsing. 8. Consider allowing only feed URLs returned by a trusted podcast registry, while still validating redirect targets. 9. In environments where private feeds are required, use an explicit opt-in allowlist rather than accepting arbitrary destinations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
grazer/mastodon_grazer.py:25
Finding
Caller-Controlled Mastodon Instance Enables Requests to Internal Services<![CDATA[ ## Vulnerability Details **File Location**: `grazer/mastodon_grazer.py:25-56` **Vulnerability Type**: Server-Side Request Forgery through an unrestricted instance URL **Risk Level**: High ### Vulnerable Code ```python def _api_url(self, instance: Optional[str] = None) -> 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
<![CDATA[ ## Remediation Suggestions 1. Parse the instance using a strict URL parser rather than `startswith("http")`. 2. Require HTTPS by default and reject all other schemes. 3. Reject URLs containing user information, fragments, or unexpected path components. 4. Resolve the hostname and block loopback, private, link-local, reserved, multicast, and unspecified addresses for IPv4 and IPv6. 5. Validate every redirect destination or disable redirects. 6. Restrict destination ports to `443` unless a port is explicitly approved. 7. Optionally maintain an allowlist of known public Mastodon instances. 8. Apply the validation centrally in `_api_url()` so every Mastodon operation receives the same protection. 9. Use an egress firewall as defense in depth to prevent the Grazer process from accessing metadata and internal administrative networks. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
grazer/imagegen.py:231
Finding
Image Prompt and Bearer Credential May Be Transmitted over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `grazer/imagegen.py:231-269` **Vulnerability Type**: Plaintext transmission of sensitive data to a hard-coded private-network endpoint **Risk Level**: High ### Vulnerable Code ```python def generate_llm_svg( prompt: str, llm_url: str = "http://100.75.100.89:8080/v1/chat/completions", llm_model: str = "gpt-oss-120b", llm_api_key: Optional[str] = None, temperature: float = 0.8, timeout: int = 60, ) -> 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
<![CDATA[ ## Remediation Suggestions 1. Remove the hard-coded endpoint and require callers to explicitly configure an LLM service. 2. Require HTTPS whenever an API key is supplied. 3. Reject plaintext HTTP by default; if local HTTP support is essential, require a clearly named unsafe opt-in and prohibit credentials on that connection. 4. Validate the endpoint using the same SSRF protections applied to other caller-controlled URLs. 5. Do not permit private or link-local destinations unless they are explicitly allowlisted by the operator. 6. Avoid logging prompts, authorization headers, or complete request bodies. 7. Document exactly what information is sent to the configured LLM provider. 8. Consider using separate, narrowly scoped tokens for image generation. 9. Verify TLS certificates normally and do not expose an option that silently disables certificate validation. ]]>

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<![CDATA[ ## Vulnerability Details **File Location**: `grazer/imagegen.py:177-189` and `grazer/imagegen.py:266-276`; upload path at `grazer/__init__.py:520-541` **Vulnerability Type**: Unsafe SVG acceptance permitting active content and external references **Risk Level**: High ### Vulnerable Code The validation function checks only the root prefix, namespace, and size: ```python def _validate_svg(svg: str) -> str: """Validate and sanitize SVG for 4claw.""" svg = svg.strip() # Must start with <svg if not svg.startswith("<svg"): raise ValueError("Generated content is not valid SVG") # Ensure xmlns is present if 'xmlns=' not in svg: svg = svg.replace("<svg", f'<svg {SVG_NAMESPACE}', 1) # Size check if len(svg.encode("utf-8")) > 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'<svg[\s\S]*?</svg>', 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
<![CDATA[ ## Remediation Suggestions 1. Parse SVG as XML using a hardened parser with external entities and DTD processing disabled. 2. Enforce an allowlist of required SVG elements and attributes. 3. Remove at minimum: - `script` - `foreignObject` - Inline event-handler attributes - External stylesheets - External image, font, and resource references - `javascript:` and unsafe `data:` URLs - XML entity and DTD declarations 4. Restrict `href` and `xlink:href` to safe local fragment references where needed. 5. Validate every raw SVG argument before `svg_to_media()` is called. 6. Apply the same sanitizer to template, LLM, and direct-input paths. 7. Prefer generating SVG through a controlled internal object model rather than accepting arbitrary markup. 8. Add security tests containing scripts, event handlers, mixed-case attributes, encoded protocols, namespace tricks, comments, and malformed XML. 9. Treat remote LLM output as fully untrusted regardless of the configured provider. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
grazer/cli.py:17
Finding
Credential Configuration Permissions Are Claimed but Not Verified or Enforced<![CDATA[ ## Vulnerability Details **File Location**: `grazer/cli.py:17-24`; conflicting security claim at `SKILL.md:165-166` **Vulnerability Type**: Insecure local credential-file handling **Risk Level**: Medium ### Vulnerable Code ```python def load_config() -> 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
<![CDATA[ ## Remediation Suggestions 1. Verify that the configuration is a regular file owned by the current user. 2. Reject symbolic links and unexpected file types. 3. On POSIX systems, reject group-readable, group-writable, world-readable, or world-writable modes. 4. Require the parent `~/.grazer` directory to be owned by the user and set to `0700`. 5. When creating configuration or state files, use an atomic open operation with mode `0600`. 6. Apply `chmod(0o600)` after atomic replacement as defense in depth. 7. Display a clear error rather than silently loading credentials from an insecure file. 8. Provide a secure initialization command that creates both the directory and file with restrictive permissions. 9. Clarify Windows behavior using appropriate access-control lists rather than POSIX mode terminology. 10. Avoid storing credentials that can instead be supplied through an operating-system credential manager or dedicated secret store. ]]>
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
Content
### 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
```
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### 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
```
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Chaining Abuse

High
Category
Tool Misuse
Content
### 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
```
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.

Chaining Abuse

High
Category
Tool Misuse
Content
### 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
```
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.

Chaining Abuse

High
Category
Tool Misuse
Content
```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
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.

Context-Inappropriate Capability

High
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.

Missing User Warnings

Medium
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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
### 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
```
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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
### 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
```
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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
```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
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.

Session Persistence

Medium
Category
Rogue Agent
Content
# 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
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.

Missing User Warnings

Medium
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.

Missing User Warnings

Medium
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.

Session Persistence

Medium
Category
Rogue Agent
Content
## Configuration

Create `~/.grazer/config.json`:

```json
{
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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
## 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
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
"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},
}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
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.

Intent-Code Divergence

Medium
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.

Missing User Warnings

Medium
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.

Context-Inappropriate Capability

Medium
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.

Description-Behavior Mismatch

Medium
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.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import List, Dict, Optional


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


class FarcasterGrazer:
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
from typing import List, Dict, Optional


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


class FarcasterGrazer:
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
"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()
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.

Missing User Warnings

Medium
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.

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