The same list, read the other way
The catalog that grants the microphone also builds a fence. On the Mac, the same code that installs trust (lesson 4) hands the extension host a set of family hosts: the hub's and Pippa's from Settings, and the host of every web target in the catalog. To those the host adds three patterns that are family on every Mac: *://localhost/*, *://127.0.0.1/* and *://*.ts.net/*. Then every installed extension is explicitly denied all of them, whatever it asked for. The first block is the mechanism.
Three details make it work. A denied pattern beats a granted one: measured on 2026-10-04 against a package that asked for <all_urls>, WebKit's hasAccess says no for a denied host, so the extension keeps everything it was granted elsewhere. The host remembers exactly which family patterns it denied each extension, so a change clears those and none of the extension's own. And Settings can let one extension in by its id, after which the family's sites open to it until Dad takes that back. The test installs a package granted every site, checks that it reaches example.com but not loopback, a ts.net host or a catalog host, adds a host the catalog names later, lets the extension in, and takes it out again.
The reason was a measured failure. With 1Password's extension running on cwkPippa, its content script took over navigator.credentials, and a Touch ID passkey could not be saved while its desktop pairing was open (876ea6a, 2026-10-04). Track 8 tells the rest of the 1Password story, and Pippa Embed Quest teaches the Chrome extension model itself, hands-on.
Wide to deny, narrow to grant
Read side by side, the two uses of the family list err in opposite directions.
| Microphone and camera grant | Extension fence | |
|---|---|---|
| Unit | An exact origin: scheme, host, port | A host pattern: http or https (WebKit's * scheme matches only those two), any port |
| Tailnet | Only origins the catalog or Settings list | Every *.ts.net host |
| Loopback | Any loopback spelling | localhost and 127.0.0.1 |
| Cost of being too wide | A page nobody vouched for hears Dad | An extension misses a family site |
The fence can name every *.ts.net host without asking tailscaled, because a name pattern is enough to keep something out. It is not enough to let something in, which is why the grant waits for an exact origin. Each list errs in the direction that is cheap for what it guards.
By name, today
The architecture lists the address field as taking "a URL, a search, a family app by name, or a question for Pippa." Two of those four are built. AddressResolver opens with a comment, "Phase 2 puts a family app's name in front of both (the launch catalog)", and its resolve(_:) knows full schemes, text that reads as a host, and the search template. Nothing in the address field, its suggestions or the toolbar consults the catalog. A bare word is a search, so typing a family app's name there today searches the web for that word. A question for Pippa is not built there either. The plan keeps both open, and Dad ruled that they are taught as not yet built.
Today "by name" means two surfaces:
- On the Mac, the native Home's "Find a family app" field. It filters the catalog grid as Dad types, matching each target's title and label together, ignoring case.
- On iPhone and iPad, a Home Screen widget that shows the family apps by name.
A widget that holds no address
The phone app keeps a shelf for the widget: each web target's catalog id, title and favourite flag, plus the catalog revision and when it was read. Never an address. The shelf lives in the app's team-prefixed keychain group, which the widget shares, through the kit's family document store. An App Group is the usual tool for this, but a new App Group cannot be registered by the family's key-based TestFlight pipeline, and a keychain group is already in every profile.
A tile is a link, cwkvoyager://family/<target id> (second block). The widget never dials anything. Its timeline policy is .never, and the app reloads it only when the shelf's apps change. The small, medium and large sizes show 4, 8 and 16 tiles, favourites first.
When a tile is tapped, the app resolves the id against the catalog it already holds (third block). If the id is not listed, or no catalog has been read yet, it reads the catalog first. Then it opens the address that catalog lists, bringing an existing tab of the same origin to the front instead of opening a second one, or says plainly that Firelink no longer lists that app. So when the family moves an app to another Mac, Firelink's pointer changes and nothing on the Home Screen does. The tap follows the move once the app has read the catalog again, which it does when it comes to the front, at most once a minute. A tap that wins that race, or comes while the hub cannot be reached, still opens the address the old catalog listed: a listed id is never held back to wait for a fresh read.
That scheme is the only one the phone app declares. Voyager is not the default browser on iOS (Dad, 2026-10-26), so it claims no http or https. One testing gotcha: simctl openurl always stops at an "Open in Voyager?" confirmation that a widget tap on a phone never shows, so the link path is held by the app's hosted suite, where seven tests cover the link and the shelf.