v147 · Security · Navigate Demo

Navigate Demo

Illustrates the attack vector that Chrome 147's LNA restriction closes, and the exact URL categories where WindowClient.navigate() is now blocked from a public service worker.

WindowClient.navigate() is only callable from inside a service worker. The restriction applies when the service worker was registered from a public origin and the target URL is a private network or loopback address.

safe navigate decision tester

Enter the service worker origin and target URL. The tester performs the same network-tier decision without navigating this tab away.

blocked attack scenario

  1. 1 User visits https://compromised.example.com — registers a service worker
  2. 2 Service worker listens for a trigger (push notification, timer, fetch event)
  3. 3 Service worker calls client.navigate('http://192.168.1.1/admin') for any open window it controls
  4. Chrome 147 blocks this. The navigate() promise rejects — the user's window is not redirected to the local network URL.

navigate() URL rules

SW origin navigate() target Chrome 147
https://public.example.com/sw.js http://192.168.1.1/admin Blocked — private IP
https://public.example.com/sw.js http://localhost:8080 Blocked — loopback
https://public.example.com/sw.js http://10.0.0.1 Blocked — private IP
https://public.example.com/sw.js https://public.example.com/page Allowed — same origin, public
https://public.example.com/sw.js https://other.public.com/ Allowed — both public
http://192.168.1.50/sw.js http://192.168.1.100/ Allowed — private to private

code

// service-worker.js
self.addEventListener('message', async event => {
  if (event.data.type !== 'navigate') return;

  const clients = await self.clients.matchAll({ type: 'window' });
  for (const client of clients) {
    try {
      // Chrome 147: throws if SW is public and target is private
      const navigated = await client.navigate(event.data.url);
      if (navigated === null) {
        // null = URL was same-origin but out of scope
        console.log('Navigate returned null: out of scope');
      }
    } catch (err) {
      // LNA block or invalid URL
      console.error('navigate() failed:', err.message);
    }
  }
});

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗