Repository navigation
Conversation
…executor
makeActuateFn checked only the first URL against the module's egress allow-list and
then sent with http.DefaultClient, which follows redirects (a redirect target was never
re-checked), honours the proxy environment, has no timeout, and read the response with
io.ReadAll. The production wiring passes a nil doer, so this was the live path.
- newEgressClient: a redirect is never followed (the 3xx is returned), the proxy
environment is ignored, connect/request timeouts are set, and every connection is
checked at dial time against the blocked address ranges (private, loopback,
link-local, unspecified, broadcast; IPv4-mapped unwrapped), which also covers a DNS
answer pointing at a private address. A nil doer now defaults to it.
- responses over 1 MiB are a typed error (code response_too_large) and are never
held in memory: the declared length is checked first and the read stops one byte
past the cap. The cap applies to any injected doer as well.
- typed errors are {"error": msg, "code": code}; "error" stays the key guests read.
- testdata/egress-parity/policy.txt is a fixture both executors assert (the same
limits and the same blocked/allowed address verdicts): a Go test and a Rust test in
http-executor, so a drift on either side fails.
The registry default test pinned http.DefaultClient and is updated.
Not aligned: the Rust executor allows only https in its production shape; the Go
path allows http when the module manifest permits it, which is a manifest-level
decision.
Refs #163
---
[signing-metadata]
key = cic-my-sign-key
signature = vault:v1:MEUCIE77Azt26wus6/rJkucMqPXevxJQ75DCjw8cJsmCtwZ9AiEA2omc+J3UvVJCjcw58r8xQ/19CxeGAJQXP6k7DSLwMOg=
hash-algorithm = sha256
digest = KIrQlLKOFiO+gCJDyIPwi9Q2AojDPEdz/qQH1w/l3AI=
[certificate]
-----BEGIN CERTIFICATE-----
MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw
QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV
BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy
MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb
MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe
i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU
yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS
WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0
lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW
-----END CERTIFICATE-----
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #163
What
makeActuateFnchecked only the first URL against the module's egress allow-list and then sent withhttp.DefaultClient: redirects were followed (a redirect target was never re-checked), the proxy environment was honoured, there was no timeout, and the response was read withio.ReadAll. The production wiring passes a nil doer (cmd/relay/main.go:664), so this was the live path.newEgressClient(newcmd/relay/egress_client.go): a redirect is never followed (the 3xx is returned to the caller), the proxy environment is ignored, connect and request timeouts are set (10 s / 30 s), and every connection is checked at dial time, after DNS resolution, against the blocked address ranges (private, loopback, link-local, unspecified, broadcast; IPv4-mapped unwrapped), the same ranges as the Rusthttp-executor. A nil doer now defaults to it.{"error": ..., "code": "response_too_large"}and are never held in memory: the declared length is checked first and the read stops one byte past the cap. The cap applies to any injected doer. A blocked address iscode: "blocked_address"."error"stays the key guests already read;"code"is additive.testdata/egress-parity/policy.txtis a fixture both executors assert (three limits and 25 address verdicts):TestEgressParity_Fixturein Go andegress_parity_fixture_matches_policyinhttp-executor. Plain text, so the Rust crate gets no new dependency.Claim check against the issue
All three acceptance points are covered. The issue's "not checked" point is settled: the production wiring does pass a nil doer. Additions: the existing
TestNewCICFlowRegistry_DefaultsDoerpinnedhttp.DefaultClientand is updated; the Rust production policy is https-only while the Go path follows the module manifest (not aligned here, a manifest-level decision).Behaviour change
The default client no longer dials private, loopback or link-local addresses. A module whose manifest allow-lists an internal host now gets
blocked_address. I did not review production manifests; that is the owner's call. Tests build the client withallowPrivate=true(the counterpart of the Rustinsecure_allowing); there is no such switch on the production path.Tests
With the old behaviour emulated (a default-like client, unbounded read) these fail: all three cases of
TestActuate_RedirectToNonAllowedHost_NotFollowed, all three ofTestActuate_ResponseOverCap_TypedError,TestEgressClient_DialGuard_BlocksLoopback,TestEgressClient_IgnoresProxyEnvironment,TestEgressClient_DialGuard_MalformedAddressNotDialed,TestNewCICFlowRegistry_DefaultsDoer.They pass on the old behaviour, so they document rather than protect:
TestEgressParity_Fixture(it exercisesisBlockedIP, which is unchanged) andTestActuate_ResponseAtCap_NotRefused. Mutating the fixture (a limit and one address verdict) fails both the Go and the Rust test.TestEgressClient_IgnoresProxyEnvironmentasserts the transport (Proxy == nil), because Go never proxies loopback, so a request-level check against a local server could not fail.Local gates. Go (Docker
golang:1.26.11):gofmt -s -lempty,go vet ./...clean,staticcheck ./cmd/relay/clean,go test -count=1 ./...allok,go test -race ./cmd/relay/ok,SYMBOLS.mdunchanged (no new exported symbol),MANIFEST.sha256diff empty. Rust (Dockerrust:1,--no-default-features):cargo test -p http-executor --locked --lib29/29,cargo fmt --check,cargo clippy --all-targets -D warningsclean.Reachability
cmd/relay/main.go:664newCICFlowRegistry(..., nil)->newEgressClient(false)->makeActuateFn(registered atcic_flow_host_funcs.go:85) ->deps.doer.Do-> response cap. No new entry indeadcode ./....Not covered / notes
max_request_bytes(request cap) is not mirrored; the issue does not ask for it.rustlsfeature,coverage-rust,deny-rust.main()was not run.🤖 Generated with Claude Code