commit 5b6865f990f36405f2fdfc0fbdb0052c8d128ff4
parent f951c238c0e5bc2c94f0fa4ed5808b960533b132
Author: smayya <smayya@mozilla.com>
Date: Fri, 17 Oct 2025 09:48:52 +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:
5 files changed, 126 insertions(+), 0 deletions(-)
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
@@ -14490,6 +14490,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/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp
@@ -260,6 +260,9 @@ static const char* gCallbackPrefsForSocketProcess[] = {
"network.lna.enabled",
"network.lna.blocking",
"network.lna.address_space.private.override",
+ "network.lna.address_space.public.override",
+ "network.lna.websocket.enabled",
+ "network.lna.local-network-to-localhost.skip-checks",
nullptr,
};
diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -3775,6 +3775,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");
@@ -535,3 +537,4 @@ add_task(async function test_lna_websocket_preference() {
await SpecialPowers.popPrefEnv();
});
+
diff --git a/netwerk/test/unit/test_local_network_access.js b/netwerk/test/unit/test_local_network_access.js
@@ -89,6 +89,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);
@@ -114,6 +121,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"
@@ -334,6 +345,7 @@ 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 network.lna.skip-domains preference
@@ -574,3 +586,97 @@ add_task(async function lna_domain_skip_tests() {
override.clearOverrides();
Services.dns.clearCache(true);
});
+// 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"
+ );
+});