tor-browser

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

test_local_network_access.js (23214B)


      1 "use strict";
      2 
      3 const { HttpServer } = ChromeUtils.importESModule(
      4  "resource://testing-common/httpd.sys.mjs"
      5 );
      6 const { NodeHTTP2Server } = ChromeUtils.importESModule(
      7  "resource://testing-common/NodeServer.sys.mjs"
      8 );
      9 
     10 const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
     11  Ci.nsINativeDNSResolverOverride
     12 );
     13 
     14 function makeChannel(url, triggeringPrincipalURI = null) {
     15  let uri2 = NetUtil.newURI(url);
     16  // by default system principal is used, which cannot be used for permission based tests
     17  // because the default system principal has all permissions
     18  var principal = Services.scriptSecurityManager.createContentPrincipal(
     19    uri2,
     20    {}
     21  );
     22 
     23  // For LNA tests, we need a cross-origin triggering principal to test blocking behavior
     24  // If not specified, use a different origin to ensure cross-origin requests
     25  var triggeringPrincipal;
     26  if (triggeringPrincipalURI) {
     27    let triggeringURI = NetUtil.newURI(triggeringPrincipalURI);
     28    triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal(
     29      triggeringURI,
     30      {}
     31    );
     32  } else {
     33    // Default to a cross-origin principal (public.example.com)
     34    let triggeringURI = NetUtil.newURI("https://public.example.com");
     35    triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal(
     36      triggeringURI,
     37      {}
     38    );
     39  }
     40 
     41  return NetUtil.newChannel({
     42    uri: url,
     43    loadingPrincipal: principal,
     44    triggeringPrincipal,
     45    securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
     46    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
     47  }).QueryInterface(Ci.nsIHttpChannel);
     48 }
     49 
     50 var ChannelCreationObserver = {
     51  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
     52  observe(aSubject, aTopic) {
     53    if (aTopic == "http-on-opening-request") {
     54      var chan = aSubject.QueryInterface(Ci.nsIHttpChannel);
     55      if (chan.URI.spec.includes("test_lna_social_tracker")) {
     56        chan.loadInfo.triggeringThirdPartyClassificationFlags =
     57          Ci.nsIClassifiedChannel.CLASSIFIED_ANY_SOCIAL_TRACKING;
     58      } else if (chan.URI.spec.includes("test_lna_basic_tracker")) {
     59        chan.loadInfo.triggeringThirdPartyClassificationFlags =
     60          Ci.nsIClassifiedChannel.CLASSIFIED_ANY_BASIC_TRACKING;
     61      } else if (chan.URI.spec.includes("test_lna_content_tracker")) {
     62        chan.loadInfo.triggeringThirdPartyClassificationFlags =
     63          Ci.nsIClassifiedChannel.CLASSIFIED_TRACKING_CONTENT;
     64      }
     65    }
     66  },
     67 };
     68 
     69 ChromeUtils.defineLazyGetter(this, "H1_URL", function () {
     70  return "http://localhost:" + httpServer.identity.primaryPort;
     71 });
     72 
     73 ChromeUtils.defineLazyGetter(this, "H2_URL", function () {
     74  return "https://localhost:" + server.port();
     75 });
     76 
     77 ChromeUtils.defineLazyGetter(this, "H1_EXAMPLE_URL", function () {
     78  return "http://example.com:" + httpServer.identity.primaryPort;
     79 });
     80 
     81 ChromeUtils.defineLazyGetter(this, "H1_TEST_EXAMPLE_URL", function () {
     82  return "http://test.example.com:" + httpServer.identity.primaryPort;
     83 });
     84 
     85 ChromeUtils.defineLazyGetter(this, "H1_SERVER_LOCAL_URL", function () {
     86  return "http://server.local:" + httpServer.identity.primaryPort;
     87 });
     88 
     89 ChromeUtils.defineLazyGetter(this, "H1_API_DEV_LOCAL_URL", function () {
     90  return "http://api.dev.local:" + httpServer.identity.primaryPort;
     91 });
     92 
     93 let httpServer = null;
     94 let server = new NodeHTTP2Server();
     95 function pathHandler(metadata, response) {
     96  response.setStatusLine(metadata.httpVersion, 200, "OK");
     97  let body = "success";
     98  response.bodyOutputStream.write(body, body.length);
     99 }
    100 
    101 add_setup(async () => {
    102  Services.prefs.setBoolPref("network.lna.block_trackers", true);
    103  Services.obs.addObserver(ChannelCreationObserver, "http-on-opening-request");
    104  // fail transactions on Local Network Access
    105  Services.prefs.setBoolPref("network.lna.blocking", true);
    106 
    107  // enable prompt for prefs testing, with this we can simulate the prompt actions by
    108  // network.lna.blocking.prompt.allow = false/true
    109  Services.prefs.setBoolPref("network.localhost.prompt.testing", true);
    110  Services.prefs.setBoolPref("network.localnetwork.prompt.testing", true);
    111 
    112  Services.prefs.setBoolPref(
    113    "network.lna.local-network-to-localhost.skip-checks",
    114    false
    115  );
    116 
    117  Services.prefs.setBoolPref("network.lna.websocket.enabled", true);
    118 
    119  // H1 Server
    120  httpServer = new HttpServer();
    121  httpServer.registerPathHandler("/test_lna", pathHandler);
    122  httpServer.start(-1);
    123  // Add domain identities for testing domain skip patterns
    124  httpServer.identity.add("http", "example.com", 80);
    125  httpServer.identity.add("http", "test.example.com", 80);
    126  httpServer.identity.add("http", "server.local", 80);
    127  httpServer.identity.add("http", "api.dev.local", 80);
    128 
    129  // H2 Server
    130  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    131    Ci.nsIX509CertDB
    132  );
    133  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
    134 
    135  await server.start();
    136  registerCleanupFunction(async () => {
    137    try {
    138      await server.stop();
    139      await httpServer.stop();
    140      Services.prefs.clearUserPref("network.lna.blocking");
    141      Services.prefs.clearUserPref("network.lna.blocking.prompt.testing");
    142      Services.prefs.clearUserPref("network.localhost.prompt.testing.allow");
    143      Services.prefs.clearUserPref("network.localnetwork.prompt.testing.allow");
    144      Services.prefs.clearUserPref(
    145        "network.lna.local-network-to-localhost.skip-checks"
    146      );
    147      Services.prefs.clearUserPref("network.lna.websocket.enabled");
    148 
    149      Services.prefs.clearUserPref(
    150        "network.lna.address_space.private.override"
    151      );
    152    } catch (e) {
    153      // Ignore errors during cleanup
    154      info("Error during cleanup:", e);
    155    }
    156  });
    157  await server.registerPathHandler("/test_lna", (req, resp) => {
    158    let content = `ok`;
    159    resp.writeHead(200, {
    160      "Content-Type": "text/plain",
    161      "Content-Length": `${content.length}`,
    162    });
    163    resp.end(content);
    164  });
    165 });
    166 
    167 // This test simulates the failure of transaction due to local network access
    168 // (local host) and subsequent retries based on user prompt actions.
    169 // The user prompt actions are simulated by prefs `network.lna.blocking.prompt.testing.allow`
    170 add_task(async function lna_blocking_tests_localhost_prompt() {
    171  const localHostTestCases = [
    172    // [allowAction, parentIpAddressSpace, urlSuffix, expectedStatus]
    173    [true, Ci.nsILoadInfo.Public, "/test_lna", Cr.NS_OK, H1_URL],
    174    [true, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H1_URL],
    175    [false, Ci.nsILoadInfo.Local, "/test_lna", Cr.NS_OK, H1_URL],
    176    [
    177      false,
    178      Ci.nsILoadInfo.Public,
    179      "/test_lna",
    180      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    181      H1_URL,
    182    ],
    183    [
    184      false,
    185      Ci.nsILoadInfo.Private,
    186      "/test_lna",
    187      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    188      H1_URL,
    189    ],
    190    [true, Ci.nsILoadInfo.Public, "/test_lna", Cr.NS_OK, H2_URL],
    191    [true, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H2_URL],
    192    [true, Ci.nsILoadInfo.Local, "/test_lna", Cr.NS_OK, H2_URL],
    193    [
    194      false,
    195      Ci.nsILoadInfo.Public,
    196      "/test_lna",
    197      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    198      H2_URL,
    199    ],
    200    [
    201      false,
    202      Ci.nsILoadInfo.Private,
    203      "/test_lna",
    204      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    205      H2_URL,
    206    ],
    207    [true, Ci.nsILoadInfo.Local, "/test_lna", Cr.NS_OK, H2_URL],
    208    [
    209      false,
    210      Ci.nsILoadInfo.Public,
    211      "/test_lna",
    212      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    213      H2_URL,
    214    ],
    215    [
    216      false,
    217      Ci.nsILoadInfo.Private,
    218      "/test_lna",
    219      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    220      H2_URL,
    221    ],
    222    [false, Ci.nsILoadInfo.Local, "/test_lna", Cr.NS_OK, H2_URL],
    223    // Test cases for local network access from trackers
    224    // NO LNA then request should not be blocked
    225    [false, Ci.nsILoadInfo.Local, "/test_lna_basic_tracker", Cr.NS_OK, H2_URL],
    226    [false, Ci.nsILoadInfo.Local, "/test_lna_social_tracker", Cr.NS_OK, H2_URL],
    227    [
    228      false,
    229      Ci.nsILoadInfo.Local,
    230      "/test_lna_content_tracker",
    231      Cr.NS_OK,
    232      H2_URL,
    233    ],
    234    [
    235      false,
    236      Ci.nsILoadInfo.Public,
    237      "/test_lna_basic_tracker",
    238      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    239      H2_URL,
    240    ],
    241    [
    242      false,
    243      Ci.nsILoadInfo.Public,
    244      "/test_lna_social_tracker",
    245      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    246      H2_URL,
    247    ],
    248    [
    249      true,
    250      Ci.nsILoadInfo.Public,
    251      "/test_lna_content_tracker",
    252      Cr.NS_OK,
    253      H2_URL,
    254    ],
    255    [
    256      false,
    257      Ci.nsILoadInfo.Private,
    258      "/test_lna_basic_tracker",
    259      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    260      H2_URL,
    261    ],
    262    [
    263      false,
    264      Ci.nsILoadInfo.Private,
    265      "/test_lna_social_tracker",
    266      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    267      H2_URL,
    268    ],
    269    [
    270      false,
    271      Ci.nsILoadInfo.Private,
    272      "/test_lna_content_tracker",
    273      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    274      H2_URL,
    275    ],
    276  ];
    277 
    278  for (let [allow, space, suffix, expectedStatus, url] of localHostTestCases) {
    279    info(`do_test ${url}${suffix}, ${space} -> ${expectedStatus}`);
    280 
    281    Services.prefs.setBoolPref("network.localhost.prompt.testing.allow", allow);
    282 
    283    let chan = makeChannel(url + suffix);
    284    chan.loadInfo.parentIpAddressSpace = space;
    285 
    286    let expectFailure = expectedStatus !== Cr.NS_OK ? CL_EXPECT_FAILURE : 0;
    287 
    288    await new Promise(resolve => {
    289      chan.asyncOpen(new ChannelListener(resolve, null, expectFailure));
    290    });
    291 
    292    Assert.equal(chan.status, expectedStatus);
    293    if (expectedStatus === Cr.NS_OK) {
    294      Assert.equal(chan.protocolVersion, url === H1_URL ? "http/1.1" : "h2");
    295    }
    296  }
    297 });
    298 
    299 add_task(async function lna_blocking_tests_local_network() {
    300  // add override such that target servers is considered as local network (and not localhost)
    301  var override_value =
    302    "127.0.0.1" +
    303    ":" +
    304    httpServer.identity.primaryPort +
    305    "," +
    306    "127.0.0.1" +
    307    ":" +
    308    server.port();
    309 
    310  Services.prefs.setCharPref(
    311    "network.lna.address_space.private.override",
    312    override_value
    313  );
    314 
    315  const localNetworkTestCases = [
    316    // [allowAction, parentIpAddressSpace, urlSuffix, expectedStatus]
    317    [true, Ci.nsILoadInfo.Public, "/test_lna", Cr.NS_OK, H1_URL],
    318    [false, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H1_URL],
    319    [false, Ci.nsILoadInfo.Local, "/test_lna", Cr.NS_OK, H1_URL],
    320    [
    321      false,
    322      Ci.nsILoadInfo.Public,
    323      "/test_lna",
    324      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    325      H1_URL,
    326    ],
    327    [false, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H1_URL],
    328    [true, Ci.nsILoadInfo.Public, "/test_lna", Cr.NS_OK, H2_URL],
    329    [false, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H2_URL],
    330    [false, Ci.nsILoadInfo.Local, "/test_lna", Cr.NS_OK, H2_URL],
    331    [
    332      false,
    333      Ci.nsILoadInfo.Public,
    334      "/test_lna",
    335      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    336      H2_URL,
    337    ],
    338  ];
    339 
    340  for (let [
    341    allow,
    342    space,
    343    suffix,
    344    expectedStatus,
    345    url,
    346  ] of localNetworkTestCases) {
    347    info(`do_test ${url}, ${space} -> ${expectedStatus}`);
    348 
    349    Services.prefs.setBoolPref(
    350      "network.localnetwork.prompt.testing.allow",
    351      allow
    352    );
    353 
    354    let chan = makeChannel(url + suffix);
    355    chan.loadInfo.parentIpAddressSpace = space;
    356 
    357    let expectFailure = expectedStatus !== Cr.NS_OK ? CL_EXPECT_FAILURE : 0;
    358 
    359    await new Promise(resolve => {
    360      chan.asyncOpen(new ChannelListener(resolve, null, expectFailure));
    361    });
    362 
    363    Assert.equal(chan.status, expectedStatus);
    364    if (expectedStatus === Cr.NS_OK) {
    365      Assert.equal(chan.protocolVersion, url === H1_URL ? "http/1.1" : "h2");
    366    }
    367  }
    368  Services.prefs.clearUserPref("network.lna.address_space.private.override");
    369 });
    370 
    371 // Test the network.lna.skip-domains preference
    372 add_task(async function lna_domain_skip_tests() {
    373  // Add DNS overrides to map test domains to 127.0.0.1
    374  override.clearOverrides();
    375  Services.dns.clearCache(true);
    376 
    377  override.addIPOverride("example.com", "127.0.0.1");
    378  override.addIPOverride("test.example.com", "127.0.0.1");
    379  override.addIPOverride("server.local", "127.0.0.1");
    380  override.addIPOverride("api.dev.local", "127.0.0.1");
    381 
    382  // Add override such that target servers are considered as local network (and not localhost)
    383  // This includes all the domains we're testing with
    384  var override_value =
    385    "127.0.0.1" +
    386    ":" +
    387    httpServer.identity.primaryPort +
    388    "," +
    389    "127.0.0.1" +
    390    ":" +
    391    server.port();
    392 
    393  Services.prefs.setCharPref(
    394    "network.lna.address_space.private.override",
    395    override_value
    396  );
    397 
    398  const domainSkipTestCases = [
    399    // [skipDomains, parentSpace, expectedStatus, baseURL, description]
    400    // Exact domain match
    401    [
    402      "localhost",
    403      Ci.nsILoadInfo.Public,
    404      Cr.NS_OK,
    405      H1_URL,
    406      "exact domain match - localhost",
    407    ],
    408    [
    409      "localhost",
    410      Ci.nsILoadInfo.Public,
    411      Cr.NS_OK,
    412      H2_URL,
    413      "exact domain match - localhost H2",
    414    ],
    415    [
    416      "example.com",
    417      Ci.nsILoadInfo.Public,
    418      Cr.NS_OK,
    419      H1_EXAMPLE_URL,
    420      "exact domain match - example.com",
    421    ],
    422 
    423    // Wildcard domain match
    424    [
    425      "*.localhost",
    426      Ci.nsILoadInfo.Public,
    427      Cr.NS_OK,
    428      H1_URL,
    429      "wildcard domain match - *.localhost matches localhost",
    430    ],
    431    [
    432      "*.example.com",
    433      Ci.nsILoadInfo.Public,
    434      Cr.NS_OK,
    435      H1_TEST_EXAMPLE_URL,
    436      "wildcard domain match - *.example.com matches test.example.com",
    437    ],
    438    [
    439      "*.example.com",
    440      Ci.nsILoadInfo.Public,
    441      Cr.NS_OK,
    442      H1_EXAMPLE_URL,
    443      "wildcard domain match - *.example.com matches example.com",
    444    ],
    445    [
    446      "*.test.com",
    447      Ci.nsILoadInfo.Public,
    448      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    449      H1_EXAMPLE_URL,
    450      "wildcard no match - *.test.com doesn't match example.com",
    451    ],
    452 
    453    // Multiple domains (comma-separated)
    454    [
    455      "example.com,localhost,test.org",
    456      Ci.nsILoadInfo.Public,
    457      Cr.NS_OK,
    458      H1_URL,
    459      "multiple domains - localhost match",
    460    ],
    461    [
    462      "example.com,localhost,test.org",
    463      Ci.nsILoadInfo.Public,
    464      Cr.NS_OK,
    465      H1_EXAMPLE_URL,
    466      "multiple domains - example.com match",
    467    ],
    468    [
    469      "foo.com,test.org",
    470      Ci.nsILoadInfo.Public,
    471      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    472      H1_EXAMPLE_URL,
    473      "multiple domains no match - example.com not in list",
    474    ],
    475 
    476    // Empty skip domains (should apply normal LNA rules)
    477    [
    478      "",
    479      Ci.nsILoadInfo.Public,
    480      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    481      H1_URL,
    482      "empty skip domains - should block",
    483    ],
    484 
    485    // .local domain tests
    486    [
    487      "*.local",
    488      Ci.nsILoadInfo.Public,
    489      Cr.NS_OK,
    490      H1_SERVER_LOCAL_URL,
    491      "wildcard .local - *.local matches server.local",
    492    ],
    493    [
    494      "*.local",
    495      Ci.nsILoadInfo.Public,
    496      Cr.NS_OK,
    497      H1_API_DEV_LOCAL_URL,
    498      "wildcard .local - *.local matches api.dev.local",
    499    ],
    500    [
    501      "*.dev.local",
    502      Ci.nsILoadInfo.Public,
    503      Cr.NS_OK,
    504      H1_API_DEV_LOCAL_URL,
    505      "wildcard subdomain .local - *.dev.local matches api.dev.local",
    506    ],
    507    [
    508      "server.local",
    509      Ci.nsILoadInfo.Public,
    510      Cr.NS_OK,
    511      H1_SERVER_LOCAL_URL,
    512      "exact match .local - server.local matches server.local",
    513    ],
    514    [
    515      "*.local",
    516      Ci.nsILoadInfo.Public,
    517      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    518      H1_URL,
    519      "wildcard .local - *.local doesn't match localhost",
    520    ],
    521 
    522    // localhost variations
    523    [
    524      "localhost,*.local,*.internal",
    525      Ci.nsILoadInfo.Public,
    526      Cr.NS_OK,
    527      H1_URL,
    528      "combined patterns - localhost matches localhost",
    529    ],
    530    [
    531      "localhost,*.local,*.internal",
    532      Ci.nsILoadInfo.Public,
    533      Cr.NS_OK,
    534      H1_SERVER_LOCAL_URL,
    535      "combined patterns - *.local matches server.local",
    536    ],
    537 
    538    // Plain "*" wildcard matches all domains
    539    [
    540      "*",
    541      Ci.nsILoadInfo.Public,
    542      Cr.NS_OK,
    543      H1_URL,
    544      "wildcard all - * matches localhost",
    545    ],
    546    [
    547      "*",
    548      Ci.nsILoadInfo.Public,
    549      Cr.NS_OK,
    550      H1_EXAMPLE_URL,
    551      "wildcard all - * matches example.com",
    552    ],
    553    [
    554      "*",
    555      Ci.nsILoadInfo.Public,
    556      Cr.NS_OK,
    557      H1_SERVER_LOCAL_URL,
    558      "wildcard all - * matches server.local",
    559    ],
    560    [
    561      "*",
    562      Ci.nsILoadInfo.Public,
    563      Cr.NS_OK,
    564      H1_TEST_EXAMPLE_URL,
    565      "wildcard all - * matches test.example.com",
    566    ],
    567  ];
    568 
    569  for (let [
    570    skipDomains,
    571    parentSpace,
    572    expectedStatus,
    573    url,
    574    description,
    575  ] of domainSkipTestCases) {
    576    info(`Testing domain skip: ${description} - domains: "${skipDomains}"`);
    577 
    578    // Set the domain skip preference
    579    Services.prefs.setCharPref("network.lna.skip-domains", skipDomains);
    580 
    581    // Disable prompt simulation for clean testing
    582    Services.prefs.setBoolPref("network.localhost.prompt.testing.allow", false);
    583 
    584    let chan = makeChannel(url + "/test_lna");
    585    chan.loadInfo.parentIpAddressSpace = parentSpace;
    586 
    587    let expectFailure = expectedStatus !== Cr.NS_OK ? CL_EXPECT_FAILURE : 0;
    588 
    589    await new Promise(resolve => {
    590      chan.asyncOpen(new ChannelListener(resolve, null, expectFailure));
    591    });
    592 
    593    Assert.equal(
    594      chan.status,
    595      expectedStatus,
    596      `Status should match for: ${description}`
    597    );
    598    if (expectedStatus === Cr.NS_OK) {
    599      Assert.equal(chan.protocolVersion, url === H2_URL ? "h2" : "http/1.1");
    600    }
    601  }
    602 
    603  // Cleanup
    604  Services.prefs.clearUserPref("network.lna.skip-domains");
    605  Services.prefs.clearUserPref("network.lna.address_space.private.override");
    606  override.clearOverrides();
    607  Services.dns.clearCache(true);
    608 });
    609 // Test the new network.lna.local-network-to-localhost.skip-checks preference
    610 add_task(async function lna_local_network_to_localhost_skip_checks() {
    611  // Test cases: [skipPref, parentSpace, urlSuffix, expectedStatus, baseURL]
    612  const skipTestCases = [
    613    // Skip pref disabled (false) - existing behavior should be preserved
    614    [
    615      false,
    616      Ci.nsILoadInfo.Private,
    617      "/test_lna",
    618      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    619      H1_URL,
    620    ],
    621    [
    622      false,
    623      Ci.nsILoadInfo.Private,
    624      "/test_lna",
    625      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    626      H2_URL,
    627    ],
    628    [
    629      false,
    630      Ci.nsILoadInfo.Public,
    631      "/test_lna",
    632      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    633      H1_URL,
    634    ],
    635    [
    636      false,
    637      Ci.nsILoadInfo.Public,
    638      "/test_lna",
    639      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    640      H2_URL,
    641    ],
    642 
    643    // Skip pref enabled (true) - new behavior: Private->Local allowed, Public->Local still blocked
    644    [true, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H1_URL], // Private->Local now allowed
    645    [true, Ci.nsILoadInfo.Private, "/test_lna", Cr.NS_OK, H2_URL], // Private->Local now allowed
    646    [
    647      true,
    648      Ci.nsILoadInfo.Public,
    649      "/test_lna",
    650      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    651      H1_URL,
    652    ], // Public->Local still blocked
    653    [
    654      true,
    655      Ci.nsILoadInfo.Public,
    656      "/test_lna",
    657      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    658      H2_URL,
    659    ], // Public->Local still blocked
    660  ];
    661 
    662  for (let [
    663    skipPref,
    664    parentSpace,
    665    suffix,
    666    expectedStatus,
    667    url,
    668  ] of skipTestCases) {
    669    info(
    670      `Testing skip pref: ${skipPref}, ${parentSpace} -> Local, expect: ${expectedStatus}`
    671    );
    672 
    673    // Set the new skip preference
    674    Services.prefs.setBoolPref(
    675      "network.lna.local-network-to-localhost.skip-checks",
    676      skipPref
    677    );
    678 
    679    // Disable prompt simulation for clean testing (prompt should not affect skip logic)
    680    Services.prefs.setBoolPref("network.localhost.prompt.testing.allow", false);
    681 
    682    let chan = makeChannel(url + suffix);
    683    chan.loadInfo.parentIpAddressSpace = parentSpace;
    684    // Target is always Local (localhost) since we're testing localhost servers
    685 
    686    let expectFailure = expectedStatus !== Cr.NS_OK ? CL_EXPECT_FAILURE : 0;
    687 
    688    await new Promise(resolve => {
    689      chan.asyncOpen(new ChannelListener(resolve, null, expectFailure));
    690    });
    691 
    692    Assert.equal(chan.status, expectedStatus);
    693    if (expectedStatus === Cr.NS_OK) {
    694      Assert.equal(chan.protocolVersion, url === H1_URL ? "http/1.1" : "h2");
    695    }
    696  }
    697 
    698  // Cleanup
    699  Services.prefs.clearUserPref(
    700    "network.lna.local-network-to-localhost.skip-checks"
    701  );
    702 });
    703 
    704 // Test that same-origin requests skip LNA checks
    705 add_task(async function lna_same_origin_skip_checks() {
    706  // Ensure the local-network-to-localhost skip pref is disabled for this test
    707  Services.prefs.setBoolPref(
    708    "network.lna.local-network-to-localhost.skip-checks",
    709    false
    710  );
    711 
    712  // Test cases: [triggeringOriginURI, targetURL, parentSpace, expectedStatus, description]
    713  const sameOriginTestCases = [
    714    // Same origin cases - should skip LNA checks and allow the request
    715    [
    716      H1_URL,
    717      H1_URL + "/test_lna",
    718      Ci.nsILoadInfo.Public,
    719      Cr.NS_OK,
    720      "same origin localhost to localhost from Public should be allowed",
    721    ],
    722    [
    723      H1_URL,
    724      H1_URL + "/test_lna",
    725      Ci.nsILoadInfo.Private,
    726      Cr.NS_OK,
    727      "same origin localhost to localhost from Private should be allowed",
    728    ],
    729    [
    730      H2_URL,
    731      H2_URL + "/test_lna",
    732      Ci.nsILoadInfo.Public,
    733      Cr.NS_OK,
    734      "same origin localhost to localhost (H2) from Public should be allowed",
    735    ],
    736    [
    737      H2_URL,
    738      H2_URL + "/test_lna",
    739      Ci.nsILoadInfo.Private,
    740      Cr.NS_OK,
    741      "same origin localhost to localhost (H2) from Private should be allowed",
    742    ],
    743 
    744    // Cross-origin cases - should apply normal LNA checks and block
    745    // Use null to get the default cross-origin principal (public.example.com)
    746    [
    747      null,
    748      H1_URL + "/test_lna",
    749      Ci.nsILoadInfo.Public,
    750      Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    751      "cross origin to localhost from Public should be blocked",
    752    ],
    753    // Note: Private->Local transition test removed temporarily
    754    // as there may be other logic affecting this transition
    755 
    756    // Same origin but from local address space should still be allowed
    757    [
    758      H1_URL,
    759      H1_URL + "/test_lna",
    760      Ci.nsILoadInfo.Local,
    761      Cr.NS_OK,
    762      "same origin localhost to localhost from Local should be allowed",
    763    ],
    764  ];
    765 
    766  for (let [
    767    triggeringOriginURI,
    768    targetURL,
    769    parentSpace,
    770    expectedStatus,
    771    description,
    772  ] of sameOriginTestCases) {
    773    info(`Testing same origin check: ${description}`);
    774 
    775    // Disable prompt simulation for clean testing
    776    Services.prefs.setBoolPref("network.localhost.prompt.testing.allow", false);
    777 
    778    // Use makeChannel with explicit triggering principal
    779    let chan = makeChannel(targetURL, triggeringOriginURI);
    780    chan.loadInfo.parentIpAddressSpace = parentSpace;
    781 
    782    let expectFailure = expectedStatus !== Cr.NS_OK ? CL_EXPECT_FAILURE : 0;
    783 
    784    await new Promise(resolve => {
    785      chan.asyncOpen(new ChannelListener(resolve, null, expectFailure));
    786    });
    787 
    788    Assert.equal(
    789      chan.status,
    790      expectedStatus,
    791      `Status should match for: ${description}`
    792    );
    793    if (expectedStatus === Cr.NS_OK) {
    794      Assert.equal(
    795        chan.protocolVersion,
    796        targetURL.startsWith(H2_URL) ? "h2" : "http/1.1"
    797      );
    798    }
    799  }
    800 });