fix: add lowercase proxy env vars, route_to_yaml_dict, and richer tool descriptions
- Set http_proxy/https_proxy (lowercase) alongside uppercase variants in smolmachines guest env for tools that only check lowercase - Replace dataclasses.asdict with route_to_yaml_dict in /allowlist introspection so returned routes use YAML-schema-compatible keys - Expand routes_yaml tool description in supervise_server to document all accepted route keys, making the round-trip from list-egress-routes to propose/apply explicit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -217,11 +217,15 @@ def _discover_urls(
|
|||||||
agent_supervise_url = f"http://{loopback_ip}:{supervise_host_port}/"
|
agent_supervise_url = f"http://{loopback_ip}:{supervise_host_port}/"
|
||||||
|
|
||||||
existing_no_proxy = plan.guest_env.get("NO_PROXY", "localhost,127.0.0.1")
|
existing_no_proxy = plan.guest_env.get("NO_PROXY", "localhost,127.0.0.1")
|
||||||
|
no_proxy = f"{existing_no_proxy},{loopback_ip}"
|
||||||
guest_env = {
|
guest_env = {
|
||||||
**plan.guest_env,
|
**plan.guest_env,
|
||||||
"HTTPS_PROXY": agent_proxy_url,
|
"HTTPS_PROXY": agent_proxy_url,
|
||||||
"HTTP_PROXY": agent_proxy_url,
|
"HTTP_PROXY": agent_proxy_url,
|
||||||
"NO_PROXY": f"{existing_no_proxy},{loopback_ip}",
|
"https_proxy": agent_proxy_url,
|
||||||
|
"http_proxy": agent_proxy_url,
|
||||||
|
"NO_PROXY": no_proxy,
|
||||||
|
"no_proxy": no_proxy,
|
||||||
}
|
}
|
||||||
if agent_git_gate_host:
|
if agent_git_gate_host:
|
||||||
guest_env["GIT_GATE_URL"] = f"http://{agent_git_gate_host}"
|
guest_env["GIT_GATE_URL"] = f"http://{agent_git_gate_host}"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ egress container."""
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import dataclasses
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
@@ -27,6 +26,7 @@ from egress_addon_core import ( # type: ignore[import-not-found] # pylint: dis
|
|||||||
load_config,
|
load_config,
|
||||||
match_route,
|
match_route,
|
||||||
outbound_scan_headers,
|
outbound_scan_headers,
|
||||||
|
route_to_yaml_dict,
|
||||||
scan_inbound,
|
scan_inbound,
|
||||||
scan_outbound,
|
scan_outbound,
|
||||||
)
|
)
|
||||||
@@ -82,7 +82,7 @@ class EgressAddon:
|
|||||||
def _serve_introspection(self, flow: http.HTTPFlow, path: str) -> None:
|
def _serve_introspection(self, flow: http.HTTPFlow, path: str) -> None:
|
||||||
if path == "/allowlist":
|
if path == "/allowlist":
|
||||||
payload = json.dumps(
|
payload = json.dumps(
|
||||||
{"routes": [dataclasses.asdict(r) for r in self.config.routes]},
|
{"routes": [route_to_yaml_dict(r) for r in self.config.routes]},
|
||||||
indent=2,
|
indent=2,
|
||||||
).encode("utf-8")
|
).encode("utf-8")
|
||||||
flow.response = http.Response.make(
|
flow.response = http.Response.make(
|
||||||
|
|||||||
@@ -359,6 +359,56 @@ def _parse_one(idx: int, raw: object) -> Route:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _path_match_to_dict(pm: PathMatch) -> dict[str, object]:
|
||||||
|
d: dict[str, object] = {"value": pm.value}
|
||||||
|
if pm.type != "prefix":
|
||||||
|
d["type"] = pm.type
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _header_match_to_dict(hm: HeaderMatch) -> dict[str, object]:
|
||||||
|
d: dict[str, object] = {"name": hm.name, "value": hm.value}
|
||||||
|
if hm.type != "exact":
|
||||||
|
d["type"] = hm.type
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def _match_entry_to_dict(me: MatchEntry) -> dict[str, object]:
|
||||||
|
d: dict[str, object] = {}
|
||||||
|
if me.paths:
|
||||||
|
d["paths"] = [_path_match_to_dict(p) for p in me.paths]
|
||||||
|
if me.methods:
|
||||||
|
d["methods"] = list(me.methods)
|
||||||
|
if me.headers:
|
||||||
|
d["headers"] = [_header_match_to_dict(h) for h in me.headers]
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def route_to_yaml_dict(r: Route) -> dict[str, object]:
|
||||||
|
"""Serialize a Route to YAML-schema-compatible dict.
|
||||||
|
|
||||||
|
Uses the same field names the YAML parser accepts, so the output
|
||||||
|
can be round-tripped directly into an `allow` or `egress-block`
|
||||||
|
proposal without translation. Fields that are empty/default are
|
||||||
|
omitted so the agent doesn't copy irrelevant keys."""
|
||||||
|
d: dict[str, object] = {"host": r.host}
|
||||||
|
if r.auth_scheme:
|
||||||
|
d["auth_scheme"] = r.auth_scheme
|
||||||
|
d["token_env"] = r.token_env
|
||||||
|
if r.matches:
|
||||||
|
d["matches"] = [_match_entry_to_dict(m) for m in r.matches]
|
||||||
|
if r.git_fetch:
|
||||||
|
d["git"] = {"fetch": True}
|
||||||
|
dlp: dict[str, object] = {}
|
||||||
|
if r.outbound_detectors is not None:
|
||||||
|
dlp["outbound_detectors"] = list(r.outbound_detectors)
|
||||||
|
if r.inbound_detectors is not None:
|
||||||
|
dlp["inbound_detectors"] = list(r.inbound_detectors)
|
||||||
|
if dlp:
|
||||||
|
d["dlp"] = dlp
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def load_routes(text: str) -> tuple[Route, ...]:
|
def load_routes(text: str) -> tuple[Route, ...]:
|
||||||
"""Parse YAML text → routes."""
|
"""Parse YAML text → routes."""
|
||||||
try:
|
try:
|
||||||
@@ -698,6 +748,7 @@ def scan_inbound(
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LOG_BLOCKS",
|
"LOG_BLOCKS",
|
||||||
|
"route_to_yaml_dict",
|
||||||
"LOG_FULL",
|
"LOG_FULL",
|
||||||
"LOG_OFF",
|
"LOG_OFF",
|
||||||
"Config",
|
"Config",
|
||||||
|
|||||||
@@ -172,7 +172,24 @@ TOOL_DEFINITIONS: list[dict[str, object]] = [
|
|||||||
"properties": {
|
"properties": {
|
||||||
"routes_yaml": {
|
"routes_yaml": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Full proposed /etc/egress/routes.yaml content.",
|
"description": (
|
||||||
|
"Full proposed /etc/egress/routes.yaml content. "
|
||||||
|
"Each route entry accepts these keys:\n"
|
||||||
|
" host: <hostname> (required)\n"
|
||||||
|
" auth_scheme: Bearer|token (must pair with token_env)\n"
|
||||||
|
" token_env: <ENV_VAR_NAME> (must pair with auth_scheme)\n"
|
||||||
|
" matches: (optional list of match entries)\n"
|
||||||
|
" - paths: [{type: prefix|exact|regex, value: /...}]\n"
|
||||||
|
" methods: [GET, POST, ...]\n"
|
||||||
|
" headers: [{name: X-Hdr, value: val, type: exact|regex}]\n"
|
||||||
|
" git: (optional; omit to block git clone/fetch)\n"
|
||||||
|
" fetch: true\n"
|
||||||
|
" dlp: (optional DLP scanner overrides)\n"
|
||||||
|
" outbound_detectors: [token_patterns, known_secrets]\n"
|
||||||
|
" inbound_detectors: [naive_injection_detection]\n"
|
||||||
|
"Omit any key that should use its default. "
|
||||||
|
"`list-egress-routes` returns routes in this same format."
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"justification": {
|
"justification": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -196,7 +213,24 @@ TOOL_DEFINITIONS: list[dict[str, object]] = [
|
|||||||
"properties": {
|
"properties": {
|
||||||
"routes_yaml": {
|
"routes_yaml": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Full proposed /etc/egress/routes.yaml content.",
|
"description": (
|
||||||
|
"Full proposed /etc/egress/routes.yaml content. "
|
||||||
|
"Each route entry accepts these keys:\n"
|
||||||
|
" host: <hostname> (required)\n"
|
||||||
|
" auth_scheme: Bearer|token (must pair with token_env)\n"
|
||||||
|
" token_env: <ENV_VAR_NAME> (must pair with auth_scheme)\n"
|
||||||
|
" matches: (optional list of match entries)\n"
|
||||||
|
" - paths: [{type: prefix|exact|regex, value: /...}]\n"
|
||||||
|
" methods: [GET, POST, ...]\n"
|
||||||
|
" headers: [{name: X-Hdr, value: val, type: exact|regex}]\n"
|
||||||
|
" git: (optional; omit to block git clone/fetch)\n"
|
||||||
|
" fetch: true\n"
|
||||||
|
" dlp: (optional DLP scanner overrides)\n"
|
||||||
|
" outbound_detectors: [token_patterns, known_secrets]\n"
|
||||||
|
" inbound_detectors: [naive_injection_detection]\n"
|
||||||
|
"Omit any key that should use its default. "
|
||||||
|
"`list-egress-routes` returns routes in this same format."
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"justification": {
|
"justification": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
Reference in New Issue
Block a user