Back to skill

Security audit

OpenCode ACP Control

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent OpenCode automation purpose, but it includes unsafe update/install guidance and a runnable demo that automatically approves tool permissions.

Install only if you are comfortable with an agent spawning OpenCode with filesystem-write and terminal authority. Do not run the demo in a sensitive workspace or with secrets available, and avoid the documented curl-to-bash installer path unless you independently verify the downloaded installer first. Treat permission prompts as security decisions and prefer manual per-request review.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T03 · Remote Payload Retrieval and Execution

Error
Location
SKILL.md:213
Finding

Unpinned Remote Installer Is Downloaded and Executed Directly

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 213-216
Vulnerability Type: Remote payload retrieval and execution
Risk Level: Critical

Vulnerable Code

text
install (review the installer script before piping `curl | bash`):

curl -fsSL https://opencode.ai/install | bash

text

Technical Analysis

The installation fallback pipes mutable content retrieved from an external URL directly into a shell. The downloaded script is not pinned to an immutable version, authenticated with a cryptographic signature, or checked against an expected digest before execution.

Although the surrounding instruction recommends reviewing the installer, the command itself provides no review step: the response body is immediately interpreted by bash. Consequently, the effective code executed by the Skill can change after the Skill package has been reviewed.

Executing a remote installer is not required for the Skill's core ACP session-control functionality. Including it as an update fallback therefore expands the privilege and supply-chain exposure beyond the minimum necessary behavior.

CHANGELOG.md:56 records that a safety note was added, but this documentation-only warning does not technically mitigate the unsafe execution path.

Attack Path

  1. The agent checks the installed OpenCode version and determines that an update is available.
  2. The documented restart-based automatic update does not produce the expected version.
  3. The agent follows the fallback command in SKILL.md.
  4. curl retrieves the current response from https://opencode.ai/install.
  5. A compromised website, hosting environment, DNS or delivery path, or maliciously modified upstream installer supplies attacker-controlled shell code.
  6. The response is passed directly to bash without review or integrity verification.
  7. The attacker-controlled code executes with the operating-system privileges and environment inherited ...[truncated 851 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the direct curl | bash pipeline from the Skill.
  2. Prefer installation through a trusted package manager that supports pinned versions and package-signature verification.
  3. If a standalone installer must be supported:
    • Pin an immutable release artifact rather than a mutable installer endpoint.
    • Download the artifact to a dedicated file without executing it.
    • Verify a vendor-published cryptographic signature or pinned SHA-256 digest.
    • Display the verified script or provide its path for review.
    • Require explicit user approval before execution.
    • Execute it with the least-privileged account and in a constrained environment.
  4. Do not allow the agent to infer approval merely because an update is available or a restart-based update failed.
  5. Document the expected release version, artifact URL, checksum source, and verification procedure.
  6. Treat verification failure, redirects to unexpected hosts, and transport errors as hard failures rather than reasons to execute an unverified response.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
examples/acp_demo.py:143
Finding

ACP Demo Automatically Approves Arbitrary Tool Permission Requests

Content
View full analysis

Vulnerability Details

File Location: examples/acp_demo.py, lines 143-159
Vulnerability Type: Permission-boundary bypass through unconditional approval
Risk Level: High

Vulnerable Code

python
        # Handle Server-to-Client Requests (e.g., requestPermission)
        if "method" in msg and "id" in msg:
            if msg["method"] == "requestPermission":
                params = msg.get("params", {})
                tool_call = params.get("toolCall", {})
                title = tool_call.get("title", "unknown")
                print(f"[demo] server request: requestPermission for '{title}' (id={msg['id']})")
                
                # Auto-approve permission request to prevent deadlocks
                reply = {
                    "jsonrpc": "2.0",
                    "id": msg["id"],
                    "result": {"reply": "once"}
                }
                proc.stdin.write(frame(reply))
                proc.stdin.flush()
                print(f"[demo] auto-approved permission request id={msg['id']}")
            continue

Technical Analysis

The demo responds to every requestPermission server request with {"reply":"once"}. It extracts only the tool title for logging and does not validate the requested command, operation kind, arguments, target path, network destination, or session identity. It also does not obtain user confirmation.

This defeats the purpose of the ACP permission boundary. A response of once is narrower than session-wide always approval, but it still authorizes the complete requested operation. An attacker or prompt-influenced model can submit successive requests and receive a fresh approval for each one.

The risk is amplified because the ACP initialization declares filesystem read, filesystem write, and terminal capabilities. The spawned OpenCode process also inherits the invoking user's environment and filesystem access. The beha ...[truncated 2073 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove unconditional permission approval and default unknown or unattended requests to reject.
  2. Before requesting user consent, display the complete normalized request, including:
    • Tool kind and title.
    • Command and arguments.
    • Working directory.
    • Filesystem paths and requested operation.
    • Network destinations, where applicable.
    • Session and tool-call identifiers.
  3. Require explicit, per-request user confirmation before sending reply: once.
  4. Never provide reply: always by default.
  5. Introduce restrictive allowlists for demo-safe operations and reject shell metacharacters, unexpected executables, paths outside the selected project, and other high-risk inputs.
  6. Add a clearly named opt-in flag if unattended approval is necessary for isolated testing. The flag should emit a prominent warning and require a sandbox with no sensitive credentials.
  7. Run OpenCode in a constrained environment with:
    • Project-scoped filesystem access.
    • A minimal environment without credentials.
    • Restricted network access.
    • A non-privileged operating-system account.
  8. Add tests verifying that unknown requests, dangerous commands, malformed permission messages, and out-of-scope paths are rejected, and that approval requires explicit user input.
  9. Update the README to disclose the permission model and safe execution requirements.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (20)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · CHANGELOG.md (reported line 56)May include surrounding context.

md
- `SKILL.md` release-check URL pointed at `anomalyco/opencode` (404). Corrected to `sst/opencode`.
- `SKILL.md` `updateOpenCode()` example also pointed at `anomalyco/opencode`; corrected to `sst/opencode`.
- `SKILL.md` install URL was `opencode.dev`. Corrected to `opencode.ai`.
- Added safety note alongside the `curl | bash` install command.
- `README.md` referenced a non-existent `hermes skill install` subcommand. Replaced with the two real copy-into-skills-dir patterns.
- Version drift: `_meta.json` said `0.1.0`, `SKILL.md` frontmatter said `1.0.2`. Aligned both to `0.2.0`.

External Script Fetching

High
Category
Supply Chain
Confidence
98% confidence
Finding

The skill recommends falling back to a manual install by piping a remotely fetched script directly into bash, which executes unverified code from the network in the local environment. Even though it says to review the installer script first, the provided one-liner normalizes a dangerous pattern that bypasses integrity verification and can lead to full host compromise if the source, transport, or endpoint is tampered with.

Content

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

  • terminal("opencode acp", background:true) to restart and trigger the auto-download.
  1. Verify with opencode --version again. If still old, fall back to a manual install (review the installer script before piping curl | bash):

    text
    curl -fsSL https://opencode.ai/install | bash
    

External Script Fetching

High
Category
Supply Chain
Confidence
99% confidence
Finding

The explicit command curl -fsSL https://opencode.ai/install | bash is a textbook remote code execution pattern. In a skill meant for autonomous agents with shell access, this is especially dangerous because an agent may execute it non-interactively, granting immediate execution of attacker-controlled or modified code with the agent's privileges.

Content

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

install (review the installer script before piping curl | bash):

text
curl -fsSL https://opencode.ai/install | bash

Implementation Notes

Chaining Abuse

High
Category
Tool Misuse
Confidence
98% confidence
Finding

The shell pipeline into bash is a chaining pattern that turns remote content retrieval directly into execution, eliminating inspection points and compounding the risk of command injection or supply-chain compromise. Given this skill's purpose—automating shell-driven agent workflows—the pattern is more dangerous than in ordinary documentation because it may be adopted verbatim by autonomous tooling.

Content

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

install (review the installer script before piping curl | bash):

text
curl -fsSL https://opencode.ai/install | bash

Implementation Notes

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The README explicitly advertises starting background CLI sessions, sending coding tasks, handling permission requests, and triggering auto-updates, but it does not warn users that these actions may execute commands, modify files, or affect local system state. In the context of an agent skill that programmatically drives a coding tool, omission of these safety caveats can lead operators to grant permissions or run the demo with insufficient understanding of the consequences.

Content

No source excerpt is available for this finding.

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

cp SKILL.md ~/.hermes/profiles//skills/opencode-acp-control.md

Or load the whole directory

mkdir -p ~/.hermes/profiles//skills/opencode-acp-control cp SKILL.md ~/.hermes/profiles//skills/opencode-acp-control/SKILL.md

text

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

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

Or load the whole directory

mkdir -p ~/.hermes/profiles//skills/opencode-acp-control cp SKILL.md ~/.hermes/profiles//skills/opencode-acp-control/SKILL.md

text

The agent will pick up the file on its next skills refresh.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill clearly instructs use of shell execution (terminal(...)) and network access (web_fetch, installer download), but it does not declare any explicit tool scope or allowed-tools boundary. In an agent environment, missing capability scoping increases the chance that a caller grants broader execution and network powers than intended, making misuse or prompt-induced overreach more likely.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The update procedure tells the agent to enumerate and kill every running opencode acp process without an explicit confirmation step or warning about disrupting active sessions. Because this skill is specifically for programmatic process control, the instruction is contextually relevant, but it still creates a real availability risk: an agent could terminate unrelated or in-progress sessions and cause loss of work.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
96% confidence
Finding

The explicit 'Auto-approve permission request' behavior is an unsafe autonomous decision affecting security-sensitive actions. In this ACP-control context, the agent is not merely suggesting an action; it is granting execution authority to a subprocessed agent session that may access the filesystem or terminal.

Content

Scanner excerpt · examples/acp_demo.py (reported line 151)May include surrounding context.

python
title = tool_call.get("title", "unknown")
                print(f"[demo] server request: requestPermission for '{title}' (id={msg['id']})")
                
                # Auto-approve permission request to prevent deadlocks
                reply = {
                    "jsonrpc": "2.0",
                    "id": msg["id"],

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The demo client automatically approves every requestPermission request from the OpenCode subprocess with {"reply": "once"}, removing any human review or policy enforcement. In this skill context, the ACP client advertises filesystem write and terminal capabilities, so auto-approval can let the controlled session perform sensitive local actions immediately if prompted or compromised.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code accepts all permission requests from the subprocess without confirmation, effectively disabling the permission boundary the protocol expects. Because this skill is specifically designed to drive an agentic CLI session over JSON-RPC, that behavior is more dangerous than in a passive demo: it can authorize file modifications or terminal execution originating from model output or untrusted prompts.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
95% confidence
Finding

The log message confirms that the system has already auto-approved a permission request, evidencing autonomous authorization without oversight. That weakens a core safety control and can enable harmful actions if the session is influenced by malicious prompts, compromised tools, or unexpected server behavior.

Content

Scanner excerpt · examples/acp_demo.py (reported line 159)May include surrounding context.

python
}
                proc.stdin.write(frame(reply))
                proc.stdin.flush()
                print(f"[demo] auto-approved permission request id={msg['id']}")
            continue

        if msg.get("method") == "session/update":

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · examples/acp_demo.py (reported line 217)May include surrounding context.

python
return 1

    print(f"[demo] spawning: {opencode_bin} acp (cwd={cwd})")
    proc = subprocess.Popen(
        [opencode_bin, "acp"],
        cwd=str(cwd),
        stdin=subprocess.PIPE,

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · tests/test_acp_demo.py (reported line 79)May include surrounding context.

python
def test_read_frame_parses_single_line_bytes():
    stream = io.BytesIO(b'{"jsonrpc":"2.0","id":0,"result":{"ok":true}}\n')
    frame = acp_demo.read_frame(stream, timeout=0.0)
    assert frame == {"jsonrpc": "2.0", "id": 0, "result": {"ok": True}}

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · tests/test_acp_demo.py (reported line 85)May include surrounding context.

python
def test_read_frame_parses_single_line_bytes():
    stream = io.BytesIO(b'{"jsonrpc":"2.0","id":0,"result":{"ok":true}}\n')
    frame = acp_demo.read_frame(stream, timeout=0.0)
    assert frame == {"jsonrpc": "2.0", "id": 0, "result": {"ok": True}}

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · tests/test_acp_demo.py (reported line 90)May include surrounding context.

python
def test_read_frame_parses_single_line_bytes():
    stream = io.BytesIO(b'{"jsonrpc":"2.0","id":0,"result":{"ok":true}}\n')
    frame = acp_demo.read_frame(stream, timeout=0.0)
    assert frame == {"jsonrpc": "2.0", "id": 0, "result": {"ok": True}}

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · tests/test_acp_demo.py (reported line 96)May include surrounding context.

python
def test_read_frame_parses_single_line_bytes():
    stream = io.BytesIO(b'{"jsonrpc":"2.0","id":0,"result":{"ok":true}}\n')
    frame = acp_demo.read_frame(stream, timeout=0.0)
    assert frame == {"jsonrpc": "2.0", "id": 0, "result": {"ok": True}}

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_acp_demo.py (reported line 105)May include surrounding context.

python
def _run_cli(*args: str) -> subprocess.CompletedProcess:
    return subprocess.run(
        [sys.executable, str(DEMO_PATH), *args],
        capture_output=True,
        check=False,

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The module docstring says the script 'Gracefully shuts the subprocess down,' but the actual cleanup logic sends OS-level terminate/kill signals in the finally block. That is a direct mismatch between the documented intent and the implemented behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.