The policy in force is the one on main
passThe live tailnet policy is read back and compared byte for byte with the render of main. The console stores the file verbatim, so any difference is real.
commit 130ee6c · push
A Zero Trust access model on a real home network, built in the open. Every status below was read from GitHub Actions when this page was built, and links to the run that produced it. None of them is typed by hand. A check older than its schedule turns amber on its own.
Built · repository · status and plan · decision log
2 of ~40
devices in the house are reachable from the network overlay — each by one grant, on one port (measured live: 2 routes in effect, the widest /32). Everything else is refused by default.
The policy in force equals main. 42 policy
assertions — 34 of them refusals — run against the live network. CI holds no stored key for
the network or the cloud.
Measured once, not counted: after a 15-minute link loss the telemetry path came back in 11 s with no hands; the readings sent meanwhile were lost, not queued.
Field units: deferred — the house's sensors cannot run a client, and the cloud host that would run simulated ones serves two projects. Device-management posture and multi-user sign-in: a paid tier ($8/user/mo) and one user. Just-in-time access and log streaming: $18/user/mo. The before-and-after exposure reading: missed, and stated, not reconstructed.
Posture on this plan is reported by the client itself: it shows how a device is configured, not that it is intact. The break-glass path into production — the cloud provider's console — exists but has not yet been exercised (D-042).
This panel reads GitHub's public Actions API from your browser: what is running this minute, step by step, and the last runs. Everything else on the page moves forward with it.
Where each part lives, which tool manages it, and who may reach what. Contours and words are drawn by hand; every access arrow is parsed from the policy file, so the picture cannot show a path the policy does not grant. Counts on the tiles are live, counted on 2026-09-11T02:45:28Z.
Green arrows are grants parsed from the policy, with the port each names; dashed green goes to a role with no host yet. Red arrows are refusals asserted by the policy tests. Dotted green is data moving inside the house, where the tailnet has no path. Everything not drawn is refused by default.
| Part | Managed by · cost | What it is for | Why |
|---|---|---|---|
| GitHub Actions | workflows | Runs every check. Holds no network or cloud key: each job gets a short-lived token by OIDC. | D-041 |
| Tailscale | policy as code | Evaluates the policy and refuses a save whose tests fail. The policy is not in Terraform: two tools writing one global file fight. | D-001 |
| CI plan role | terraform | Lets CI run terraform plan and nothing more. Its trust names this repository by immutable id; wildcards are refused three ways. | D-028 |
| Archive bucket | terraform | Where the collector's data is meant to land. It exists so the certificate credential has something real to authorise. | D-039 |
| Roles Anywhere | terraform | Written, switched off: no certificate authority exists yet. Cloud access for a machine that holds a certificate, not a key. | Phase 5 |
| Terraform state | terraform | State joins the account's existing bucket rather than creating one; locking is the native lockfile. | D-029 |
| Operator laptop | by hand | The physical console. Applies the policy by hand after CI has validated it. Its posture attributes are reported by the client on the device itself: they establish configuration, not integrity. | D-040 |
| Operator phones | by hand | Posture subjects, and the way back in if everything else fails — verified off-network. | D-016 |
| Home server | by hand | Runs the camera recorder and the house's duty timers, and is someone's daily machine, so it stays user-owned with no machine identity. Members reach their own devices on SSH only -- the house's recovery path, restored after the lab's policy had cut it. | D-051 |
| Media appliance | tag | A device on the network that should reach nothing — and is tested to reach nothing. | D-019 |
| Automation hub | tag | The only door into the home: it advertises one /32 route per exposed device, never the subnet. | D-024 |
| Collector | tag | Telemetry sink: a container on the cloud dev host, outside the house. The hub pushes readings to it on one port; it has no way in, and the tests assert that. | D-049 |
| Mobile units | design pending | A unit that reports AND takes commands. The candidate is the house's robot vacuum, which cannot run a client: it would need an adapter with a path into the hub, which is a widening to decide, not a default. | Phase 6 |
| Sensors | deferred | Push-only field units. The house's own sensors cannot run a client, so their readings arrive through the hub. Simulated ones would run on the cloud dev host, which serves two projects on 2 GB and no swap: deferred. A real one is a single-board computer and a sensor, about $30. | Phase 6 |
| Production VM | by hand | The production stand-in. A destination, never a source. Outside Terraform; also where a person runs terraform apply. | D-036 |
| Smart plug | one /32 | The action tier: operators may switch it, only from a device whose client reports the required posture. On this plan the report comes from the device itself, so a compromised node is not constrained by it — which is what the device-management tile below would change. | D-040 |
| Smart plug | one /32 | The same model on the same port, never granted. Proves least privilege is per host, not per protocol. | D-010 |
| Cat camera | no route | The one camera the owner released for the lab. Nothing on the tailnet can reach it: the recorder in the house watches it, and at the end of each visit the hub pushes the event -- id, times, scores, no image -- to the collector. Named in the recorder, never addressed. | D-049 |
| Robot vacuum | no route · not built | Run by the hub through its vendor's cloud, not the home network. The candidate for the mobile-unit role; it cannot run a client, so it would need an adapter with a path into the hub -- a widening to decide, not built. | Phase 6 |
| Identity provider | Standard $8/user/mo | One login for many people; their groups become policy sources instead of every member. Plugs into the control plane and cloud sign-in. | multi-user |
| MDM / EDR | Standard $8/user/mo | Posture from the fleet's management system instead of the client's own report. An integration a paid plan adds. | D-040 |
| Just-in-time | Premium $18/user/mo | Standing access to the action tier replaced by grants that expire. Needs a paid plan: one time-boxed month. | Phase 4 |
| Log streaming | Premium $18/user/mo | Who connected to what, kept and searchable. The drills in Phase 6 would read it. | Phase 6 |
| Identity Center | AWS: no charge | Cloud sign-in for people through the identity provider. Lives in the organisation's management account, not this stack. | D-030 |
| Private CA | free, or $50+/mo | The missing half of Roles Anywhere: machines get certificates, never keys. Switches the cloud tile above on. | Phase 5 |
| The rest of the house | no route | About 40 devices by type, counted by hand on 2026-09-10 from the hub's registry. None is reachable from the tailnet; the hub talks to them locally. The camera count is withheld, and categories that would let it be subtracted are folded together. | D-008 |
The live tailnet policy is read back and compared byte for byte with the render of main. The console stores the file verbatim, so any difference is real.
commit 130ee6c · push
The tailnet itself parses the policy and runs its tests — including every “this must be refused” assertion — against the real devices. Read-only; no credential is stored anywhere.
commit 130ee6c · push
A pull request that deliberately asserted a forbidden path. The pipeline refused it. A check that has only ever been seen passing proves nothing.
PR #8: failure, closed without merging · commit aebaa0b · pull_request
A read-only plan against the cloud account, authenticated by federation — no access keys exist. CI can plan; only a person can apply.
terraform plan: no changes · commit 75f5a00 · workflow_dispatch
Every file and every commit message is swept for real-world values before merge, including a private list of this network's own names.
commit 74d3573 · push
The whole git history is scanned for credentials on every change.
commit 74d3573 · push
GitHub enforces the checks above on main for everyone, the owner included.
required: disclosure sweep, secret scanner, policy template lint, terraform guards · applies to: everyone · commit 74d3573 · read from GitHub at build
Not a check that passes or fails: numbers observed while something was deliberately broken, counted from both ends, and written down once. They change only when the drill is run again.
Not tested: the hub's own link, and a node restart · runbook, with the timeline
Counted from the repository's files when the page was built. The live checks above are what show these are the rules actually in force.
policy template · decision log
What this lab intends and does not have, why, and what each would take. An item leaves this list when it is built; nothing here is drawn on the diagram as if it existed. The full plan is in STATUS.md.
| What | State | Why not yet | What it takes | Ref |
|---|---|---|---|---|
| Sensors as tailnet nodes | deferred | The house's sensors cannot run a client. Simulated ones would share a cloud host that serves two projects on 2 GB with no swap. | a single-board computer and a sensor, about $30 | Phase 6 |
| A mobile unit that takes commands | design pending | The candidate, the house's robot vacuum, cannot run a client; an adapter would need a path into the hub. That is a widening to decide, not a default. | a design, then the owner's decision | Phase 6 |
| The kiosk tablet in a role of its own | owner decision | It is a member today, so formally it may do everything an operator may. A role would leave it the dashboard and nothing else. | one tag and one grant; the owner's yes | Phase 2 |
| Lost comms on the household side | planned | Measured so far only where the lab could break its own link. The hub's link and the control plane are the house's. | an agreed window with the household | Phase 6 |
| A button that starts a check from this page | not built | A public button needs an endpoint that holds a credential to start a workflow. This page holds none, on purpose. | a small cloud function with a capped, audited dispatch | D-044 |
| Two stale route advertisements | owner decision | Two user devices advertise a whole /24 that was never approved, so nothing is routed. Harmless, and a finding. | the owners switch the advertisement off | D-047 |
| Just-in-time access to the action tier | needs a paid plan | Standing access to the socket would become a grant that expires. | one time-boxed month, $18/user | Phase 4 |