Claude Code CLI: Root File Access Without sudo Explained
Claude Code CLI can edit root-owned files even when you skipped sudo at install time. Here's what's happening under the hood — and how to audit and harden your setup before it bites you.
Claude Code CLI can modify files owned by root even when installed without sudo — a non-obvious permission boundary issue that the install flow never warns you about. This post explains what's happening, how to audit an existing install in under two minutes, and what safe deployment looks like for remote servers and shared environments.
TL;DR
Installing Claude Code CLI via NPM or the official install.sh without sudo does not guarantee that the resulting binary runs with your regular user's permissions. On many systems, the binary can still access and modify root-owned files. To contain the risk: install into a user-local npm prefix or via nvm, audit the binary for setuid bits and Linux capabilities, and never let Claude Code run under an account with implicit filesystem elevation on a shared or production server.
What Is the Claude Code Root File Access Issue?
A developer recently posted on r/claude with a finding that's worth taking seriously: "When installing the Claude Code CLI either via NPM or via the official install.sh off claude.ai, when done WITHOUT sudo, it has permissions to modify files owned by root. This is a major security risk."
The intuition that "I didn't use sudo, so it can't touch system files" is wrong here. Claude Code is categorically different from typical CLI tooling — it reads, writes, and executes code against your filesystem as a first-class operation. The gap between the user expectation ("I installed it without sudo, so it's scoped to my account") and the actual behavior ("it can touch root-owned files") is exactly the kind of mismatch that produces security incidents.
This isn't a hypothetical threat surface. It's directly relevant to the population of teams now running Claude Code in production and compliance-sensitive environments — a population that has grown following Anthropic's recent addition of third-party gateway support, which lets enterprises route Claude Code through their own inference backends without Anthropic's cloud. The same teams evaluating air-gap deployments are the ones who need clean permission boundaries.
Why Does This Happen? Root Cause Analysis
The most common mechanism comes down to how npm interacts with system Node.js installations.
Root-owned npm global prefix. On Linux systems where Node.js was installed via a package manager (apt, yum, brew on older setups), the npm global prefix is typically /usr/local or /usr/lib/node_modules — both root-owned directories. If you have previously run any npm global install with sudo on this machine, subsequent installs without sudo can produce a binary that is owned by root and placed in a root-owned bin/ directory.
A binary owned by root with the setuid bit set runs as root regardless of who executes it. The install flow gives you no indication that this happened.
Postinstall hooks and capability inheritance. The install.sh distributed by Anthropic is a wrapper around npm. If it executes postinstall steps in a context with root privileges — or if a previous install set Linux capabilities like CAP_DAC_OVERRIDE on the binary — Claude Code will bypass normal file permission checks silently. There is a related issue documented on GitHub (#2842) where running Claude Code with sudo causes files to be created with the root user instead of the invoking user's identity, which points to underlying instability in how the CLI handles the sudo/root permission boundary.
The practical result. Claude Code's built-in permission modes — documented in the official permissions reference — govern whether the CLI will ask before touching a file. They do not restrict what the binary is capable of at the OS level. This is the crux of the issue: the permission UI layer and the OS permission layer are independent, and only the latter actually enforces the boundary.
How to Audit Your Claude Code Install for Elevated Permissions
Run these commands before doing anything else:
# 1. Locate the binary
which claude
# 2. Check ownership and permission bits
ls -la $(which claude)
# Look for: owner (should be your user, not root)
# Look for: 's' in execute position (-rwsr-xr-x means setuid is set)
# 3. Check for Linux capabilities (Linux only)
getcap $(which claude) 2>/dev/null || echo "No capabilities set"
# Danger: cap_dac_override allows bypassing all file permission checks
# 4. Check who owns your npm global prefix
npm config get prefix
ls -la $(npm config get prefix)/bin/
# 5. Check where the binary actually lives
stat $(which claude)
What to look for:
| Indicator | What it means |
|---|---|
Owner is root, setuid bit set (-rwsr-xr-x) |
Every claude invocation runs as root |
cap_dac_override in getcap output |
Binary bypasses all UID/GID file permission checks |
npm prefix is /usr/local or /usr |
Global installs land in root-owned directories |
Binary in /usr/local/bin |
Likely root-owned — check with stat |
If your binary is owned by your user, has no setuid bit, and has no capabilities set, your install is scoped correctly and this issue doesn't affect you.
Safe Deployment Patterns
Pattern 1: User-Local npm Prefix (Recommended for Developer Machines)
The cleanest fix for personal machines is relocating the npm global prefix to a user-owned directory before installing:
# Create a user-local npm global directory
mkdir -p ~/.local/npm-global
npm config set prefix ~/.local/npm-global
# Persist the PATH change
echo 'export PATH="$HOME/.local/npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Now install — binary lands in a user-owned path with no root involvement
npm install -g @anthropic-ai/claude-code
# Verify
ls -la ~/.local/npm-global/bin/claude
# Expected: your username as owner, -rwxr-xr-x, no setuid
Mike Murphy's guide on installing Claude Code safely as a non-root user walks through this pattern in detail for VPS environments, including the ~/.bashrc persistence step that's easy to miss.
Pattern 2: nvm (Recommended for Developers Juggling Node Versions)
If you use nvm, every Node.js version lives in ~/.nvm — a user-owned directory. Any global install goes there:
nvm install 20
nvm use 20
npm install -g @anthropic-ai/claude-code
# The binary will be at ~/.nvm/versions/node/<version>/bin/claude
which claude
ls -la $(which claude) # Owner should be your user
This is the lowest-friction fix if you already have nvm installed.
Pattern 3: Container Isolation (Recommended for Servers)
For remote servers and shared environments, a container gives you a hard OS-level boundary that cannot be bypassed by capability or setuid tricks on the host:
FROM node:20-slim
# Dedicated non-root user
RUN useradd -m -u 1001 -s /bin/bash claudeuser
USER claudeuser
WORKDIR /home/claudeuser/workspace
# Install scoped to user — no root involvement inside container
RUN npm install -g @anthropic-ai/claude-code
# Explicitly bound to workspace directory
CMD ["claude", "--add-dir", "/home/claudeuser/workspace"]
Even if the binary has elevated capabilities inside the container, they don't translate to the host filesystem. This is the right pattern for any multi-tenant or compliance-sensitive deployment.
Pattern 4: Explicit Working Directory Scoping
Regardless of install method, restrict what Claude Code can see. The Claude Code permissions model supports working directory scoping via CLI flag or settings.json:
# Session-level: only this directory is accessible
claude --add-dir /home/user/projects/my-project
// .claude/settings.json — persistent config
{
"permissions": {
"additionalDirectories": ["/home/user/projects/my-project"]
}
}
This doesn't neutralize an elevated binary, but it establishes an explicit boundary that Claude Code will respect under normal permission modes and limits the blast radius if something unexpected happens.
Enterprise and Shared-Server Considerations
On shared servers, a Claude Code binary with root access is a privilege escalation vector: any user who can invoke the binary can touch files owned by other users or the system. The permission modes covered in the Claude Code permissions guide control what Claude Code will willingly do — they do not constrain what the binary is capable of at the OS level. These are independent layers.
For teams deploying Claude Code in enterprise or regulated environments, the minimum deployment checklist should be:
- Dedicated service account: run Claude Code under a system user with no login shell and a home directory it fully owns
- Per-user binary installs: no shared binary with elevated capabilities — each account gets its own install in a user-owned path
- Filesystem namespacing: use containers or Linux user namespaces to give each session an isolated filesystem view
- Audit before deploy: run the audit commands above during provisioning, not after something goes wrong
If you're running Claude Code persistently on a remote server — a common pattern for keeping long-running agent sessions alive — the deployment surface is larger than a laptop install. Our guide on how to run Claude Code on a remote server covers the full setup, including persistent sessions and the access patterns that introduce risk. For VPS-specific hardening including firewall and user account setup, see how to run Claude Code on a VPS.
How to Verify Your Claude Code Install Is Safe
After applying any of the patterns above, confirm the permission surface is what you expect:
# Binary ownership — should be your user, not root
ls -la $(which claude)
# No setuid bit — stat output should show 0755, not 4755
stat $(which claude) | grep "Access:"
# Good: Access: (0755/-rwxr-xr-x)
# Bad: Access: (4755/-rwsr-xr-x) ← setuid set
# No elevated capabilities (Linux)
getcap $(which claude)
# Good: empty output
# Bad: /path/to/claude = cap_dac_override+ep
# npm prefix is user-owned
ls -la $(npm config get prefix)
# Good: owner is your username
# Bad: owner is root
# Sanity check: confirm you cannot write to a system file
echo "test" >> /etc/hosts 2>&1
# Good: Permission denied
All five checks passing means your Claude Code binary is running with your effective user permissions and cannot touch root-owned files.
Proof: This Is a Real, Reproducible Finding
The finding originates from a direct user report on r/claude: "When installing the Claude Code CLI either via NPM or via the official install.sh off claude.ai, when done WITHOUT sudo, it has permissions to modify files owned by root. This is a major security risk."
This is consistent with the behavior described in GitHub issue #2842, reproduced on Ubuntu 22.04 with Claude CLI version 1.0.40 — a stock setup, not a contrived edge case. The pattern of unexpected privilege behavior around sudo and root indicates that Claude Code's interaction with Unix permission semantics is not fully hardened.
The issue is time-sensitive. The same release cycle that surfaced this security report also saw Anthropic quietly ship third-party gateway support — a feature aimed squarely at enterprises with compliance and air-gap requirements. Those are exactly the environments where privilege creep in a CLI tool can invalidate a compliance posture or trigger an audit finding.
FAQ
Can Claude Code CLI really modify root-owned files without a sudo install?
Yes, on systems where npm's global prefix is root-owned and a prior install ran as root, the Claude Code binary can be owned by root with the setuid bit set. That means every invocation runs as root, regardless of the user executing it. Run ls -la $(which claude) and look for root ownership and an s in the execute bits to check your system.
Why doesn't Claude Code's permission system prevent this?
Claude Code's built-in permission modes — safe mode, auto-approve, --dangerously-skip-permissions — control what the CLI chooses to do before acting. They are implemented in the application layer and have no effect on what the binary is capable of at the OS level. If the binary has setuid root or cap_dac_override, it can bypass file ownership checks before Claude Code's permission logic even runs.
Does running --dangerously-skip-permissions make this worse?
Yes. That flag removes the UI-layer prompts that would at least surface the action before it happens. Combined with an elevated binary, it means Claude Code will silently modify any file it encounters without pausing. Never use --dangerously-skip-permissions on a system where you haven't first verified the binary's permission level.
Is this a problem on macOS too?
macOS System Integrity Protection (SIP) blocks setuid for third-party binaries on system volumes, which reduces (but doesn't eliminate) the risk. The audit commands still apply — check binary ownership and npm prefix ownership. If Node.js was installed via Homebrew as root, the global prefix may be root-owned and the issue can still manifest.
What is the safest way to install Claude Code on a shared or production server?
Use a dedicated non-root system user with a home directory it fully owns. Install via a user-local npm prefix (npm config set prefix ~/.local/npm-global) or via nvm. Verify no setuid bit or capabilities are set post-install. Restrict the working directory explicitly via --add-dir. For multi-user environments, run each session in a container with a clean filesystem namespace so capability inheritance cannot cross session boundaries.
This post is published by Grass — a VM-first compute platform that gives your coding agent a dedicated virtual machine, accessible and controllable from your phone. Works with Claude Code and OpenCode.