tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 8f95bcdd2d67afa4718e7119761ad945846e6ad9
parent d3486cb9ee571efdb6d32a20031cfd89a0205519
Author: smayya <smayya@mozilla.com>
Date:   Wed, 15 Oct 2025 12:04:33 +0000

Bug 1993938 - Add preference to skip LNA checks for local network to localhost requests. r=necko-reviewers,valentin

This adds a new preference network.lna.local-network-to-localhost.skip-checks
that allows local network requests to localhost to bypass LNA checks when enabled.
This is useful for development scenarios where local network clients need
to access localhost services.

Differential Revision: https://phabricator.services.mozilla.com/D266339

Diffstat:
Mmodules/libpref/init/StaticPrefList.yaml | 7+++++++
Mnetwerk/protocol/http/nsHttpTransaction.cpp | 7+++++++
Mnetwerk/test/browser/browser_test_local_network_access.js | 3+++
Mnetwerk/test/unit/test_local_network_access.js | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 124 insertions(+), 0 deletions(-)

diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml @@ -14501,6 +14501,13 @@ value: false mirror: always +# When this pref is true, skip LNA checks for requests from private network +# to localhost (private -> local IP address space transitions). +- name: network.lna.local-network-to-localhost.skip-checks + type: RelaxedAtomicBool + value: true + mirror: always + # The proxy type. See nsIProtocolProxyService.idl # PROXYCONFIG_DIRECT = 0 # PROXYCONFIG_MANUAL = 1 diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -3767,6 +3767,13 @@ bool nsHttpTransaction::AllowedToConnectToIpAddressSpace( // for private network access // XXX add link to LNA spec once it is published + // Skip LNA checks for private network to localhost if preference is enabled + if (StaticPrefs::network_lna_local_network_to_localhost_skip_checks() && + mParentIPAddressSpace == nsILoadInfo::IPAddressSpace::Private && + aTargetIpAddressSpace == nsILoadInfo::IPAddressSpace::Local) { + return true; // Allow private->localhost access + } + if (mozilla::net::IsLocalOrPrivateNetworkAccess(mParentIPAddressSpace, aTargetIpAddressSpace)) { if (aTargetIpAddressSpace == nsILoadInfo::IPAddressSpace::Local && diff --git a/netwerk/test/browser/browser_test_local_network_access.js b/netwerk/test/browser/browser_test_local_network_access.js @@ -27,6 +27,8 @@ add_setup(async function () { ["network.lna.block_trackers", true], ["network.lna.blocking", true], ["network.http.rcwn.enabled", false], + ["network.lna.websocket.enabled", true], + ["network.lna.local-network-to-localhost.skip-checks", false], ], }); Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk"); @@ -534,4 +536,5 @@ add_task(async function test_lna_websocket_preference() { } await SpecialPowers.popPrefEnv(); + await SpecialPowers.popPrefEnv(); }); diff --git a/netwerk/test/unit/test_local_network_access.js b/netwerk/test/unit/test_local_network_access.js @@ -69,6 +69,13 @@ add_setup(async () => { Services.prefs.setBoolPref("network.localhost.prompt.testing", true); Services.prefs.setBoolPref("network.localnetwork.prompt.testing", true); + Services.prefs.setBoolPref( + "network.lna.local-network-to-localhost.skip-checks", + false + ); + + Services.prefs.setBoolPref("network.lna.websocket.enabled", true); + // H1 Server httpServer = new HttpServer(); httpServer.registerPathHandler("/test_lna", pathHandler); @@ -89,6 +96,10 @@ add_setup(async () => { Services.prefs.clearUserPref("network.lna.blocking.prompt.testing"); Services.prefs.clearUserPref("network.localhost.prompt.testing.allow"); Services.prefs.clearUserPref("network.localnetwork.prompt.testing.allow"); + Services.prefs.clearUserPref( + "network.lna.local-network-to-localhost.skip-checks" + ); + Services.prefs.clearUserPref("network.lna.websocket.enabled"); Services.prefs.clearUserPref( "network.lna.address_space.private.override" @@ -309,4 +320,100 @@ add_task(async function lna_blocking_tests_local_network() { Assert.equal(chan.protocolVersion, url === H1_URL ? "http/1.1" : "h2"); } } + Services.prefs.clearUserPref("network.lna.address_space.private.override"); +}); + +// Test the new network.lna.local-network-to-localhost.skip-checks preference +add_task(async function lna_local_network_to_localhost_skip_checks() { + // Test cases: [skipPref, parentSpace, urlSuffix, expectedStatus, baseURL] + const skipTestCases = [ + // Skip pref disabled (false) - existing behavior should be preserved + [ + false, + Ci.nsILoadInfo.Private, + "/test_lna", + Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, + H1_URL, + ], + [ + false, + Ci.nsILoadInfo.Private, + "/test_lna", + Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, + H2_URL, + ], + [ + false, + Ci.nsILoadInfo.Public, + "/test_lna", + Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, + H1_URL, + ], + [ + false, + Ci.nsILoadInfo.Public, + "/test_lna", + Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, + H2_URL, + ], + + // Skip pref enabled (true) - new behavior: Private->Local allowed, Public->Local still blocked + [true, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H1_URL], // Private->Local now allowed + [true, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H2_URL], // Private->Local now allowed + [ + true, + Ci.nsILoadInfo.Public, + "/test_lna", + Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, + H1_URL, + ], // Public->Local still blocked + [ + true, + Ci.nsILoadInfo.Public, + "/test_lna", + Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, + H2_URL, + ], // Public->Local still blocked + ]; + + for (let [ + skipPref, + parentSpace, + suffix, + expectedStatus, + url, + ] of skipTestCases) { + info( + `Testing skip pref: ${skipPref}, ${parentSpace} -> Local, expect: ${expectedStatus}` + ); + + // Set the new skip preference + Services.prefs.setBoolPref( + "network.lna.local-network-to-localhost.skip-checks", + skipPref + ); + + // Disable prompt simulation for clean testing (prompt should not affect skip logic) + Services.prefs.setBoolPref("network.localhost.prompt.testing.allow", false); + + let chan = makeChannel(url + suffix); + chan.loadInfo.parentIpAddressSpace = parentSpace; + // Target is always Local (localhost) since we're testing localhost servers + + let expectFailure = expectedStatus !== Cr.NS_OK ? CL_EXPECT_FAILURE : 0; + + await new Promise(resolve => { + chan.asyncOpen(new ChannelListener(resolve, null, expectFailure)); + }); + + Assert.equal(chan.status, expectedStatus); + if (expectedStatus === Cr.NS_OK) { + Assert.equal(chan.protocolVersion, url === H1_URL ? "http/1.1" : "h2"); + } + } + + // Cleanup + Services.prefs.clearUserPref( + "network.lna.local-network-to-localhost.skip-checks" + ); });