Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ This is the important part — a web page that can kill processes needs guardrai
actions. That is deliberate (owner-only tailnet + token-gated actions), but do
not read "loopback-only" as "not network-reachable" — a proxy in front is not
a trust boundary.
- **Fronting it with a proxy needs `AUM_TRUSTED_HOSTS`.** The DNS-rebinding guard
refuses any request whose `Host` is a non-local DNS name — which is exactly what
a proxy forwards. Opt that one name in explicitly:

```bash
AUM_TRUSTED_HOSTS=box.your-tailnet.ts.net agent-usage-manager
```

Comma-separated for several. Only add a name nobody else can mint a cert for on
this node; a name you don't control reopens the hole the guard exists to close.
Empty by default — out of the box only `localhost`, `127.0.0.1` and `::1` are
accepted.

## Limits & known issues

Expand Down
17 changes: 12 additions & 5 deletions agent_usage_manager/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,11 +691,18 @@ async def _lifespan(_: FastAPI):
app = FastAPI(title="agent-usage-manager", lifespan=_lifespan)


# Loopback names are always local. The ts.net FQDN is the tailnet front door:
# `tailscale serve --https=8448` proxies to this app and preserves the Host
# header. Only tailscaled can mint that name/cert for this node, so allowing
# it does not reopen the DNS-rebinding hole _host_allowed exists to close.
_LOCAL_HOSTS = {"localhost", "127.0.0.1", "::1", "minglongs-mac-mini.tailab5be0.ts.net"}
# Loopback names are always local. Anything else must be opted in per install via
# AUM_TRUSTED_HOSTS (comma-separated hostnames) — typically the FQDN of a reverse
# proxy or tailnet front door that preserves the Host header, e.g.
# `tailscale serve --https=8448` fronting this app. Only add a name that nobody
# else can mint a cert for on this node; any other name reopens the DNS-rebinding
# hole _host_allowed exists to close.
def _configured_trusted_hosts() -> set[str]:
raw = os.environ.get("AUM_TRUSTED_HOSTS", "")
return {h.strip().lower() for h in raw.split(",") if h.strip()}


_LOCAL_HOSTS = {"localhost", "127.0.0.1", "::1"} | _configured_trusted_hosts()


def _hostname_of(hostport: str) -> str:
Expand Down
Loading