This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new 925b4e3  docs(secure-agent-internals): note macOS chained-curl gap in 
The blind spot (#25)
925b4e3 is described below

commit 925b4e34f02c95a581c5c31c633a0f66d3e7647c
Author: Jarek Potiuk <[email protected]>
AuthorDate: Fri May 1 17:25:43 2026 +0200

    docs(secure-agent-internals): note macOS chained-curl gap in The blind spot 
(#25)
    
    Verification surfaced a real gap that the doc currently understates:
    
    - `permissions.deny` patterns match against the *first* command of
      a Bash tool invocation, not against every command in a
      multi-command chain. A standalone `curl https://example.com` is
      correctly denied at the permission prompt; the same call buried
      mid-pipeline (`echo a; curl https://example.com; echo b`)
      starts as `echo a` and slips past the deny list.
    
    - On Linux, that gap is closed by socat's SNI proxy: even when the
      runtime lets `curl` start, the network layer of the sandbox
      blocks the egress unless the destination host is on
      `sandbox.network.allowedDomains`.
    
    - On macOS there is no socat. The framework's setup uses Seatbelt
      for filesystem isolation but does not currently wrap Bash-
      subprocess network egress on macOS, so a chained `curl` reaches
      the network on macOS even when the same call would be blocked
      on Linux.
    
    Adds a sub-section to `## The blind spot: Bash(curl *) and
    DNS-over-HTTPS` documenting the matching behaviour and the macOS-
    specific gap. Lists three mitigations in increasing strength
    (issue Bash calls one command at a time; run an OS-level packet
    filter; future framework enhancement to wrap macOS subprocesses
    in a network-restricting sandbox-exec profile).
    
    Generated-by: Claude Code (Opus 4.7)
---
 secure-agent-internals.md | 44 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/secure-agent-internals.md b/secure-agent-internals.md
index 5c950a0..ce903d3 100644
--- a/secure-agent-internals.md
+++ b/secure-agent-internals.md
@@ -9,6 +9,7 @@
   - [Linux: bubblewrap + user namespaces](#linux-bubblewrap--user-namespaces)
   - [macOS: Seatbelt](#macos-seatbelt)
   - [The blind spot: `Bash(curl *)` and 
DNS-over-HTTPS](#the-blind-spot-bashcurl--and-dns-over-https)
+    - [macOS: `permissions.deny` first-command-only 
matching](#macos-permissionsdeny-first-command-only-matching)
   - [How the feedback mechanisms layer 
together](#how-the-feedback-mechanisms-layer-together)
   - [Residual risks](#residual-risks)
   - [See also](#see-also)
@@ -167,6 +168,49 @@ contains `Bash(curl *)`, `Bash(wget *)`, and the various 
cloud
 CLIs — defence in depth against an exfiltration path that the
 sandbox alone does not close.
 
+### macOS: `permissions.deny` first-command-only matching
+
+Claude Code's `permissions.deny` patterns match against the
+*first* command of a Bash tool invocation, not against every
+command in a multi-command chain. A standalone Bash call of
+`curl https://example.com` is correctly denied at the permission
+prompt; the same call buried mid-pipeline (`echo a; curl
+https://example.com; echo b`) starts as `echo a` and slips past
+the deny list — the runtime sees the first command and lets the
+chain run.
+
+On Linux, that gap is closed by socat's SNI proxy: even if the
+runtime lets `curl` start, the network layer of the sandbox
+blocks the egress unless the destination host is on
+`sandbox.network.allowedDomains`.
+
+**On macOS there is no socat.** Network egress for the sandboxed
+Bash subprocess is unfiltered — Seatbelt enforces filesystem
+isolation but the framework's setup does not currently wrap
+network egress on macOS. A chained `curl` to an arbitrary host
+therefore reaches the network on macOS even when the same call
+in the same session would be blocked on Linux. This is a real
+adopter-facing gap, not an implementation detail.
+
+Mitigations available today, ordered from cheapest to strongest:
+
+- Issue `Bash` calls one command at a time, not as chained
+  pipelines. The deny pattern then matches the actual command
+  that runs. The agent-guided
+  `verify-secure-config` skill does this deliberately when
+  running its denial checks.
+- On hosts where `Bash(*)` chained execution is a meaningful
+  exfiltration concern, run an outbound packet filter
+  (`pf` on macOS, `nftables` on Linux) that whitelists the same
+  hosts as `sandbox.network.allowedDomains`. The OS-level filter
+  applies regardless of whether the call goes through Claude
+  Code's runtime or escapes via a chain.
+- A future framework enhancement could wrap macOS Bash
+  subprocesses in a `sandbox-exec` profile that *also* restricts
+  outbound `network*` operations the way the current profile
+  restricts `file-read*`. That is an open follow-up, not a
+  shipped capability today.
+
 ## How the feedback mechanisms layer together
 
 | Mechanism | Scope | What it tells you | When it fires |

Reply via email to