Back to skill

Security audit

Metaskill

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly does what it claims, but it can silently send task details and saved learning notes to a different external LLM provider than the user selected.

Review this skill before installing if you handle confidential projects. Use it only if you are comfortable with task/error text and excerpts of saved learning notes being sent to configured LLM providers, and be aware that the current fallback logic may use a different remote provider when the selected one fails. Treat the .learnings and memory report files as persistent records that may need redaction or cleanup.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/llm_provider.py:43
Finding
Silent Cross-Provider Disclosure of Sensitive Task and Learning Data## Vulnerability Details **File Location**: `scripts/llm_provider.py:43-58`; data originates from `scripts/llm_extract.py:6-13` and `scripts/llm_transfer.py:5-14,31-48` **Vulnerability Type**: Unapproved external data transmission caused by automatic provider fallback **Risk Level**: High ### Vulnerable Code `scripts/llm_provider.py:43-58`: ```python def call_llm(prompt: str, provider_type: str = "fast", max_tokens: int = 500) -> str | None: """ Call LLM using configured provider. provider_type: "fast" (haiku-level) or "deep" (sonnet-level) Returns text string or None on failure. """ cfg = _load_config() if not cfg: return _fallback(prompt, max_tokens) provider = cfg.get("providers", {}).get(provider_type, "anthropic") model = cfg.get("models", {}).get(provider, {}).get(provider_type, None) env_var = cfg.get("env_vars", {}).get(provider, "") result = _call_provider(provider, model, prompt, max_tokens, env_var) if result is not None: return result # Fallback: try the other provider other_provider = "openai" if provider != "openai" else "anthropic" other_model = cfg.get("models", {}).get(other_provider, {}).get(provider_type) other_env = cfg.get("env_vars", {}).get(other_provider, "") return _call_provider(other_provider, other_model, prompt, max_tokens, other_env) ``` `scripts/llm_transfer.py:5-14`: ```python def get_analogous_principles(task_desc, learnings_content): prompt = f"""Given this task: "{task_desc}" Which of these past learnings are analogically relevant? Explain the connection. Return ONLY valid JSON in this format: {{"principles": [{{"principle": "...", "reasoning": "..."}}]}} Past learnings: {learnings_content}""" result = call_llm(prompt, provider_type="deep", max_tokens=500) ``` `scripts/llm_extract.py:6-13`: ```python def extract_levels(error_desc): prompt ...[truncated 3097 chars]
Remediation
## Remediation Suggestions 1. Remove automatic cross-provider fallback from `call_llm()`. Fail closed or use the existing offline heuristic when the selected provider fails. 2. Never fall back from Ollama to a remote provider unless the user has explicitly enabled remote fallback. 3. Add separate configuration such as: ```yaml fallback: enabled: false allowed_providers: [] ``` 4. Require affirmative consent before transmitting persistent learning records to any external provider. 5. Display the effective destination before submission, including whether processing is local or remote. 6. Fail closed when `config.yaml` cannot be parsed. Do not silently replace user configuration with remote-provider defaults. 7. Validate `provider_type`, provider names, and model selections against an explicit allowlist. 8. Add prompt redaction for common secrets, credentials, tokens, private keys, and sensitive identifiers. 9. Minimize submitted content and provide a local-only mode that cryptographically or structurally prevents remote network calls. 10. Add tests verifying that an Ollama failure never causes an external request unless explicit remote fallback consent is configured.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/eval.sh:125
Finding
Evaluation Script Recursively Reads the Entire Agent Memory Directory## Vulnerability Details **File Location**: `scripts/eval.sh:125` **Vulnerability Type**: Excessive filesystem access beyond the evaluation feature's minimum requirements **Risk Level**: Medium ### Vulnerable Code `scripts/eval.sh:125`: ```bash TRANSFER_USES=$(grep -rl "transfer-check" "$WORKSPACE/memory/" 2>/dev/null | wc -l | tr -d ' '); TRANSFER_USES=${TRANSFER_USES:-0} ``` ### Technical Analysis The monthly evaluation only needs to determine how often the transfer-check feature has been used. Instead of maintaining a dedicated counter or inspecting a Metaskill-specific audit record, the script recursively searches every readable file under `$WORKSPACE/memory/`. The `grep -r` operation opens unrelated memory files even though their contents are not required for Metaskill operation. Agent memory can contain conversation artifacts, confidential task context, user preferences, operational notes, or data created by unrelated skills. Suppressing errors with `2>/dev/null` also hides evidence that unexpected paths, inaccessible files, or special filesystem objects were encountered. The current implementation does not transmit the matched contents, but it violates least-privilege data-access principles and expands the sensitive-data exposure surface of the evaluation command. ### Attack Path 1. An operator runs `scripts/eval.sh`, including the documented monthly `--save` workflow. 2. The script resolves `$WORKSPACE` from the repository root or `OPENCLAW_WORKSPACE`. 3. `grep -rl` recursively traverses `$WORKSPACE/memory/`. 4. Every readable file encountered is opened and searched for the string `transfer-check`. 5. Unrelated agent memory is accessed even though only a usage count is needed. 6. If the memory tree contains links or files managed by other components, the scan can extend the accessed scope within the behavior supported by the local `grep` implementation and filesystem permissions. ### Impact Assessment ...[truncated 740 chars]
Remediation
## Remediation Suggestions 1. Record transfer-check invocations in a dedicated Metaskill-owned counter or append-only usage log. 2. Restrict evaluation to a documented file such as: ```text skills/metaskill/.learnings/USAGE.log ``` 3. If daily notes must be inspected, use an explicit allowlist of known filenames and a bounded date range rather than recursively scanning the entire memory tree. 4. Reject or carefully handle symbolic links when scanning any user-controlled directory. 5. Add file-count and file-size limits to prevent expensive unbounded scans. 6. Report access errors clearly instead of suppressing all diagnostics, while ensuring error output does not disclose sensitive paths. 7. Document the exact files the evaluation command accesses and ensure those accesses are limited to data necessary for the reported metrics.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Tainted flow: 'req' from os.environ.get (line 116, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
data = json.dumps({"model": model, "prompt": prompt, "stream": False}).encode()
            req = urllib.request.Request("http://localhost:11434/api/generate",
                                         data=data, headers={"Content-Type": "application/json"})
            with urllib.request.urlopen(req, timeout=30) as r:
                return json.loads(r.read())["response"]
        except Exception as e:
            print(f"[llm_provider] ollama error: {e}", file=sys.stderr)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 116, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}"
            body = json.dumps({"contents": [{"parts": [{"text": prompt}]}]}).encode()
            req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"})
            with urllib.request.urlopen(req, timeout=30) as r:
                resp = json.loads(r.read())
                return resp["candidates"][0]["content"]["parts"][0]["text"]
        except Exception as e:
Confidence
90% confidence
Finding
The Gemini request embeds the API key directly in the URL query string, which can expose credentials through logs, proxies, monitoring systems, or exception traces outside the application. While sending a request to an external provider is expected for this module, placing secrets in the URL is an avoidable credential-handling weakness.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared purpose frames the skill as reflective learning support, but the content also indicates configuration loading, API-key usage, provider routing, and communication with external LLM services. That mismatch is dangerous because users may invoke the skill expecting local reasoning assistance while it may actually access secrets and send task content to remote systems.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose frames the skill as reflective learning support, but the content also indicates configuration loading, API-key usage, provider routing, and communication with external LLM services. That mismatch is dangerous because users may invoke the skill expecting local reasoning assistance while it may actually access secrets and send task content to remote systems.

Self-Modification

High
Category
Rogue Agent
Content
echo "   Deep provider : $DEEP_PROVIDER / $DEEP_MODEL $([ "$DEEP_READY" = "True" ] && echo "✅" || echo "❌ missing key")"
  else
    echo "   Version: v1.0 fallback (Manual extraction mode)"
    echo "   No LLM provider ready. Edit skills/metaskill/config.yaml and set the required env var."
    echo "   Fast: $FAST_PROVIDER → needs \$$(python3 -c "import sys; sys.path.insert(0,'$SCRIPT_DIR'); from llm_provider import _load_config; c=_load_config(); print(c['env_vars'].get(c['providers']['fast'],'?'))" 2>/dev/null)"
  fi
else
Confidence
85% confidence
Finding
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises executable scripts and references capabilities such as environment-variable access, file reads, and network-backed providers, but it does not declare any explicit tool scope or permissions boundary. In an agent ecosystem, this can cause the agent or operator to underestimate what the skill may access or transmit, increasing the chance of unintended secret exposure or unauthorized network use.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script creates and writes to a persistent learnings file in the workspace without making that side effect explicit before use. Because it stores user-supplied error descriptions and derived principles/habits, it can persist sensitive operational details or secrets into a predictable location, increasing the risk of unintended retention and later disclosure.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script initializes a persistent file under the workspace without warning the user that running it may modify long-lived state. In a tool that accepts free-form error text, this can cause sensitive debugging context or internal data to be silently retained, which is a security and privacy concern even if the write itself is not malicious.

Context-Inappropriate Capability

Medium
Confidence
75% confidence
Finding
The manifest describes a skill for teaching agents to learn better through deep correction, transfer learning, and pattern recognition, but does not indicate that it will invoke a separate Python-based LLM extraction step. Calling an LLM helper may be an additional capability beyond the stated purpose, especially since the script presents it as an optional automated extraction mode rather than a necessary implementation detail.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The script appends the supplied error description, principle, and habit directly into a persistent markdown file without advance disclosure or sanitization. This creates a clear data retention risk: users may paste incident details, credentials, stack traces, or proprietary content expecting transient processing, but the script silently preserves them for future access.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The manifest describes a metaskill focused on deep correction, transfer learning, and pattern recognition workflows, but this file primarily acts as a general multi-provider LLM router that selects vendors and falls back across providers. That is a broader operational capability than the stated pedagogical/analysis purpose and is not described in the manifest text.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The manifest frames the skill as helping agents learn from errors and successes, not as credentialed integration infrastructure. Reading ANTHROPIC_API_KEY, OPENAI_API_KEY, and GOOGLE_API_KEY introduces secret-handling and external service access capabilities that are not explicitly justified by that purpose statement.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
User-supplied prompts are sent to third-party LLM providers with no user-facing disclosure or confirmation in the code path. In this skill's context, prompts may include reflective analysis, task context, or sensitive operational data, making silent external transmission more dangerous than in a clearly networked assistant integration.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The code can transmit arbitrary prompts to external services and a local inference endpoint without any visible consent, policy enforcement, or data classification checks. In a metaskill context, prompts may contain prior task history, errors, or sensitive workspace content, which increases the risk of unintended data exfiltration.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
try:
            import urllib.request, json
            data = json.dumps({"model": model, "prompt": prompt, "stream": False}).encode()
            req = urllib.request.Request("http://localhost:11434/api/generate",
                                         data=data, headers={"Content-Type": "application/json"})
            with urllib.request.urlopen(req, timeout=30) as r:
                return json.loads(r.read())["response"]
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The manifest frames the skill as teaching agents to learn better through correction, transfer learning, and pattern recognition, with this script specifically labeled as a pre-task analogy lookup. In addition to lookup, the code creates `.learnings` directories and initializes `LEARNINGS.md`, which is a behaviorally broader side effect than a read/check operation and is not implied by the script's stated lookup role.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The skill states that it writes learning data to disk, including into another skill's directory when present, but it does not prominently warn about persistence, retention, or the kinds of potentially sensitive task/error data that may be stored. This can lead to accidental retention of secrets, prompts, internal project details, or failure data in workspace files that are later committed, shared, or indexed.

Missing User Warnings

Low
Confidence
84% confidence
Finding
When run with --save, the script creates and overwrites a dated markdown report in the workspace, and earlier it also creates learning/error files if missing. Although the behavior is visible in code, there is no confirmation prompt and no user-facing warning at the point of write describing that files will be created or modified.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The function sends raw user-provided error text to an external LLM via `call_llm` without any disclosure, consent flow, or visible minimization in this file. Error descriptions often contain sensitive data such as file paths, stack traces, API keys, tokens, customer data, or internal prompts, so forwarding them off-box can create an unintended data exfiltration path.

Missing User Warnings

Low
Confidence
80% confidence
Finding
This shell script appends user-supplied arguments into WINS.md and may also create learning files earlier in execution, but it provides no upfront warning that it will modify files in the workspace. Although it prints a success message after writing, there is no pre-action disclosure, confirmation prompt, or other user-facing warning before the file modification occurs.

Intent-Code Divergence

Low
Confidence
80% confidence
Finding
The comment describes a passive fallback to an existing learnings directory, but the implementation actively mutates the workspace by creating the directory and seeding a markdown file. This is a meaningful intent mismatch because a reader would expect selection of an alternate source, not creation of new state.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The script silently creates the .learnings directory and initializes LEARNINGS.md if the fallback path is used. Although this is a modest file write, there is no confirmation prompt, no user-facing disclosure before the write, and the surrounding comments are developer-facing rather than warning the user about modifying workspace files.

Static analysis

No suspicious patterns detected.