claude-code - 💡(How to fix) Fix Feature: Landlock sandbox directives for PreToolUse hooks [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#57901Fetched 2026-05-11 03:22:27
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
labeled ×5

PreToolUse hooks can perform static analysis on Bash commands before execution, but several bypass classes are fundamentally unsolvable by static analysis: shell function indirection (r() { cat "$1"; }; r .env), bash array expansion, runtime path construction, and write-then-execute attacks. These all construct or resolve file paths at runtime, making them invisible to any pre-execution text parser.

Linux Landlock LSM (available unprivileged since kernel 5.13, ABI v6 on modern kernels) solves all of these at the kernel level by restricting open() syscalls regardless of how the path was derived. However, the current hook architecture can't leverage it because hooks run as separate processes that exit before the command executes — Landlock restrictions only apply to the calling process and its descendants.

Root Cause

I maintain a PreToolUse secret-protection hook (~1450 lines of Python) that blocks Bash commands accessing sensitive files. After three rounds of multi-agent red team testing (8 parallel agents each round), we've identified and fixed 33 bypass vectors — but 5 architectural gaps remain that no amount of static analysis can solve:

GapExampleWhy static analysis fails
Shell function indirectionr() { cat "$1"; }; r .envFunction body is opaque
Array expansiona=(cat .env); "${a[@]}"Array contents not trackable
Runtime path constructionpython3 -c "open(os.path.join(...))"No .env in command text
Write-then-executeWrite script, run itScript constructs paths at runtime
Symlink TOCTOURepoint symlink between check and execRace condition

I built a working Landlock prototype that blocks all five. In testing, r() { cat "$1"; }; r .env returns Permission denied while echo hello and ls /tmp work normally. The missing piece is the bridge between the hook's decision and the command's execution context.

Code Example

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "sandbox": {
      "landlock_deny_read": [
        "/home/user/.env",
        "/home/user/.vault-token",
        "/home/user/.ssh"
      ]
    }
  }
}
RAW_BUFFERClick to expand / collapse

Summary

PreToolUse hooks can perform static analysis on Bash commands before execution, but several bypass classes are fundamentally unsolvable by static analysis: shell function indirection (r() { cat "$1"; }; r .env), bash array expansion, runtime path construction, and write-then-execute attacks. These all construct or resolve file paths at runtime, making them invisible to any pre-execution text parser.

Linux Landlock LSM (available unprivileged since kernel 5.13, ABI v6 on modern kernels) solves all of these at the kernel level by restricting open() syscalls regardless of how the path was derived. However, the current hook architecture can't leverage it because hooks run as separate processes that exit before the command executes — Landlock restrictions only apply to the calling process and its descendants.

Proposal

Add support for hooks to return a sandbox directive that Claude Code applies to the Bash command's execution environment. Specifically:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "sandbox": {
      "landlock_deny_read": [
        "/home/user/.env",
        "/home/user/.vault-token",
        "/home/user/.ssh"
      ]
    }
  }
}

When Claude Code receives this response from a PreToolUse hook, it would:

  1. Create a Landlock ruleset handling LANDLOCK_ACCESS_FS_READ_FILE
  2. Grant read access to all filesystem paths EXCEPT those listed in landlock_deny_read
  3. Apply prctl(PR_SET_NO_NEW_PRIVS) and landlock_restrict_self() to the Bash child process before exec
  4. Execute the Bash command in the restricted environment

Why this matters

I maintain a PreToolUse secret-protection hook (~1450 lines of Python) that blocks Bash commands accessing sensitive files. After three rounds of multi-agent red team testing (8 parallel agents each round), we've identified and fixed 33 bypass vectors — but 5 architectural gaps remain that no amount of static analysis can solve:

GapExampleWhy static analysis fails
Shell function indirectionr() { cat "$1"; }; r .envFunction body is opaque
Array expansiona=(cat .env); "${a[@]}"Array contents not trackable
Runtime path constructionpython3 -c "open(os.path.join(...))"No .env in command text
Write-then-executeWrite script, run itScript constructs paths at runtime
Symlink TOCTOURepoint symlink between check and execRace condition

I built a working Landlock prototype that blocks all five. In testing, r() { cat "$1"; }; r .env returns Permission denied while echo hello and ls /tmp work normally. The missing piece is the bridge between the hook's decision and the command's execution context.

Alternative: SHELL override

A simpler approach: if Claude Code honors the SHELL environment variable (or provides a configuration option for the shell binary), users could point it at a wrapper script that applies Landlock before execing bash. This requires no Claude Code code changes but is less elegant and harder to configure.

Technical details

  • Landlock ABI v6 is available on kernel 6.1+ (Debian 13, Ubuntu 24.04+, Fedora 39+)
  • No privileges requiredlandlock_restrict_self() works for unprivileged users
  • Overhead: ~2ms for ruleset creation (one-time per command)
  • Compatibility: gracefully degrades on kernels without Landlock (hook falls back to static analysis only)
  • Rules use the "allowlist" approach: handle READ_FILE, grant read to everything except denied paths. This requires walking the filesystem to enumerate allowed paths, with transitive ancestor computation for directories containing sensitive files.

Scope

This proposal focuses on Landlock filesystem restrictions because they directly address the most common hook use case (protecting sensitive files). Future extensions could include:

  • landlock_deny_write: prevent writing to protected paths
  • seccomp_deny_syscalls: restrict specific syscalls (network, ptrace, etc.)
  • landlock_deny_net: restrict network access (Landlock ABI v4+)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING