Skip to content

Harden command execution in init wizard beyond string-based validation #384

@betegon

Description

@betegon

Summary

cli/src/lib/init/local-ops.ts executes commands from the API server using shell: true. The current protections are string-based and could be bypassed by a sufficiently creative payload. Before sentry init exits experimental mode, we should add OS-level sandboxing.

Current protections

validateCommand() (line 140) — three layers of string validation:

  1. Shell metacharacter blocking — rejects commands containing ;, &&, ||, |, `, $(, $, quotes, redirects (>, <), braces, globs, newlines, backslashes
  2. Env var injection blocking — rejects commands where the first token contains =
  3. Dangerous executable blocklist — rejects 40+ executables (rm, curl, sudo, ssh, bash, eval, etc.) but only checks the first token — e.g. npm exec -- rm -rf / would pass because npm is the primary executable

safePath() / path validation — prevents path traversal and symlink escapes outside the project root.

runSingleCommand() (line 426) — spawns via spawn(command, [], { shell: true, cwd, ... }) with a timeout and output truncation (64KB cap).

Gap

All protections are string-based pattern matching. The command still runs under shell: true with full user privileges. Potential bypasses:

  • Package manager subcommands that execute arbitrary code (e.g. npm exec, lifecycle scripts)
  • Unicode/encoding tricks that survive validation but are interpreted differently by the shell
  • Future commands the server might send that don't match current patterns

Suggested hardening (Linux)

These are additive layers — the string validation remains as a fast first pass.

Technique How Benefit
Linux namespaces / unshare unshare --net --mount --pid wrapper Network isolation, mount isolation, PID isolation
Landlock LSM Restrict filesystem access to project dir + node_modules + package manager cache Prevents reads/writes outside expected paths even if shell escapes
bubblewrap (bwrap) Lightweight sandbox with readonly / bind-mount, writable project dir only Strong filesystem + namespace isolation in one tool
Drop to restricted user runuser / setuid to a no-login user for spawned commands Limits damage from any escape
Read-only filesystem mounts Bind-mount / as read-only, whitelist project dir as writable Commands can't modify system files
Network namespace isolation Empty network namespace for commands that don't need network (e.g. codemods) Prevents data exfiltration
--shell=false where possible For simple commands like npx @sentry/wizard, split into [executable, ...args] and avoid shell entirely Eliminates shell injection surface

macOS considerations

macOS lacks namespaces/Landlock. Options are more limited:

  • sandbox-exec (deprecated but functional) with a restrictive profile
  • Avoid shell: true where possible by splitting commands into argv arrays

Priority

Low — the current string validation is a reasonable defense for an experimental feature where commands originate from our own API server. This becomes more important if/when the command surface expands or the feature exits experimental.

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