Compare commits

..

3 Commits

Author SHA1 Message Date
didericis-claude 39a5122f68 build: drop unused agent-image apt deps
lint / lint (push) Successful in 1m24s
Removes socat, openssh-client, and dnsutils from Dockerfile.claude
and Dockerfile.codex.

- socat was the privileged forwarder for the in-container ssh-agent
  that PRD 0009 removed; nothing in bot_bottle references it.
- openssh-client was needed back when the agent talked ssh:// to
  upstreams; git-gate's insteadOf rewrites now route every upstream
  through HTTP/git-protocol, and ssh-keygen runs host-side from the
  deploy-key provisioner.
- dnsutils was only used by tests/integration/test_sandbox_escape.py
  (attack 4b runs dig from inside the agent container).

Splits python3/python3-pip/python3-venv onto a separate layer with
a comment noting they're app-specific and a candidate to move to a
downstream image.
2026-06-06 16:19:46 -04:00
didericis-claude e82bbb587f refactor(egress): centralize block logging in _block helper
test / unit (pull_request) Successful in 30s
test / integration (pull_request) Successful in 49s
lint / lint (push) Successful in 1m26s
test / unit (push) Successful in 31s
test / integration (push) Successful in 49s
Update Quality Badges / update-badges (push) Successful in 1m13s
2026-06-06 17:00:42 +00:00
didericis-claude c89a0d334a feat(egress): log block reason to stderr on blocked requests
lint / lint (push) Successful in 1m24s
test / unit (pull_request) Successful in 31s
test / integration (pull_request) Successful in 41s
2026-06-06 16:56:26 +00:00
+16 -23
View File
@@ -82,6 +82,14 @@ class EgressAddon:
{"Content-Type": "text/plain; charset=utf-8"},
)
def _block(self, flow: http.HTTPFlow, reason: str) -> None:
sys.stderr.write(f"{reason}\n")
flow.response = http.Response.make(
403,
reason.encode("utf-8"),
{"Content-Type": "text/plain; charset=utf-8"},
)
def request(self, flow: http.HTTPFlow) -> None:
request_path, _, query = flow.request.path.partition("?")
@@ -100,25 +108,18 @@ class EgressAddon:
scan_text = auth_header + "\n" + body
dlp_result = scan_outbound(route, scan_text, os.environ)
if dlp_result is not None and dlp_result.severity == "block":
flow.response = http.Response.make(
403,
f"egress DLP: {dlp_result.reason}".encode("utf-8"),
{"Content-Type": "text/plain; charset=utf-8"},
)
self._block(flow, f"egress DLP: {dlp_result.reason}")
return
# Strip inbound Authorization — agent cannot smuggle tokens.
flow.request.headers.pop("authorization", None)
if is_git_push_request(request_path, query):
flow.response = http.Response.make(
403,
(
b"egress: git push over HTTPS is not supported; "
b"use the bottle.git SSH path (gitleaks-scanned by "
b"git-gate's pre-receive hook)."
),
{"Content-Type": "text/plain; charset=utf-8"},
self._block(
flow,
"egress: git push over HTTPS is not supported; "
"use the bottle.git SSH path (gitleaks-scanned by "
"git-gate's pre-receive hook).",
)
return
@@ -135,11 +136,7 @@ class EgressAddon:
)
if decision.action == "block":
flow.response = http.Response.make(
403,
decision.reason.encode("utf-8"),
{"Content-Type": "text/plain; charset=utf-8"},
)
self._block(flow, decision.reason)
return
if decision.inject_authorization is not None:
@@ -159,11 +156,7 @@ class EgressAddon:
if result is None:
return
if result.severity == "block":
flow.response = http.Response.make(
403,
f"egress DLP: {result.reason}".encode("utf-8"),
{"Content-Type": "text/plain; charset=utf-8"},
)
self._block(flow, f"egress DLP: {result.reason}")
elif result.severity == "warn":
sys.stderr.write(f"egress DLP warn: {result.reason}\n")