commit ffb69b1c90e243751100c01925d65ed30c1ae27a
parent bbed26719012cc184d36c8e31f7412996ef0e7ed
Author: smayya <smayya@mozilla.com>
Date: Thu, 16 Oct 2025 08:19:05 +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:
4 files changed, 124 insertions(+), 0 deletions(-)
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
@@ -14491,6 +14491,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"
+ );
});