Back to skill

Security audit

IdleClaw

Security checks for vulnerabilities and agentic risk

Overview

IdleClaw is a disclosed community Ollama inference-sharing skill; its network and local Ollama behavior matches its stated purpose, though users should treat shared prompts and dependency installation carefully.

Install only if you are comfortable sending prompts, responses, model names, and contributor-node metadata through the IdleClaw routing service or a server you configure. Avoid using plaintext http:// servers except local development, avoid sensitive prompts, and prefer installing dependencies in a dedicated virtual environment.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T08 · Insecure Dependencies

Warning
Location
install.sh:30
Finding

Unpinned and Unverified Third-Party Dependencies

Content
View full analysis
=0.4,<1.0 websockets>=14.0,<15.0 python-dotenv>=1.0,<2.0 httpx>=0.27,<1.0 ``` ### Technical Analysis The installer resolves dependencies from broad version ranges without exact version pins, package hashes, or a reviewed lock file. Consequently, the code installed on a future invocation may differ from the code that was available during this audit. Python package installation may execute package-controlled build or installation logic. If a dependency publisher account, package release, package index, or software distribution path is compromised, a malicious version satisfying one of these ranges could be selected and installed. The installation command also targets the currently active Python environment instead of creating a dedicated virtual environment, increasing the scope of any dependency conflict or compromise. The package names do not show evidence of typosquatting or dependency confusion in the audited files. The issue is the absence of reproducible, integrity-verified dependency resolution. ### Attack Path 1. An attacker compromises an allowed dependency release, its publisher account, or the package distribution path. 2. The attacker publishes a malicious version that remains within a configured version range. 3. A user runs `bash install.sh`. 4. `pip` resolves the new mutable version because no exact version or cryptographic hash is required. 5. Package-controlled installation or runtime code executes with the privileges of the user running the installer. 6. The compromised package can affect the Skill whenever its Python scripts import or invoke that dependency. ## ...[truncated 482 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/config.py:11
Finding

Plaintext Routing Endpoints Permit Interception and Modification of Inference Traffic

Content
View full analysis
str: """Read and validate the server URL from environment.""" url = os.environ.get("IDLECLAW_SERVER", DEFAULT_SERVER) if not re.match(r"^https?://", url): print(f"Warning: IDLECLAW_SERVER '{url}' is not an HTTP/HTTPS URL. Using default: {DEFAULT_SERVER}", file=sys.stderr) return DEFAULT_SERVER return url.rstrip("/") ``` ```python # scripts/consume.py:12-21 def stream_chat(server_url: str, model: str, prompt: str) -> None: """Send a chat request and stream the response.""" url = f"{server_url}/api/chat" payload = { "model": model, "messages": [{"role": "user", "content": prompt}], } try: with httpx.stream("POST", url, json=payload, timeout=120) as response: ``` ```python # scripts/contribute.py:112-127 async def run_node(server_url: str, models: list[dict], ollama_version: str) -> None: """Connect to server, register, and handle inference requests.""" node_id = str(uuid.uuid4()) ws_url = server_url.replace("https://", "wss://").replace("http://", "ws://") + "/ws/node" logger.info("Connecting to %s", ws_url) ws = await websockets.connect(ws_url) # Register await ws.send(json.dumps({ "type": "register", "node_id": node_id, "models": models, "max_concurrent": 2, "ollama_version": ollama_version, })) ``` ### Technical Analysis The URL validation explicitly accepts both `https://` and plaintext `http://` endpoints. Consumer mode consequently sends the model name and complete prompt over plaintext HTTP when such an endpoint is configured. Contributor mode converts ...[truncated 2146 chars]
Remediation
View remediation
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill invokes Python and Bash and explicitly performs outbound network communication, but it does not declare a restrictive tool scope such as permissions or allowed-tools. This creates a trust gap where the runtime may expose broader capabilities than users expect, increasing the risk of unauthorized network access or environment-variable use if the scripts behave differently than documented or are later modified.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The node forwards tool_calls emitted by the local Ollama model back to the community server, even though comments claim no local tool execution occurs. While this code does not execute tools locally, relaying tool call structures can still enable downstream systems to act on model-suggested tool invocations, creating a confused-deputy risk if the server or consumers treat them as trusted actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This skill connects to a community server, registers the local node, and accepts remote inference requests that are processed by the user's local Ollama instance, but the code provides only operational logging and no meaningful consent or warning about the privacy implications. In this context, the skill's stated purpose is community inference sharing, which makes the behavior intentional, but it still risks users exposing model outputs, prompts, and possibly tool-related metadata to third parties without sufficiently explicit disclosure at runtime.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: websockets has 4 known advisory(ies) (CVE-2018-1000518 (websockets is vulnerable to denial of service by memory exhaustion); CVE-2021-33880 (Observable Timing Discrepancy in aaugustin websockets library); CVE-2018-1000518 (aaugustin websockets version 4 contains a CWE-409: Improper Handling of Highly C) +1 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 dependency is range-pinned (>=14.0,<15.0) rather than locked to a specific vetted release, so builds may resolve to different websockets versions over time without assurance that all known vulnerable releases are excluded. In a skill that participates in community/shared inference and uses network communication, an affected websocket library could increase exposure to denial-of-service or protocol-handling weaknesses.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: python-dotenv has 2 known advisory(ies) (CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
80% confidence
Finding

python-dotenv is specified as a broad version range (>=1.0,<2.0) instead of an exact patched version, so the installed package may vary and could include vulnerable releases. While this package is less exposed than a network stack, dotenv handling can still become security-relevant if the skill writes .env files or operates in environments where symlink attacks or unsafe file writes are possible.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: httpx has 2 known advisory(ies) (CVE-2021-41945 (Improper Input Validation in httpx); CVE-2021-41945 (Encode OSS httpx <=1.0.0.beta0 is affected by improper input validation in `http)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
86% confidence
Finding

httpx is not fixed to a single reviewed version, leaving uncertainty about whether installations will pick a release affected by known input-validation issues. Because this skill interacts with community inference and likely makes outbound HTTP requests, weaknesses in an HTTP client can matter more here than in an offline-only tool.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/contribute.py (reported line 102)May include surrounding context.

python
message = chunk.get("message", {})
        msg_dict: dict = {}
        for key in ("role", "content", "thinking", "tool_calls"):
            val = message.get(key) if isinstance(message, dict) else getattr(message, key, None)
            if val is not None and val != "" and val != []:
                if key == "tool_calls" and isinstance(val, list):
                    val = [tc.model_dump() if hasattr(tc, "model_dump") else tc for tc in val]