Skip to content

fix: resolve install binary verification, uninstall, and version prefix bugs#535

Merged
khaliqgant merged 3 commits intomainfrom
fix/install-issues
Mar 10, 2026
Merged

fix: resolve install binary verification, uninstall, and version prefix bugs#535
khaliqgant merged 3 commits intomainfrom
fix/install-issues

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Mar 10, 2026

Summary

  • macOS Gatekeeper "Killed: 9": Strip com.apple.quarantine xattr from all downloaded binaries before verification. Added strip_quarantine() helper used at all 7 download points in install.sh, plus xattr -d in SDK's installBrokerBinary().
  • Uninstall doesn't remove binaries: agent-relay uninstall now removes standalone binaries from ~/.local/bin/, the ~/.agent-relay/ directory, and runs npm uninstall -g for all three packages.
  • vopenclaw-v3.1.18 404: SDK's getLatestVersionSync() now strips both openclaw- and v prefixes from GitHub release tags before constructing download URLs.
  • Version shadow warning: verify_installation() now detects when an older npm-installed binary shadows the newly installed standalone binary and advises the user.

Test plan

  • Run curl -fsSL .../install.sh | bash on macOS — broker binary should pass verification (no "Killed: 9")
  • Run agent-relay uninstall — verify which agent-relay returns nothing afterward
  • Verify SDK auto-install of broker resolves correct URL (no vopenclaw- prefix)
  • Install via both npm and standalone, verify version conflict warning appears

🤖 Generated with Claude Code


Open with Devin

…on prefix issues

- Strip macOS Gatekeeper quarantine attribute before binary verification
  to prevent "Killed: 9" errors on downloaded binaries
- Add binary and npm package removal to uninstall command (previously
  only cleaned up data/config files, leaving binaries in place)
- Fix SDK version tag parsing to strip "openclaw-" prefix, preventing
  double-prefix download URLs like "vopenclaw-v3.1.18" (404)
- Warn when an older npm-installed binary shadows newly installed version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
timeout: 10_000,
stdio: ['pipe', 'pipe', 'pipe'],
});
} catch {

Check warning

Code scanning / CodeQL

Indirect uncontrolled command line Medium

This command depends on an unsanitized
environment variable
.
This command depends on an unsanitized
environment variable
.

Copilot Autofix

AI about 2 months ago

In general, the safest fix is to avoid exec/execSync with a single shell command string when any part of that string comes from untrusted or tainted data. Instead, use execFile/execFileSync (or spawn/spawnSync) and pass arguments as an array of strings; this avoids shell parsing, redirection, and environment-variable expansion. For cases where you truly need shell features like redirection (2>/dev/null) or || true, wrap that in a small, fixed shell snippet and keep all untrusted data passed as separate arguments, or emulate the behavior in JavaScript (e.g., ignore errors instead of || true, discard stderr instead of 2>/dev/null).

Here, we only need to (a) run xattr -d com.apple.quarantine <targetPath> and ignore any errors, and (b) run codesign --force --sign - <targetPath> and ignore errors. Both can be implemented with execFileSync without any shell metacharacters, and the “ignore failure” behavior can be preserved by wrapping each call in a try/catch as already done. So the single best fix is:

  • Add execFileSync to the existing import from node:child_process.
  • Replace:
    • execSync(\xattr -d com.apple.quarantine "${targetPath}" 2>/dev/null || true`, ...)withexecFileSync('xattr', ['-d', 'com.apple.quarantine', targetPath], ...). The previous 2>/dev/null || true` is unnecessary because:
      • stdio: ['pipe','pipe','pipe'] already prevents stderr from cluttering the console.
      • The try/catch around the call already makes errors non-fatal.
  • Replace:
    • execSync(\codesign --force --sign - "${targetPath}"`, ...)withexecFileSync('codesign', ['--force', '--sign', '-', targetPath], ...)`.

This preserves all semantics (timeout, stdio behavior, non-fatal nature of failures), removes any use of an interpolated shell command string, and thus addresses all variants of the alert at this location.

Suggested changeset 1
packages/sdk/src/client.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts
--- a/packages/sdk/src/client.ts
+++ b/packages/sdk/src/client.ts
@@ -1,5 +1,5 @@
 import { once } from 'node:events';
-import { execSync, spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
+import { execFileSync, execSync, spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
 import { createInterface, type Interface as ReadlineInterface } from 'node:readline';
 import fs from 'node:fs';
 import os from 'node:os';
@@ -793,7 +793,7 @@
     // macOS: strip quarantine attribute and re-sign to avoid Gatekeeper issues
     if (process.platform === 'darwin') {
       try {
-        execSync(`xattr -d com.apple.quarantine "${targetPath}" 2>/dev/null || true`, {
+        execFileSync('xattr', ['-d', 'com.apple.quarantine', targetPath], {
           timeout: 10_000,
           stdio: ['pipe', 'pipe', 'pipe'],
         });
@@ -801,7 +801,7 @@
         // Non-fatal
       }
       try {
-        execSync(`codesign --force --sign - "${targetPath}"`, {
+        execFileSync('codesign', ['--force', '--sign', '-', targetPath], {
           timeout: 10_000,
           stdio: ['pipe', 'pipe', 'pipe'],
         });
EOF
@@ -1,5 +1,5 @@
import { once } from 'node:events';
import { execSync, spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
import { execFileSync, execSync, spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
import { createInterface, type Interface as ReadlineInterface } from 'node:readline';
import fs from 'node:fs';
import os from 'node:os';
@@ -793,7 +793,7 @@
// macOS: strip quarantine attribute and re-sign to avoid Gatekeeper issues
if (process.platform === 'darwin') {
try {
execSync(`xattr -d com.apple.quarantine "${targetPath}" 2>/dev/null || true`, {
execFileSync('xattr', ['-d', 'com.apple.quarantine', targetPath], {
timeout: 10_000,
stdio: ['pipe', 'pipe', 'pipe'],
});
@@ -801,7 +801,7 @@
// Non-fatal
}
try {
execSync(`codesign --force --sign - "${targetPath}"`, {
execFileSync('codesign', ['--force', '--sign', '-', targetPath], {
timeout: 10_000,
stdio: ['pipe', 'pipe', 'pipe'],
});
Copilot is powered by AI and may make mistakes. Always verify output.
The try/catch already handles errors, and the shell redirection is
fragile if execCommand ever changes from exec to execFile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

The ~/.agent-relay directory is the global data dir (GLOBAL_BASE_DIR)
which stores telemetry preferences, dashboard files, and legacy project
data. Only the bin/ subdirectory contains installer-managed binaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@khaliqgant khaliqgant merged commit 93a6a31 into main Mar 10, 2026
45 of 47 checks passed
@khaliqgant khaliqgant deleted the fix/install-issues branch March 10, 2026 12:19
khaliqgant added a commit that referenced this pull request Mar 25, 2026
…ix bugs (#535)

* fix: resolve install script binary verification, uninstall, and version prefix issues

- Strip macOS Gatekeeper quarantine attribute before binary verification
  to prevent "Killed: 9" errors on downloaded binaries
- Add binary and npm package removal to uninstall command (previously
  only cleaned up data/config files, leaving binaries in place)
- Fix SDK version tag parsing to strip "openclaw-" prefix, preventing
  double-prefix download URLs like "vopenclaw-v3.1.18" (404)
- Warn when an older npm-installed binary shadows newly installed version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove shell-dependent 2>/dev/null from npm uninstall execCommand

The try/catch already handles errors, and the shell redirection is
fragile if execCommand ever changes from exec to execFile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: only remove ~/.agent-relay/bin/ not entire ~/.agent-relay directory

The ~/.agent-relay directory is the global data dir (GLOBAL_BASE_DIR)
which stores telemetry preferences, dashboard files, and legacy project
data. Only the bin/ subdirectory contains installer-managed binaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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