Skip to content

security: [CRITICAL] Command injection via scriptContent in performAutoUpdate #3291

@louisgv

Description

@louisgv

Finding

File: packages/cli/src/update-check.ts:323-328
Severity: CRITICAL

Description

The performAutoUpdate function fetches an install script from a remote URL via curl, then passes the script content directly to bash -c without any validation or integrity checks:

// Line 280-296: Fetch script bytes
const scriptBytes = executor.execFileSync(
  "curl",
  ["--proto", "=https", "-fsSL", installUrl],
  { encoding: "utf8" }
);
const scriptContent = scriptBytes ? scriptBytes.toString() : "";

// Line 323-328: Execute fetched script via bash -c
executor.execFileSync(
  "bash",
  ["-c", scriptContent],
  { stdio: installStdio }
);

Attack Vector:

  1. If installUrl (derived from SPAWN_CDN) is compromised or MITM'd despite HTTPS
  2. If DNS is poisoned to redirect openrouter.ai or GitHub
  3. If a CDN cache poisoning attack succeeds
  4. If the GitHub release artifact is compromised

The attacker can inject arbitrary shell commands that will be executed with the user's permissions during auto-update.

Current Mitigations (insufficient):

  • curl --proto =https enforces HTTPS (prevents cleartext MITM)
  • URL is hardcoded to trusted domains (not user-controllable)

Missing Mitigations:

  • No cryptographic signature verification of the downloaded script
  • No checksum validation against a known-good hash
  • No content inspection before execution
  • Script content is trusted blindly once fetched

Recommendation

Option 1: Add Signature Verification (Recommended)

  • Sign install scripts with GPG/cosign during release
  • Verify signature before execution:
    curl -fsSL $URL.sig | gpg --verify - <(echo "$scriptContent")

Option 2: Add Checksum Validation

  • Publish SHA256 checksums alongside install scripts
  • Verify checksum matches before execution:
    const expectedHash = await fetchChecksum();
    const actualHash = createHash('sha256').update(scriptContent).digest('hex');
    if (actualHash \!== expectedHash) throw new Error('Checksum mismatch');

Option 3: Remove Auto-Update (Least Disruptive)

  • Print update notice only (like npm/yarn)
  • Let users manually run spawn update with explicit consent
  • Remove performAutoUpdate entirely

Comparison:

  • Option 1 provides strongest security but requires CI/CD changes + GPG key management
  • Option 2 provides moderate security with minimal CI changes (publish checksums)
  • Option 3 removes the attack surface entirely at the cost of user convenience

Discovered by code-scanner during automated security scan

Metadata

Metadata

Assignees

No one assigned

    Labels

    under-reviewIssue is being reviewed by the team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions