Back to skill

Security audit

Multi-LLM Ensemble (MoA)

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it can automatically send sensitive prompts to several external AI providers and write model output to broad user-selected file paths without enough safeguards.

Install only if you are comfortable sending task prompts, code, and intermediate model outputs to the configured AI providers. Avoid using it with secrets, regulated data, private source code, or trading/financial decisions unless you have provider approval and have reviewed the exact models/endpoints. Keep OLLAMA_HOST local or trusted, and avoid using --output for startup files, shell configs, repo hooks, credentials, or agent memory/configuration files.

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
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ensemble.py:118
Finding

Arbitrary Ollama Endpoint Can Receive Confidential Task Data

Content
View full analysis
str: parsed = urlparse(host) if parsed.scheme not in ("http", "https"): raise ValueError( f"OLLAMA_HOST must use http:// or https://, got: {host!r}" ) if not parsed.netloc: raise ValueError(f"OLLAMA_HOST has no hostname: {host!r}") return host.rstrip("/") ``` ```python ollama_host = os.environ.get("OLLAMA_HOST", "") ollama_ok = False if ollama_host: try: validate_ollama_host(ollama_host) ollama_ok = True ``` ```python async def query_ollama(model: str, prompt: str) -> str: host = validate_ollama_host( os.environ.get("OLLAMA_HOST", "http://localhost:11434") ) client = _get_httpx() r = await client.post( f"{host}/api/generate", json={"model": model, "prompt": prompt, "stream": False}, ) r.raise_for_status() return r.json()["response"] ``` ### Technical Analysis `validate_ollama_host()` verifies only that the URL uses HTTP or HTTPS and contains a network location. It does not restrict the destination to localhost, require explicit approval for a remote host, require TLS for remote connections, or prevent connections to attacker-controlled and infrastructure-sensitive addresses. When `OLLAMA_HOST` is present and passes this minimal validation, the endpoint is treated as an available provider. The script first queries its `/api/tags` endpoint and can then transmit the complete task prompt to `/api/generate`. This creates both a confidentiality risk and a trust-boundary violation. Plain HTTP is accepted, so prompts may also be exposed to interception or modification in transit. ### Attack Path 1. An attacker influences the environment or configurati ...[truncated 1216 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
scripts/ensemble.py:288
Finding

Untrusted Model Responses Can Inject Instructions into the Synthesizer

Content
View full analysis
str: parts = [f"## Original Task\n{original_prompt}\n\n## Model Responses\n"] for i, r in enumerate(responses, 1): content = _truncate(r.content, MAX_RESPONSE_CHARS) parts.append(f"### Response {i} ({r.provider}/{r.model})\n{content}\n") parts.append("\n## Synthesized Answer") return "\n".join(parts) ``` ```python async def synthesize( provider: str, model: str, original_prompt: str, responses: list[ModelResponse], ) -> str: synthesis_prompt = build_synthesis_prompt(original_prompt, responses) messages = [{"role": "user", "content": synthesis_prompt}] async def _run() -> str: if provider == "anthropic": return await query_anthropic(model, messages, system=SYNTHESIS_SYSTEM) if provider == "openai": full = [{"role": "system", "content": SYNTHESIS_SYSTEM}] + messages return await query_openai(model, full) if provider == "gemini": return await query_gemini(model, f"{SYNTHESIS_SYSTEM}\n\n{synthesis_prompt}") if provider == "ollama": return await query_ollama(model, f"{SYNTHESIS_SYSTEM}\n\n{synthesis_prompt}") raise ValueError(f"Unknown sy ...[truncated 2094 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ensemble.py:130
Finding

Output Path Validation Allows Overwriting Startup and Security-Sensitive User Files

Content
View full analysis
pathlib.Path: resolved = pathlib.Path(path).resolve() resolved_str = str(resolved) for prefix in _SENSITIVE_PREFIXES: if resolved_str.lower().startswith(prefix.lower()): raise ValueError( f"Refusing to write to sensitive path: {resolved_str}" ) return resolved ``` ```python out_path: Optional[pathlib.Path] = None if args.output: try: out_path = validate_output_path(args.output) except ValueError as e: print(f"[error] {e}", file=sys.stderr) sys.exit(1) ``` ```python if out_path: out_path.write_text(output, encoding="utf-8") print(f"[done] Result saved to {out_path}", file=sys.stderr) ``` ### Technical Analysis The output-path control uses a short denylist of system directory prefixes. It does not confine output to a dedicated results directory and does not block many writable security-sensitive locations in a user's home directory or project workspace. Examples include shell startup files, SSH configuration and authorization files, repository hooks, application startup configuration, and persistent agent state. `Path.write_text()` opens the destination for writing and truncates an existing regular file. Resolving the path prevents simple lexical traversal but does not make the destination safe. The primary security problem is that nearly any path writable by the current process remains accepted. ### Attack Path 1. The skill is invoked with an output path targeting a writable startup, ...[truncated 1158 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/ensemble.py:2
Finding

Floating Runtime Dependencies Create an Uncontrolled Supply-Chain Boundary

Content
View full analysis
=3.10" # dependencies = [ # "anthropic>=0.40.0", # "openai>=1.0.0", # "google-genai>=1.0.0", # "httpx>=0.27.0", # ] # /// ``` ### Technical Analysis All third-party packages use lower-bound-only version constraints. No dependency lockfile or package hashes are present in the audited project. When the script is run through `uv`, dependency resolution can select future package versions that were not reviewed with the skill. The package names are consistent with the intended providers, and the audit found no evidence that the currently named packages are malicious. The weakness is the lack of reproducibility and integrity pinning. A compromised upstream release, malicious transitive dependency update, or incompatible future version satisfying these broad constraints could be installed and imported with the privileges of the user running the skill. ### Attack Path 1. An unsafe or compromised future package release satisfies one of the lower-bound constraints. 2. A user invokes the script in an environment that resolves dependencies without a reviewed lockfile. 3. `uv` downloads and installs the newly selected package or transitive dependency. 4. Installation-time or import-time package code executes with the permissions of the user running the skill. 5. The compromised dependency can access process data, environment variables, prompts, files accessible to the user, and outbound network capabilities. ### Impact Assessment A malicious dependency would execute within the Python process and inherit the running user's operating-system permissions. It could access provider credentials exposed through environment variables, read or modify user-accessible files, intercept prompts and responses, or communicate externally. A ...[truncated 157 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill documents behavior that uses environment variables, writes output files, and sends prompts to external providers, but it declares no explicit tool scope or permissions boundary. That mismatch increases the chance the agent can invoke networked and file-writing behavior without clear review, making accidental data exfiltration or unsafe execution more likely in a high-stakes multi-provider workflow.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill explicitly queries multiple external LLM providers in parallel, but the description does not provide a clear user-facing warning that their prompt may be transmitted to third parties. In this context, the omission is particularly dangerous because the skill is positioned for code review, research, and high-stakes decision support, where prompts may contain confidential or regulated data.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger criteria are very broad: 'complex reasoning,' 'high-stakes decisions,' 'code review,' 'research,' and any case where one model seems insufficient. In practice, that can cause over-invocation on sensitive tasks and unnecessarily route confidential user content to several external models, amplifying privacy and compliance risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends the full user prompt and the synthesized prompt containing multiple model outputs to third-party providers (Anthropic, OpenAI, Gemini, and optionally Ollama) without any built-in notice, consent gate, or data-classification control. In an agent setting, prompts often contain sensitive user data, proprietary code, credentials, or internal context, so silently fan-out transmission materially increases confidentiality and compliance risk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The usage example and key option descriptions are written in Korean, while the rest of the skill metadata is in English, and there is no indication that language choice is optional or region-specific. This can violate a language/locale policy when a skill implicitly imposes one language without giving the user a choice.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

이 가이드는 제목부터 본문 전체까지 한국어로만 제공되며, 다른 언어 선택권이나 다국어 안내가 없습니다. 조직의 언어/로케일 정책상 사용자 opt-in 없이 특정 언어를 강제하는 경우 자연어 정책 위반에 해당할 수 있습니다.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.