From 30e39577c2cf1bdaae8926f20739fd0217a26d79 Mon Sep 17 00:00:00 2001 From: didericis Date: Tue, 21 Jul 2026 20:17:04 -0400 Subject: [PATCH] fix(egress): emit preserve_auth into the generated routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flag from #453 was parsed off the manifest and honored by the addon, but `_route_to_yaml_fields` never wrote it, so it was dropped on the way to the proxy and has never worked end to end. Every registry route silently kept the unconditional Authorization strip. That is the whole of the Docker Hub / GHCR "unauthorized" blocker in #392: podman fetched a valid 2658-byte bearer from auth.docker.io, the proxy stripped it, and registry-1.docker.io answered the resulting anonymous request with a fresh WWW-Authenticate challenge — which reads exactly like a credential problem rather than a serializer gap. Found by diffing a bottle's rendered routes.yaml against its manifest: preserve_auth was in the manifest and absent from the output. Co-Authored-By: Claude Opus 4.8 --- bot_bottle/egress.py | 4 ++++ tests/unit/test_egress.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/bot_bottle/egress.py b/bot_bottle/egress.py index 663996e..22b3dfc 100644 --- a/bot_bottle/egress.py +++ b/bot_bottle/egress.py @@ -255,6 +255,8 @@ def _route_to_yaml_fields(r: Route) -> dict[str, object]: fields["matches"] = matches_data if r.git_fetch: fields["git"] = {"fetch": True} + if r.preserve_auth: + fields["preserve_auth"] = True if ( r.outbound_detectors is not None or r.inbound_detectors is not None @@ -335,6 +337,8 @@ def egress_render_routes( lines.append(" git:") if git_dict.get("fetch") is True: lines.append(" fetch: true") + if f.get("preserve_auth") is True: + lines.append(" preserve_auth: true") if "dlp" in f: dlp_dict: dict[str, object] = f["dlp"] # type: ignore lines.append(" dlp:") diff --git a/tests/unit/test_egress.py b/tests/unit/test_egress.py index 2db81b7..43cb73d 100644 --- a/tests/unit/test_egress.py +++ b/tests/unit/test_egress.py @@ -379,6 +379,25 @@ class TestRenderRoutes(unittest.TestCase): addon_routes = load_config(rendered).routes self.assertTrue(addon_routes[0].git_fetch) + def test_preserve_auth_round_trips_to_the_addon(self): + """Regression: the manifest parsed preserve_auth and the addon honored + it, but the renderer in between dropped it — so the flag never reached + the proxy and registry pulls kept failing with "unauthorized" while + the config looked correct everywhere it was inspected.""" + from bot_bottle.egress_addon_core import load_config + b = _bottle([{"host": "registry-1.docker.io", "preserve_auth": True}]) + routes = egress_routes_for_bottle(b) + rendered = egress_render_routes(routes) + self.assertIn("preserve_auth: true", rendered) + self.assertTrue(load_config(rendered).routes[0].preserve_auth) + + def test_preserve_auth_omitted_when_unset(self): + b = _bottle([{"host": "x.example"}]) + rendered = egress_render_routes(egress_routes_for_bottle(b)) + self.assertNotIn("preserve_auth", rendered) + from bot_bottle.egress_addon_core import load_config + self.assertFalse(load_config(rendered).routes[0].preserve_auth) + def test_log_zero_omitted_from_render(self): b = _bottle([{"host": "x.example"}]) routes = egress_routes_for_bottle(b)