Skip to content

security: harden hosts.json and supply chain install paths#60

Merged
asieduernest12 merged 15 commits intodamoahdominic:multihostfrom
FletcherFrimpong:multihost
Apr 4, 2026
Merged

security: harden hosts.json and supply chain install paths#60
asieduernest12 merged 15 commits intodamoahdominic:multihostfrom
FletcherFrimpong:multihost

Conversation

@FletcherFrimpong
Copy link
Copy Markdown
Collaborator

Summary

  • Move sensitive host connection configs (SSH host, user, keyPath) from plaintext ~/.occ/hosts.json to VS Code SecretStorage (OS keychain, encrypted, per-extension isolated). Existing data auto-migrates on activation.
  • Add adapter trust validationregisterHostAdapter() now requires extension ID, checks against a trusted allowlist, and prompts users before allowing unknown extensions to register as adapters. All registrations logged.
  • Set 0600 file permissions on hosts.json writes (defense-in-depth).
  • Eliminate all unverified install paths — every npm install fallback now uses SHA-512 verified tarballs; installer scripts are downloaded, SHA-256 verified, then executed (no more curl | bash).
  • Remove npm install -g openclaw@latest fallback from update prompts.

Motivation

Security review identified two attack vectors:

  1. Malicious VS Code extension could read ~/.occ/hosts.json to exfiltrate SSH connection details, then register as an adapter with zero verification.
  2. Supply chain attack via fallback install paths that bypassed the primary SHA-512 verification (bare npm install -g openclaw, curl | bash).

Test plan

  • Add an SSH host → verify ~/.occ/hosts.json contains only { "type": "ssh" } stub (no host/user/keyPath)
  • Verify SSH connection still works (config hydrated from SecretStorage)
  • Check file permissions: stat -f '%Lp' ~/.occ/hosts.json600
  • Existing hosts auto-migrate on extension activation
  • Adapter registration from trusted extensions succeeds silently
  • Simulate untrusted adapter → modal approval prompt appears
  • Fresh install uses verified tarball in all code paths

🤖 Generated with Claude Code

FletcherFrimpong and others added 15 commits March 26, 2026 21:35
…ties

- config.ts: remove unsafe-eval from Content Security Policy
- statusController.ts: HTML-escape version strings before innerHTML injection
- statusHtml.ts: sanitize maintainerName/URL before inserting into innerHTML
- extension.ts: use imported cp module instead of require(); redact API keys from logs
- openclaw-ssh/connection.ts: validate file paths to reject null bytes and non-absolute paths
- status.ts: add localResourceRoots to restrict webview resource loading
- apps/web: upgrade Next.js to 16.2.1 to fix CSRF bypass and HTTP smuggling CVEs

Co-Authored-By: FletcherFrimpong <[email protected]>
JWT was stored in plaintext globalState (a JSON file on disk), readable
by any process running as the same OS user or any VS Code extension.

Changes:
- All reads: context.globalState.get(OCC_JWT_KEY) → context.secrets.get()
- All writes: context.globalState.update() → context.secrets.store/delete()
- One-time migration on activate: moves existing JWT from globalState to
  SecretStorage, then deletes plaintext copy
- Smoke test no longer logs any part of the JWT
- Deep-link URI handler (occode://auth) stores token in SecretStorage

JWT is now encrypted at rest via OS keychain (Keychain on macOS,
Credential Manager on Windows, libsecret on Linux).

Co-Authored-By: FletcherFrimpong <[email protected]>
Before running npm install, OCC now:
1. Fetches package metadata from registry.npmjs.org
2. Downloads the tarball to a temp file
3. Verifies SHA-512 hash against dist.integrity (SRI format)
4. Only installs from the verified local file — no re-download

All three install paths are covered:
- Main npm install path
- Sudo retry path (re-downloads and re-verifies)
- nvm fallback path

If the hash does not match, installation is aborted with a clear
error message. Temp files are always cleaned up after install.

Protects against: MITM attacks, CDN compromise, corrupted downloads.

Co-Authored-By: FletcherFrimpong <[email protected]>
…export

The site uses Next.js static export (output: "export"), which means
server-side fetches only run at build time. Moving the GitHub releases
API fetch to a client-side useEffect ensures users always get the
latest release download URLs at runtime.
Replace the unsafe `curl | bash` pattern with a 4-step verified
install: download script to temp file, fetch SHA-256 checksum from
a separate domain (releases.openclaw.sh), verify with sha256sum,
then execute only if the hash matches. Temp files are cleaned up on
both success and failure paths.

Protects against MITM attacks, DNS hijacking, and CDN compromise —
an attacker must now compromise two independent origins simultaneously.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Added quotes to HTML attributes and anon=1 param to include all contributors.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…er trust

Mitigates malicious extension attack vector where any VS Code extension
could read ~/.occ/hosts.json to exfiltrate SSH connection details (host,
user, keyPath). Three hardening measures:

1. Connection configs now stored in VS Code SecretStorage (OS keychain),
   with automatic migration from plaintext. hosts.json retains only a
   type stub for adapter routing.

2. hosts.json written with 0600 permissions (owner-only).

3. registerHostAdapter() now requires extension ID, validates against
   trusted allowlist, and prompts user approval for unknown adapters.
   All registrations logged to output channel.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…bash

Closes remaining supply chain gaps identified in security review:

1. Post-Node.js-install fallback now uses SHA-512 verified tarball
   instead of bare `npm install -g openclaw`.

2. Local installer scripts (Unix/Windows) no longer pipe remote
   scripts directly into shell. Scripts are downloaded to temp files,
   SHA-256 checksums fetched and verified before execution.

3. Removed `npm install -g openclaw@latest` fallback from update
   prompt — only suggests `openclaw update` now.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Mitigates gateway exposure attack (port 18789 reachable on LAN):

1. Add bind-address safety check — when gateway is running on a local
   host, probe the port on a non-loopback interface. If reachable,
   show a warning that the gateway is exposed to the network.

2. All health check HTTP requests now use 127.0.0.1 explicitly instead
   of localhost (which could resolve to a non-loopback address).

3. Add readGatewayToken() helper for future use — reads the auth token
   from openclaw.json so health checks can include authentication.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Closes the first-connect MITM vulnerability in the SSH adapter:

1. StrictHostKeyChecking changed from accept-new to yes — SSH now
   rejects connections to hosts not in known_hosts.

2. Setup wizard verifies host keys before first connect: fetches
   fingerprint via ssh-keyscan, shows it in a modal dialog, and
   only adds to known_hosts after user explicitly trusts the host.

3. testConnection() refuses unknown hosts — directs users to the
   setup wizard for proper fingerprint verification.

4. Helper functions exported: isHostKnown(), fetchHostFingerprint(),
   addHostToKnownHosts() for consistent host key management.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Mitigates fake-update and release-tampering attack vectors:

1. Version strings from npm registry now validated against strict
   semver pattern — rejects crafted/malicious version responses.
   Non-200 HTTP responses also rejected (prevents redirect-based
   spoofing).

2. Removed remaining npm install -g openclaw@latest fallback from
   statusController auto-update prompt.

3. Website download page now validates that release asset URLs
   originate from github.com/objects.githubusercontent.com domains
   before linking — prevents tampered release metadata from
   redirecting users to malicious download servers.

4. Website shows "Verify checksums (SHA-256)" link when a checksums
   file is present in the GitHub release assets.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ions

Mitigates container escape attack vector:

1. All OCC-created containers now run with --cap-drop ALL
   --cap-add NET_BIND_SERVICE --security-opt no-new-privileges.
   This drops dangerous default capabilities and prevents privilege
   escalation via setuid binaries.

2. Runtime security audit on container connect: inspects the
   container for dangerous configurations (running as root,
   --privileged mode, Docker socket mounted) and warns the user.
   Covers both OCC-created and user-provided containers.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Two critical fixes:

1. Remove webview catch-all command execution fallback in
   statusController.ts and home.ts. Previously, any unrecognized
   webview message with a `command` field was passed directly to
   vscode.commands.executeCommand() — allowing a compromised webview
   to execute arbitrary VS Code commands. The fallback clauses are
   removed; only explicitly handled commands are accepted.

2. Replace all remaining curl-pipe-bash patterns in installCli()
   across all 4 connection files (local, docker, ssh, localDefault).
   Install scripts are now downloaded to temp files, checksums
   fetched and verified via sha256sum, then executed. Temp files
   cleaned up on success and failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Three high-severity fixes:

1. openUrl webview handler now validates URLs against a domain
   allowlist (occ.mba.sh, github.com, openclaw.ai, etc.) and
   rejects non-http(s) protocols. Prevents phishing via
   compromised webview content.

2. API keys no longer passed as CLI arguments (visible in ps,
   docker logs, shell history). All runSetup() methods now pass
   the key via OPENCLAW_API_KEY environment variable instead.

3. buildExecEnv() no longer spreads the entire process.env to
   child processes. Each adapter now uses safeExecEnv() which
   allowlists only necessary variables (PATH, HOME, SHELL, etc.)
   SSH adapter keeps SSH_AUTH_SOCK; Docker keeps DOCKER_HOST.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@asieduernest12 asieduernest12 merged commit d0ad77e into damoahdominic:multihost Apr 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants