Skip to content

feat: remote access via Tailscale using [server].trusted_origins#60

Open
hkrathore wants to merge 1 commit into
bcurts:mainfrom
hkrathore:feat/tailscale-remote-access
Open

feat: remote access via Tailscale using [server].trusted_origins#60
hkrathore wants to merge 1 commit into
bcurts:mainfrom
hkrathore:feat/tailscale-remote-access

Conversation

@hkrathore
Copy link
Copy Markdown

Summary

Adds a [server].trusted_origins config (settable in config.local.toml) that lets users allow additional HTTPS origins past the same-origin check without binding the server to 0.0.0.0. This enables remote access from a phone or another device via Tailscale while keeping the project's localhost-only security model intact.

Motivation

I wanted to reach agentchattr from my phone while it keeps running on my Mac mini / Oracle VPS. --allow-network exposes the server over unencrypted HTTP on the LAN, which the README already warns against. Tailscale provides a private HTTPS entrypoint that's a much better fit, but the existing origin check rejects anything but http://127.0.0.1:PORT / http://localhost:PORT.

What changed

  • config.toml — document the new [server].trusted_origins key (default [], so no behavior change for existing users)
  • config.local.toml.example — show how to set it to a tailnet hostname
  • config_loader.py — merge [server].trusted_origins from config.local.toml into the effective config, dedupe while preserving order
  • app.py — extract _build_allowed_origins(cfg) which combines the existing localhost defaults with trusted_origins; SecurityMiddleware uses the merged set, logic otherwise unchanged
  • README.md — full Tailscale remote-access guide under the existing "Network mode" section: install, config, tailscale serve, security notes

Design notes

  • Host still binds 127.0.0.1. The browser reaches the app via Tailscale Serve's reverse proxy; the app sees the Origin header from the tailnet hostname and allows it because it's in the whitelist.
  • Only exact HTTPS origin strings are matched. No wildcards, no scheme inference.
  • Empty trusted_origins = no change from current behavior.

Test plan

  • All existing tests pass: pytest tests/ → 42 passed
  • Unit test: trusted_origins in config.local.toml flows through load_config_build_allowed_origins, with dedup
  • End-to-end against a running instance:
    • Trusted tailnet Origin → passes origin check, proceeds to token check
    • Attacker Origin (evil.example.com) → 403 {"error":"forbidden: origin not allowed"}
    • No Origin header → passes origin check (unchanged behavior)
  • Docs render: Tailscale section fits under existing network/security section

Adds a config.local.toml setting that lets users allow additional HTTPS
origins (e.g. their Tailscale HTTPS hostname) past the same-origin check
without binding the server to 0.0.0.0.

- config.toml: document new [server].trusted_origins key (default empty)
- config.local.toml.example: show how to set it for a tailnet hostname
- config_loader: merge [server].trusted_origins from config.local.toml
  into the effective config, deduplicating while preserving order
- app: _build_allowed_origins() combines the localhost defaults with
  trusted_origins; SecurityMiddleware uses the merged set unchanged
- README: full Tailscale remote-access guide — install, config, Serve

This keeps the local-only model (host still binds 127.0.0.1) and only
whitelists exact HTTPS origins. No behavior change when trusted_origins
is empty. All existing tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant