Skip to content

Add a --host flag so serve can bind an interface other than loopback - #345

Draft
mando wants to merge 2 commits into
mainfrom
mando/344-add-a-host-flag-so-serve-can-bind-an-interface-oth
Draft

Add a --host flag so serve can bind an interface other than loopback#345
mando wants to merge 2 commits into
mainfrom
mando/344-add-a-host-flag-so-serve-can-bind-an-interface-oth

Conversation

@mando

@mando mando commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #344.

serve was hardcoded to 127.0.0.1, so running Argus on a home server, NAS, or in a container meant an SSH tunnel or a separate proxy to reach the dashboard from a laptop. Only the port was configurable.

What this adds

  • A host setting resolved through the standard chain: managed > --host > ARGUS_HOST > argus.json > default, plumbed through both serve and run (one shared hostArg so the two can't drift).
  • The default stays 127.0.0.1. Nothing changes for anyone who doesn't pass the flag.
  • No ui entry. Like readOnly, this is a deployment switch, and it would be the one setting whose remote edit widens the editor's own exposure.
  • A value that isn't a bare address (a URL, host:port, whitespace, a path) warns and resolves back to loopback, so a typo narrows exposure rather than widening it. A hostname that merely doesn't resolve still gets through and surfaces as a bind failure naming the address that was asked for.
  • Startup says what's exposed: the bound address and port, that nothing on the port asks for a sign-in, and --read-only as the pairing (only when it's off).

The security decisions

Both loopback-dependent guards keep their current behavior, which is the choice the issue asked to settle:

  • rejectUnsafeHost stays strict. Widening the allowlist to the configured host would reopen the DNS-rebinding hole for exactly that hostname. So a browser on another machine reads the dashboard and can label and hide sessions, but cannot write settings, run the connection test, touch stored API keys, or reach /mcp. The startup log says this before the user hits a refusal, and there's a test asserting each of those routes 403s for a LAN Host.
  • rejectCrossSite is unaffected, since it keys off Sec-Fetch-Site plus x-argus-app, both properties of the request rather than the bind address. Now covered by a test from a LAN origin (same-origin passes, cross-site still 403s).
  • The desktop app stays loopback-only. Its sidecar gets --host 127.0.0.1 explicitly, so a widened argus.json/ARGUS_HOST can't expose the tray app's backend port. The front-door proxy was and stays loopback-only. (A managed host still wins, by design.)

Drive-by

argus config set no longer writes a value the setting rejected: parse() returning undefined meant the key was silently dropped and reported as = undefined. It now fails with the invalid value named. Only affects present-but-invalid values; clearing with an empty string still works.

Docs

A new "Opening Argus to your network" section in the CLI reference (what it exposes, the --read-only pairing, what stays local), the setting in the settings reference, a privacy section, and the guard reasoning in the internals configuration and agent-access pages. The local-only positioning is unchanged: parsing is still local and sync is still the only thing that uploads.

Verification

  • bun run typecheck, bun test (817 pass), bun run docs:build, and cargo check on the desktop shell.
  • Bound 0.0.0.0 against an empty temp store: reachable from the LAN address, the settings write refused, log lines as described.

Known rough edge, deliberately left: a remote browser's Settings screen still renders its controls and will surface a 403 when used. Teaching the SPA to hide them would mean reporting per-request loopback status through /healthz, which felt like a separate change.

mando and others added 2 commits August 19, 2026 17:27
The listener was hardcoded to 127.0.0.1, so running Argus on a home server,
NAS, or inside a container meant an SSH tunnel or a separate proxy to reach the
dashboard from another machine. Only the port was configurable.

Add a `host` setting resolved through the standard chain (managed > --host >
ARGUS_HOST > argus.json > default), plumbed through both `serve` and `run`. The
default stays loopback, so nothing changes for anyone who doesn't pass it. A
value that isn't a bare address (a URL, a host:port pair, whitespace, a path)
warns and resolves back to loopback, so a typo narrows exposure instead of
widening it. No `ui` entry: like `readOnly` this is a deployment switch, and it
would be the one setting whose remote edit widens the editor's own exposure.

Nothing on the port authenticates, so a non-loopback bind says so at startup,
naming the address and port, and suggests --read-only when it's off.

Both loopback-dependent guards keep their current behavior, which is the part
worth stating explicitly:

- rejectUnsafeHost stays loopback-only. Adding the configured host to the
  allowlist would reopen the DNS-rebinding hole for exactly that hostname. So a
  remote browser reads the dashboard, labels and hides sessions, and cannot
  write settings, run the connection test, touch stored API keys, or reach /mcp.
  The startup log says this before the user hits a refusal.
- rejectCrossSite keys off Sec-Fetch-Site plus the x-argus-app header, both
  properties of the request rather than the bind address, so CSRF defense works
  unchanged from a LAN origin. Now covered by a test.

The desktop app passes --host 127.0.0.1 to its sidecar so a widened argus.json
or ARGUS_HOST can't expose the tray app's backend port; its front-door proxy was
and stays loopback-only.

Also stop `argus config set` from writing a rejected value: parse() returning
undefined meant the key was silently dropped and reported as `= undefined`.

Docs: a new "Opening Argus to your network" section in the CLI reference (what
it exposes, the --read-only pairing, what stays local), the setting in the
settings reference, a privacy section, and the guard reasoning in the internals
configuration and agent-access pages.

Verified by binding 0.0.0.0 against an empty temp store: reachable from the LAN
address, settings write refused, log lines as described.

Closes #344

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

Add a --host flag so serve can bind an interface other than loopback

1 participant