anomalyco / anomalyco/opencode

[SECURITY] "opencode upgrade" fetches a remote script and pipes it to bash with no integrity verification (curl|bash)

Open
#42,434 3 comments 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Aug 13, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

[SECURITY] opencode upgrade fetches a remote script and pipes it to bash with no integrity verification (curl|bash pattern)

Description

Description

  • Severity: Medium (supply-chain / TOCTOU, full user privileges once action is confirmed)
  • Affected: curl-based installs (installation/index.ts:139-159, present at baef5cd43b and current dev)
  • Related: #42022 (server-side request validation only; does not address the client fetch-and-exec)

Plugins

None required

OpenCode version

Observed in source at commits baef5cd43b and 743f6410f2, current dev branch (code-verified; see References below)

Steps to reproduce

  1. Run opencode upgrade on a curl-based install (upgrade method resolves to `curl`).
  2. Observe GET https://opencode.ai/install → response body piped to bash stdin, extendEnv: true, no checksum verification (packages/opencode/src/installation/index.ts:139-159).
  3. Replace the endpoint content (local DNS/poisoned response) → script executes as the user with full privileges, no user review.

Screenshot and/or share link

N/A — verified against source (details and file:line references below).

Operating System

All (cross-platform; verified on macOS Darwin)


Details

The curl-install upgrade path does exactly the pattern the industry has been moving away from: GET https://opencode.ai/install and pipe the response body straight into a new bash process, with zero integrity verification.

const upgradeCurl = Effect.fnUntraced(function* (target: string) {
  const response = yield* httpOk.execute(HttpClientRequest.get("https://opencode.ai/install"))
  const body = yield* response.text
  const bodyBytes = new TextEncoder().encode(body)
  const proc = ChildProcess.make("bash", [], {
    stdin: Stream.make(bodyBytes),
    env: { VERSION: target },
    extendEnv: true,
  })
  ...
Impact
  1. TOCTOU: the script is fetched and executed as one opaque unit — the bytes the user might have seen in a previous run (or that were served when a previous install happened) are not the bytes that run now. No hash is pinned, computed, or checked.
  2. Full privileges: the script runs as the invoking user with extendEnv: true, i.e. it inherits the shell environment (credentials, ~/.config, SSH agent, etc.).
  3. Single-gate security: the only control between "user said upgrade" and "arbitrary remote code executes" is the prompt. Any compromise of the install endpoint, a CDN/edge compromise, or a poisoned response is unbounded code execution with no user review of what actually runs.
  4. VERSION is passed as an env var but never constrains the executed script to that version — the script itself decides what to install.
Suggested fix
  • Prefer installing from release artifacts (binary archives with published checksums) over executing a remote script.
  • If the script must stay: pin its content hash in the client, verify sha256 of the fetched body before spawn, and refuse on mismatch.
  • At minimum, fetch → render the script for review / print its hash → require explicit confirmation before execution, and never extendEnv secrets into it.

Plugins

None required

OpenCode version

Observed in source at commits baef5cd43b and 743f6410f2, current dev branch (code-verified; file:line references in Details).

Screenshot and/or share link

N/A — verified against source (details and file:line references below).

Operating System

All (cross-platform; verified on macOS Darwin).

Steps to reproduce

  1. Run opencode upgrade on a curl-based install (upgrade method resolves to curl).
  2. Observe GET https://opencode.ai/install → response body piped to bash stdin with extendEnv: true and no checksum verification (packages/opencode/src/installation/index.ts:139-159).
  3. Serve modified content for the endpoint (e.g. local DNS/poisoned response) → script executes as the user with full privileges; the user never reviewed the executed bytes.

Details

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.