[Feature gap] Report explicit Read deny-rule decisions in headless JSON output

Status Open
Reported on v2.1.260
Maintainer reply None cached
Activity 0 comments · opened Sep 6, 2026

Summary

A path-scoped native Read deny rule blocks the read, but the headless stream does not report that decision through a typed permission-denial event or the terminal permission_denials entry for that call.

This is a reporting/observability feature gap, not a vulnerability report. The observed blocking works; no enforcement bypass is claimed.

Minimal reproduction recipe

Requires an already authenticated Claude Code installation. The shell creates one synthetic file in a fresh temporary directory; it does not use private files or a custom runner.

(
  repro_dir="$(mktemp -d /tmp/claude-deny-report.XXXXXX)" || exit 1
  cd "$repro_dir" || exit 1
  printf '%s\n' 'SYNTHETIC_DENY_REPORT_MARKER' > blocked-read.txt

  claude -p --verbose --output-format stream-json \
    --safe-mode --setting-sources '' --no-session-persistence \
    --tools Read --permission-mode dontAsk --max-turns 3 \
    --settings '{"permissions":{"deny":["Read(./blocked-read.txt)"]}}' \
    -- 'Use the Read tool once on blocked-read.txt. Do not use another tool or retry. Then stop.'
)

There is one native permissions.deny rule and only Read is enabled. No OS sandbox profile is part of this reproduction. The temporary directory is intentionally left for inspection.

Inspect the stream for a Read tool_use targeting that file and pair its tool_result by tool_use_id. If the model declines without making the call, the run is inconclusive, not a reproduction.

Expected

A rejected native deny-rule call has a correlated, typed report of the decision: for example a system/permission_denied event or a terminal permission_denials entry. A native tool_result_meta permission-rule indication would also make the error distinguishable from unrelated validation errors.

No change to blocking or deny-rule precedence is requested.

Actual observation and versions

In the previously observed live Read case, the matching result has is_error: true and says the directory is denied by permission settings, but the Read call has no typed denial event, no permission-rule sidecar, and no terminal permission_denials entry.

  • 2.1.260, native macOS arm64: observed live in an earlier synthetic test.
  • 2.1.236 (stable at the time of inspection): static inspection of the official native macOS arm64 artifact found the same missing reporting path.
  • 2.1.263 (latest/next at the time of inspection): static inspection found the same path.

The compact recipe above has not been run separately. The static findings are not presented as live reproductions on 2.1.236 or 2.1.263.

Source-level finding

Read checks the explicit deny rule during input validation. That early return becomes a generic error tool result before the ordinary permission-denial reporting path. The requested improvement is to carry the already-made native deny decision into the typed output, without changing enforcement.

View original on GitHub ↗