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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions