문서 위의 세 구역
불변식 5는 Voyager 아키텍처에서 제일 야심 찬 문장이야. 신뢰는 구역이고, 구역은 검증된다. 구역은 셋이야. 이 Mac과 집 네트워크의 가족 출처, 아빠의 테일넷, 바깥세상. 테일넷 출처는 로컬 tailscaled가 피어라고 불러 주는 호스트야. 집 출처는 카탈로그가 가족 Mac에 대해 적어 둔 정확한 출처고, "LAN의 아무 주소"는 절대 아니야. 손님도 같은 Wi-Fi를 쓰니까. 나머지는 바깥세상이고, 검증할 수 없는 것도 바깥세상이야.
불변식 6은 구역으로 뭘 얻는지 말해. 가족 구역은 마이크와 카메라를 묻지 않고 써. 그 밖의 허가는 이유를 붙여 이름으로 하나씩 더하고, 바깥세상은 Safari처럼 물어봐. 없애려는 불편은 트랙 1이 처음에 꺼낸 그거야. 아빠 앱에서 아빠 마이크인데 뜨는 권한 창.
판정은 함수 하나
첫 번째 블록이 정책의 전부야. TrustPolicy는 출처(스킴, 호스트, 포트)를 구역으로 바꾸는 함수를 감싸고, 그 함수는 확인할 수 있는 쪽이 넣어 줘. 아무것도 안 넣으면 모든 출처가 바깥세상이야. decision(for:)은 출처 두 개를 봐. 묻는 프레임, 그리고 그 프레임이 들어 있는 페이지. 둘 다 바깥세상이 아닐 때만 허가해. 바깥 페이지 안의 가족 프레임은 물어봐. 가족 페이지 안의 바깥 프레임도 물어봐. 남한테 끼워 넣어진다고, 혹은 남을 끼워 넣는다고 콘텐츠가 자기 구역을 올릴 순 없어.
일부러 빠진 게 둘 있어. 정책은 절대 .deny라고 답하지 않아. 바깥세상엔 WebKit의 자체 권한 창이 뜨고 아빠가 정해. 아빠 대신 거절하는 건 없고, nothingIsRefusedOnDadsBehalf 테스트가 종류마다 확인해. 그리고 허가할 수 있는 목록은 닫혀 있어. PermissionKind엔 마이크, 카메라, 둘 다만 있고, 새 종류는 거기에 이유와 함께 이름으로만 들어가. 그 enum과, WebKit의 캡처 질문을 이 요청으로 바꾸는 엔진 자식은 트랙 3에서 봤어. 신뢰는 조용히 하나를 더 정해. 트랙 4의 잠재우기 정책은 바깥세상 탭만 재워.
Voyager가 설치하는 검증기
두 번째 블록이 코드가 만드는 유일한 검증기야. TrustPolicy.family(origins:)는 둘을 집으로 쳐. 어떤 loopback 표기로 부르든 이 Mac(localhost, ::1, 127.로 시작하는 주소 전부), 그리고 넘겨받은 정확한 출처들. 나머지는 바깥세상이야. 기본 포트는 먼저 맞춰 둬. WebKit은 443의 HTTPS를 포트 없이 부르고, 카탈로그는 포트를 적을 수도 있거든.
세 번째 블록이 그 출처들을 넘겨. Settings가 가리키는 허브와 피파, 그리고 지금 카탈로그 투영의 웹 대상 전부. 셸은 Settings나 카탈로그가 바뀔 때마다 이걸 불러. Mac에선 확장을 막는 바로 그 자리에서(다음 레슨), 폰에선 폰의 가족 훅에서. 그러니 신뢰는 카탈로그를 따라가. 오늘 카탈로그에 있는 앱은 오늘 집이고, 카탈로그에서 빠진 앱은 투영이 바뀌는 순간 바깥세상이야.
정확하다는 건 정말 정확하다는 뜻이야. 가족 신뢰 테스트는 이웃이 가족이 아닌지 확인해. 같은 테일넷 호스트의 다른 포트는 바깥이고, 같은 호스트의 평문 HTTP도 바깥이야. 적힌 출처 하나는 자기만 보증하고, 그 옆은 하나도 보증 안 해.
검증기 없이 보낸 19일
구역 타입, 판정, 그 테스트들은 2026-10-03 첫 벽돌부터 있었어. 없던 건 호출하는 쪽이었어. BrowserServices는 검증기 없는 기본값 TrustPolicy()를 그대로 들고 있었어. 그러니 모든 출처가 바깥세상이었고, 가족 앱마다 낯선 사이트처럼 WebKit 권한 창을 받았지. 그동안 신뢰 테스트는 전부 통과했어. 테스트마다 정책을 손으로 만들었으니까. 2026-10-22의 수정(3638f97, todo #603)은 두 번째 블록의 검증기를 더했어. 그리고 그만큼 중요한 것, 정책을 설치하는 코드도 더했어. 세 번째 블록과 셸의 호출이야.
타입으로만 있는 구역
이제 빈 곳을 정직하게 보자. TrustZone은 .tailnet을 선언하고, 주석엔 로컬 tailscaled가 피어라고 부르는 호스트라고 적혀 있어. 아키텍처도 집 간판도 README도 Voyager가 아빠 테일넷을 묻지 않고 믿는다고 써. 코드는 tailscaled한테 아무것도 안 물어. family(origins:)는 .home이나 .outsideWorld만 돌려줘. 선언을 빼면 앱 소스에서 .tailnet이 나오는 곳은 Site Information의 문구와 아이콘뿐이야. 어떤 검증기도 만들어 내지 않는 구역의 문구지. (테스트 하나가 .tailnet을 답하는 정책을 손으로 만들어서, 판정이 그걸 허가하는지만 확인해.)
그러니까 지금은 이래. 테일넷의 가족 앱은 Firelink 카탈로그나 Settings가 그 정확한 출처를 적어 줘서 신뢰받아. 카탈로그에 없는 테일넷 호스트는 웹의 아무 사이트랑 똑같이 물어봐. 테일넷 구역은 아직 안 만들어졌어. 검증할 수 없으면 이미 바깥세상이니까, 이 빈틈은 묻는 쪽으로 실패해. 안전한 방향이야. 빠진 것 때문에 아빠는 권한 창을 하나 더 볼 뿐이고, 원하지 않은 허가가 나가진 않아.
같은 패널에 작은 어긋남이 하나 더 있어. Site Information은 집 구역을 Family app on this Mac(이 Mac의 가족 앱)이라고 설명하는데, 다른 Mac이 서비스하는 카탈로그 앱도 집이야. 그 문구는 검증기가 생기기 19일 전, 첫날에 쓰였거든.
먼저 보안 컨텍스트
허가는 페이지가 물어볼 수 있는 곳에서만 의미가 있어. WebKit은 보안 컨텍스트에만 캡처를 내주고, 평문 HTTP를 봐 달라고 할 방법도 없어. 엔진 게이트가 그걸 쟀어(W6). Tailscale HTTPS 이름의 가족 앱은 물을 수 있어. 그런데 같은 앱을 테일넷 이름에 평문 HTTP로 열면 navigator.mediaDevices가 아예 없어. 평문 HTTP loopback은 보안 컨텍스트로 쳐. 그래서 가족 앱은 HTTPS 트윈이나 loopback으로 열어야 하고, Firelink 카탈로그도 tailnet에 대답할 땐 HTTPS 트윈을 먼저 골라. hardened runtime에선 앱이 entitlement로 마이크와 카메라를 따로 신청해야 하고, Voyager에 대한 macOS 자체 권한도 그대로 적용돼. 구역은 Voyager와 페이지 사이의 문제를 정리할 뿐이야. 시스템을 넘어서진 않아.