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 |