Merge pull request 'Say that the egress allowlist bounds the container, not the harness' (#163) from harness-tool-egress into development
Reviewed-on: #163 Reviewed-by: branderson <brad@branderson.io>
This commit was merged in pull request #163.
This commit is contained in:
@@ -922,6 +922,57 @@ The sandbox-context.sh `SessionStart` hook tells the agent to ask for this
|
||||
by name when a task is blocked on a single missing domain, rather than
|
||||
stopping the whole session over it.
|
||||
|
||||
### The firewall bounds the container, not the harness
|
||||
|
||||
Everything above is about what the *container* can connect to. A harness may
|
||||
also offer the session a tool whose network request does not come from the
|
||||
container at all, and the allowlist has no view of those.
|
||||
|
||||
Claude Code's `WebSearch` is the instance to reason from. Observed in one
|
||||
session, in this container:
|
||||
|
||||
```
|
||||
$ curl https://aider.chat/docs/config/options.html
|
||||
curl: (6) Could not resolve host: aider.chat
|
||||
```
|
||||
|
||||
and in the same session, `WebSearch` returned that page's content. The
|
||||
retrieval did not use the container's network, because the container has no
|
||||
route to that host. The only channel into a session that is not the
|
||||
container's own network is the model API connection - which dejima already
|
||||
routes through the host-side credential proxy - so the search ran on the API
|
||||
side and its result arrived in the model's response.
|
||||
|
||||
That cuts both ways, and neither is closed here:
|
||||
|
||||
- **Inward.** Text from any site on the internet can enter the session without
|
||||
the DNS allowlist seeing the name. The allowlist is described elsewhere in
|
||||
this document as bounding where a prompt injection could fetch its next
|
||||
instruction from; that is true of anything the container fetches, and not
|
||||
true of this.
|
||||
- **Outward.** A search query is session-controlled text sent to a third
|
||||
party. It is low bandwidth next to an HTTP body, but it is a channel out
|
||||
that no `.dejima/allowed-domains` line was ever reviewed for.
|
||||
|
||||
What this is not is unmonitored. The traffic rides the model API connection,
|
||||
which in the default configuration is exactly the connection dejima terminates
|
||||
in-container and forwards through its own host-side proxy - so it crosses a
|
||||
process the operator runs. dejima does not inspect what crosses it, and this
|
||||
document does not claim it does.
|
||||
|
||||
The lever an operator has is the harness's own, not dejima's: Claude Code
|
||||
takes a `permissions.deny` entry for a tool name, and dejima already writes a
|
||||
settings overlay it could go in. It is not written there by default, because
|
||||
the tool is useful and the exposure is a search query rather than a file.
|
||||
|
||||
**This is not a Claude Code quirk, it is a harness question.** Any harness may
|
||||
ship a tool whose fetch happens somewhere other than the container - Cline's
|
||||
tool set includes `fetch_web_content`, and which side executes it is a thing
|
||||
its integration has to establish rather than assume. A harness's own network
|
||||
reach belongs in its worked example in
|
||||
[development.md](development.md#adding-a-new-harness) alongside the rest of
|
||||
what it can and cannot do.
|
||||
|
||||
## Network request logging
|
||||
|
||||
If `[logging] loki_url` is set (see Prerequisites in the README), every DNS query and every new
|
||||
|
||||
@@ -129,6 +129,17 @@ What they change is what the row below it costs.
|
||||
|
||||
## What this repository does not claim
|
||||
|
||||
- **That the egress allowlist bounds what reaches the model.** It bounds what
|
||||
the container can connect to. A harness tool executed outside the container
|
||||
- Claude Code's `WebSearch` is the one confirmed by observation - puts text
|
||||
from any site into the session without the allowlist seeing the name, and
|
||||
sends a session-controlled query out to a third party. It rides the model
|
||||
API connection, so it crosses dejima's own credential proxy rather than
|
||||
going somewhere unseen, but nothing inspects it. Which of a harness's tools
|
||||
fetch from the container and which do not is a per-harness question, and
|
||||
belongs in that harness's assessment.
|
||||
[security.md](security.md#the-firewall-bounds-the-container-not-the-harness)
|
||||
has the evidence.
|
||||
- **That escape is impossible.** It is a row in the table with controls of its
|
||||
own, not an outcome that has been designed away.
|
||||
- **That the hooks stop a hostile agent.** They stop an unlucky one.
|
||||
|
||||
@@ -2641,6 +2641,28 @@ EOF
|
||||
done
|
||||
}
|
||||
|
||||
# The egress allowlist is the control this repository leans on hardest in
|
||||
# prose - it is named as what bounds where a prompt injection could fetch its
|
||||
# next instruction from. That is true of what the container fetches and not of
|
||||
# a harness tool executed elsewhere, so both documents have to keep saying so.
|
||||
# A reader who takes the allowlist as the boundary and is not corrected has
|
||||
# been misled by us, not by the harness.
|
||||
@test "both documents say the egress allowlist does not bound a harness's own tools" {
|
||||
local sec="$REPO_ROOT/docs/security.md" tm="$REPO_ROOT/docs/threat-model.md"
|
||||
grep -q 'The firewall bounds the container, not the harness' "$sec" \
|
||||
|| { echo "security.md drops the limit of the allowlist"; return 1; }
|
||||
# The evidence, not just the assertion: a reader has to be able to see why
|
||||
# this is a fact about the setup rather than a worry about it.
|
||||
grep -q 'Could not resolve host: aider.chat' "$sec" \
|
||||
|| { echo "security.md asserts it without the observation behind it"; return 1; }
|
||||
grep -q 'egress allowlist bounds what reaches the model' "$tm" \
|
||||
|| { echo "the threat model does not disclaim it"; return 1; }
|
||||
# And that it is framed per harness rather than as one harness's quirk,
|
||||
# since the next harness has to be asked the same question.
|
||||
grep -q 'per-harness question' "$tm" \
|
||||
|| { echo "the threat model reads as a Claude Code quirk"; return 1; }
|
||||
}
|
||||
|
||||
@test "docs/threat-model.md exists and is linked from the README and docs/security.md" {
|
||||
[ -f "$REPO_ROOT/docs/threat-model.md" ]
|
||||
grep -q 'docs/threat-model\.md' "$REPO_ROOT/README.md"
|
||||
|
||||
Reference in New Issue
Block a user