config: apply default frontingsnis without a country code; preserve baked-in SNI#11
Conversation
…aked-in SNI ExpandedProvider previously generated an SNI only when a country code was set (countryCode != ''). The production client (radiance NewFronted) sets no country code, so every provider's arbitrary-SNI strategy was inert and all fronting used no SNI — conspicuous, since a browser-fingerprinted ClientHello normally carries SNI. Two changes: - The 'default' frontingsnis entry now applies even with no country code (country-specific entries still override). This activates a provider's default arbitrary-SNI strategy for every client — e.g. akamai's default list (verified: 20/20 edges front 202 with arbitrary SNI, same as no SNI, so no regression). akamai's explicit cn entry (usearbitrarysnis false) still wins for China, preserving no-SNI there. - A masquerade's baked-in SNI is now preserved through expansion unless an arbitrary SNI is generated for it. This lets the config pin a per-edge front SNI (e.g. aliyun edges that reject most SNIs but accept the service domain they actually host) without relying on a country code. Established behavior is unchanged for providers with no frontingsnis or a false default and no baked-in SNI (cloudfront): SNI stays empty. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
ChangesExpandedProvider SNI handling
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
config.go (1)
165-171: 🎯 Functional Correctness | 🟡 MinorNormalize
countryCodebefore the SNI map lookup.WithCountryCodestores the value as-is, so an upper- or mixed-case code will missFrontingSNIs["br"]and fall back to"default";strings.ToLower(countryCode)avoids that.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@config.go` around lines 165 - 171, Normalize the country code before looking up entries in FrontingSNIs so mixed- or upper-case values still match the intended SNI config. Update the lookup logic in the code that uses WithCountryCode and FrontingSNIs to convert countryCode to lower case before indexing the map, while preserving the existing default fallback behavior.
🧹 Nitpick comments (1)
config_test.go (1)
92-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a case for a no-SNI country override against a baked-in SNI.
The new tests cover baked-in preservation when there's no fronting strategy and override when one generates a value, but not the interaction flagged in
config.go: a country entry withUseArbitrarySNIs: false(intended to suppress SNI) applied to a masquerade that carries a baked-inSNI. This is the scenario whose behavior changed; a test would pin down the intended outcome.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@config_test.go` around lines 92 - 101, Add a test in config_test.go that exercises ExpandedProvider with a country override using UseArbitrarySNIs: false on a Provider whose Masquerade already has a baked-in SNI. The current coverage in ExpandedProvider only checks preservation with no override and replacement when arbitrary SNIs are generated, so add a case that applies the no-SNI country override and asserts the intended final SNI behavior for the baked-in masquerade.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@config.go`:
- Around line 165-171: Normalize the country code before looking up entries in
FrontingSNIs so mixed- or upper-case values still match the intended SNI config.
Update the lookup logic in the code that uses WithCountryCode and FrontingSNIs
to convert countryCode to lower case before indexing the map, while preserving
the existing default fallback behavior.
---
Nitpick comments:
In `@config_test.go`:
- Around line 92-101: Add a test in config_test.go that exercises
ExpandedProvider with a country override using UseArbitrarySNIs: false on a
Provider whose Masquerade already has a baked-in SNI. The current coverage in
ExpandedProvider only checks preservation with no override and replacement when
arbitrary SNIs are generated, so add a case that applies the no-SNI country
override and asserts the intended final SNI behavior for the baked-in
masquerade.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 81fcbfa9-5017-4aaf-8752-3b7ff5e5a655
📒 Files selected for processing (2)
config.goconfig_test.go
There was a problem hiding this comment.
Pull request overview
This PR updates ExpandedProvider’s masquerade expansion so providers can emit a legitimate TLS SNI in more cases (notably when the production client provides no country code), while also preserving any SNI baked into individual masquerades unless an arbitrary SNI is generated.
Changes:
- Apply
frontingsnis.defaulteven whencountryCode == "", while preserving country-specific overrides. - Preserve baked-in masquerade
sniunless a generated arbitrary SNI is produced (generated > baked-in > empty). - Extend
TestExpandedProviderto cover default-without-country, baked-in preservation, and precedence rules.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| config.go | Updates SNI strategy selection and masquerade SNI precedence during provider expansion. |
| config_test.go | Adds test cases validating the new default/baked-in SNI behavior and precedence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…pilot) ExpandedProvider set VerifyHostname only from the provider default; when nil, dialFront's SNI path verified the edge cert chain-only (any cert chaining to a trusted root — for aliyun's single GlobalSign-R3 pool, any R3-issued cert a MITM could present). This PR populates SNI more often, so the weak path applied more widely. Fix: default VerifyHostname to the front Domain (and preserve a per-masquerade value if set). Verify against Domain, NOT the SNI: the SNI is often a decoy the served cert doesn't cover — akamai edges send SNI=crunchbase.com but serve their CN=a248.e.akamai.net cert (confirmed: valid for the Domain, not the SNI). Verifying against the SNI broke akamai (0/20 front); against Domain it's 20/20, and aliyun is unchanged (~55%) since the *.alicdn.com cert covers img.alicdn.com. This matches the hostname check the no-SNI path already performs against Domain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
config_test.go (1)
124-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a provider-level
VerifyHostnameprecedence case.This covers the middle branch in
ExpandedProvider: per-masquerade wins, then provider default, then derivedDomain.Suggested test case
+ t.Run("provider VerifyHostname wins over the SNI default", func(t *testing.T) { + pinned := "provider.example" + pp := &Provider{ + VerifyHostname: &pinned, + Masquerades: []*Masquerade{{Domain: "img.alicdn.com", IpAddress: "1.2.3.4", SNI: "www.mobgslb.tbcache.com"}}, + } + ep := ExpandedProvider(pp, "") + require.Len(t, ep.Masquerades, 1) + require.NotNil(t, ep.Masquerades[0].VerifyHostname) + assert.Equal(t, "provider.example", *ep.Masquerades[0].VerifyHostname) + }) + t.Run("per-masquerade VerifyHostname wins over the SNI default", func(t *testing.T) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@config_test.go` around lines 124 - 133, Add a provider-level VerifyHostname precedence test for ExpandedProvider by extending config_test.go with a case that sets Provider.VerifyHostname and leaves Masquerade.VerifyHostname unset; assert the expanded masquerade inherits the provider default. Keep the existing per-masquerade VerifyHostname check in place so the precedence order in ExpandedProvider is covered: per-masquerade first, then provider-level default, then derived Domain fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@config_test.go`:
- Around line 124-133: Add a provider-level VerifyHostname precedence test for
ExpandedProvider by extending config_test.go with a case that sets
Provider.VerifyHostname and leaves Masquerade.VerifyHostname unset; assert the
expanded masquerade inherits the provider default. Keep the existing
per-masquerade VerifyHostname check in place so the precedence order in
ExpandedProvider is covered: per-masquerade first, then provider-level default,
then derived Domain fallback.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4d41e67a-c64f-41ee-b002-d0c78e222644
📒 Files selected for processing (2)
config.goconfig_test.go
Build the expanded masquerade first and point VerifyHostname at its own Domain field when defaulting, instead of taking the address of a loop-local copy (which escaped to the heap each iteration the fallback applied). Behavior unchanged; no extra allocation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picks up getlantern/domainfront#11: ExpandedProvider applies the default frontingsnis strategy even with no country code (so akamai sends its arbitrary SNIs globally instead of the conspicuous no-SNI), preserves a baked-in per-masquerade SNI (for aliyun's www.mobgslb.tbcache.com), and verifies the SNI-path edge cert against the front Domain instead of chain-only. No kindling code changes; build + tests pass. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…I) (#539) domainfront v0.0.0-...-93591749d736 -> 518c0256669b (getlantern/domainfront#11) kindling v0.0.0-...-737fcffe2860 -> 7cdf7184420c (getlantern/kindling#41) Activates legit SNI for fronting: ExpandedProvider applies the default frontingsnis strategy with no country code (akamai sends arbitrary SNIs globally), preserves a baked-in per-masquerade SNI (aliyun mobgslb), and verifies the SNI-path edge cert against the front Domain (not chain-only). The kindling/fronted packages (which consume domainfront) build and test green. The pre-existing cmd/lantern build break (ipc.NewClient signature) is unrelated to this bump and reproduces on main with these changes stashed. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
) radiance -> v0.0.0-20260625003855-687d6be3d5f0 (getlantern/radiance#539) domainfront -> v0.0.0-20260625001429-518c0256669b (getlantern/domainfront#11) kindling -> v0.0.0-20260625002640-7cdf7184420c (getlantern/kindling#41) Final link in the chain that lets fronting send a legit SNI instead of the conspicuous no-SNI clients use today: ExpandedProvider applies the default frontingsnis strategy with no country code (akamai sends its arbitrary SNIs globally), preserves a baked-in per-masquerade SNI (aliyun www.mobgslb.tbcache.com), and verifies the SNI-path edge cert against the front Domain rather than chain-only. go build/vet clean; only go.mod and go.sum change. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary
ExpandedProviderresolves each masquerade's SNI. Two changes so providers can actually send a (legit) SNI, instead of the conspicuous no-SNI every client uses today.Background
The production client (
radiance/kindling/fronted/fronted.go::NewFronted, the only non-testdomainfront.Newcaller) passes no country code. The old code generated an SNI only whencountryCode != "":So every provider's arbitrary-SNI strategy was inert and all fronting went out with no SNI — measured directly across akamai, aliyun, cloudfront (all masquerades carry
sni: ""). A no-SNI TLS ClientHello that otherwise looks like Chrome is itself anomalous (real Chrome always sends SNI), so this is a fingerprinting liability.Changes
defaultfrontingsnis applies without a country code. Drop thecountryCode != ""guard; a country-specific entry still overridesdefault. This activates a provider's default arbitrary-SNI strategy for every client.defaultisusearbitrarysnis: true(4,638 real akamai-customer SNIs) — now active globally. Verified from a live client: 20/20 sampled edges front (202) with an arbitrary SNI, identical to no-SNI (20/20) → no regression, and stealthier.cnentry (usearbitrarysnis: false) still wins for China → no-SNI preserved there (existing intent).Baked-in masquerade SNI is preserved through expansion unless an arbitrary SNI is generated. This lets the config pin a per-edge front SNI without a country code — needed for aliyun, whose edges reject most SNIs but accept the specific service domain they host (e.g.
www.mobgslb.tbcache.com).Precedence per masquerade: generated arbitrary SNI > baked-in SNI > empty (omit).
Safety
Audited the live config's providers: only akamai (
default: true, desired) and aliyun (configured separately) have a truthy default; cloudfront isfrontingsnis: {}. Providers with no frontingsnis / afalsedefault / no baked-in SNI are unchanged (SNI stays empty). Backward-compatible at rollout: an old client (pre-change) reading a config withusearbitrarysnis: truestill omits SNI (its guard blocks it), so nothing breaks before the client updates.Tests
TestExpandedProvidergains: default arbitrary SNI applies with no country code; baked-in SNI preserved when none generated; generated SNI overrides baked-in. Existing cases (no-SNI default, country override, lowercasing) still pass.Follow-ups (separate PRs)
provider_map.yaml: set aliyun's front SNI towww.mobgslb.tbcache.com(works on ~83% of edges vs ~17% forimg.alicdn.com; supersedes the omit-SNI direction of lantern-cloud#2896).🤖 Generated with Claude Code
Summary by CodeRabbit
VerifyHostnamefallback/precedence.