Skip to content

fix: expand default denyRead to cover additional credential stores #16

@isanchez31

Description

@isanchez31

Summary

Red team testing of the sandbox revealed gaps in the default denyRead list. Several common credential stores are not blocked by default, which allows sandboxed commands to read sensitive tokens and keys.

Findings

Confirmed vulnerability: credential read + exfiltration chain

A sandboxed command can:

  1. Read credentials not in DEFAULT_DENY_READ_DIRS (e.g., GitHub CLI tokens in ~/.config/gh/hosts.yml)
  2. Stage the data in /tmp (fully writable by default)
  3. Exfiltrate via HTTP to domains in the default allowlist (e.g., api.github.com)

This was confirmed via E2E testing with opencode run in a real sandbox session.

Missing credential paths in denyRead

Path Contains Status
~/.config/gh GitHub CLI OAuth tokens NOT blocked
~/.kube Kubernetes cluster credentials NOT blocked
~/.docker/config.json Docker registry auth tokens NOT blocked
~/.netrc FTP/Git network credentials NOT blocked
~/.azure Azure CLI credentials NOT blocked

Already protected (confirmed working)

Path Mechanism
~/.ssh tmpfs overlay (empty dir)
~/.gnupg tmpfs overlay (empty dir)
~/.aws/credentials tmpfs overlay
~/.config/gcloud tmpfs overlay
~/.npmrc ro-bind to /dev/null
~/.env denied

Fix

Add the missing paths to DEFAULT_DENY_READ_DIRS in src/config.ts:

const DEFAULT_DENY_READ_DIRS = [
  ".ssh",
  ".gnupg",
  ".aws/credentials",
  ".azure",
  ".config/gcloud",
  ".config/gh",
  ".kube",
  ".docker/config.json",
  ".npmrc",
  ".netrc",
  ".env",
]

Additional notes from red team testing

The following attack vectors were tested and confirmed blocked:

  • Sandbox disable via OPENCODE_DISABLE_SANDBOX=1 env var (only checked at init time)
  • Config poisoning via write to ~/.config/opencode-sandbox/ (blocked by filesystem isolation)
  • Symlink attacks to denyRead paths (blocked by tmpfs overlay)
  • DNS exfiltration (blocked by --unshare-net)
  • Direct network bypass via unsetting proxy vars (blocked by kernel-level network namespace)
  • nsenter namespace escape (blocked by missing capabilities)
  • /proc/self/root filesystem bypass (sees sandboxed view)
  • Path traversal writes (../../) (blocked by read-only bind mount)

Remaining considerations (not addressed in this fix)

  1. Network allowlist as exfiltration surface: Any credential readable inside the sandbox can potentially be sent to any domain in the default allowlist. This is inherent to the allowlist design.
  2. /tmp fully writable: Default allowWrite includes all of os.tmpdir(), providing a staging area for multi-step attacks. Could be narrowed to /tmp/claude.
  3. *.googleapis.com is broad: Allows any Google API subdomain. Could be narrowed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions