Skip to content

feat(auth): skip the password for requests a trusted proxy vouches for - #238

Merged
andrebrait merged 3 commits into
kahme247:mainfrom
andrebrait:feat/skip-auth-ranges
Oct 10, 2026
Merged

andrebrait merged 3 commits into
kahme247:mainfrom
andrebrait:feat/skip-auth-ranges

Conversation

@andrebrait

@andrebrait andrebrait commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Adds OMP_WEB_TRUSTED_HEADER_SHA256. The reverse proxy sends a secret in X-Omp-Web-Auth, and omp-web holds only the secret's SHA-256 digest: a request whose header hashes to that digest counts as signed in. The reverse proxy decides which clients get the header, typically by address range, so trusted networks can skip the password while everyone else still signs in.

Why the proxy decides

omp-web has no client address it can trust. The password check runs in proxy.ts, which cannot see the socket address. Next fills X-Forwarded-For from the socket only when the client did not send one (??= in base-server.js), so any client could claim a trusted address. Behind a reverse proxy, every connection also comes from 127.0.0.1. nginx does see the real client address, including behind Cloudflare and other proxies through its realip module.

Changes

  • proxy.ts / lib/web-auth.ts: hash the header with SHA-256 and compare it to the configured digest in constant time. The secret is random, so a fast hash suffices, and omp-web never holds the secret itself. An unset, empty or malformed digest (anything other than 64 hex characters) disables the feature, and an empty header never matches. The header is ignored when no OMP_WEB_PASSWORD_HASH is configured. The password flow and the non-loopback listen guard are unchanged.
  • lib/project-command-env.ts: OMP_WEB_TRUSTED_HEADER_SHA256 replaces the placeholder OMP_WEB_TRUSTED_HEADER_SECRET in the list of secrets stripped from every child process.
  • systemd and launchd installers: preserve the variable.
  • Windows tray (native and PowerShell) and background service: read OMP_WEB_PASSWORD_HASH, the legacy OMP_WEB_PASSWORD (passed through only so the server refuses it with instructions) and the digest from the user, then system, environment on every server start, and remove them from the child when neither scope sets them. Previously they preferred the copy captured at logon, so a hash rotated or removed with setx kept working until the next Windows sign-in, and the native tray did not pass the hash on at all. bin/omp-web-tray.exe is rebuilt from native/tray (dotnet publish -c Release -r win-x64 --self-contained false).
  • README (en): how to generate the secret straight into an nginx include file readable only by root and nginx, printing only the digest, so the secret never reaches shell history. Only a generated 32-byte secret is documented: the digest is a fast SHA-256, safe only for a long random value. Then an nginx setup with geo and that map. It always sets the header, using an empty value for untrusted clients, so a client cannot pass its own value through. It covers realip for Cloudflare (CF-Connecting-IP trusted only from Cloudflare's ranges, with a $realip_remote_addr allowlist as hardening), Cloudflare Tunnel, and other proxies (X-Forwarded-For with real_ip_recursive). It also warns about DNS rebinding (explicit server_name, and a default_server that returns 444) and about local processes on the same host. The Japanese and Chinese READMEs get the table row. DESIGN.md's security contract and the CHANGELOG are updated.

Rebased onto main after #239 (password hashes), squashed into one commit.

Verification

  • lib/web-auth.test.mjs covers a matching secret on a page and an API route, the /login redirect, wrong, near-miss (extra or missing character, different case), empty and missing headers, the digest itself sent as the header, an uppercase digest, and an unset, empty or malformed digest, all with a hashed password configured. The systemd installer test asserts the passthrough, and the child-environment test asserts the digest is stripped. The full suite passes: 1343 pass, 0 fail, 5 skipped.

  • tsc --noEmit passes, and eslint reports no errors on the changed files.

  • Live smoke test through a real nginx using the README's directives, with realip on CF-Connecting-IP:

    Request Result
    Trusted client 10.0.0.5 200, and /login redirects to /
    Untrusted client 203.0.113.9 401
    Untrusted client sending the real secret itself 401 (nginx overwrites the header)
    Connection from an address outside the $realip_remote_addr allowlist 403
    Direct request to omp-web without the header 401
    Direct request to omp-web sending the digest as the header 401

    The README's generation snippet was run as written (with a temporary path in place of /etc/nginx and without sudo): it wrote a 0640 include file, the printed digest matched the secret in that file, and the shell history file did not contain the secret.

  • Live smoke on the rebased commit with OMP_WEB_PASSWORD_HASH from ompweb hash-password: trusted header 200 (and /login → /), wrong header, digest as header and no header 401, password sign-in 200.

Not run: the PowerShell scripts and the rebuilt exe, which need Windows. The launchd passthrough has no test, because there is no launchd test file.

OMP_WEB_TRUSTED_HEADER_SHA256 holds the SHA-256 digest of a secret a reverse
proxy sends in X-Omp-Web-Auth for trusted client ranges; a matching request
counts as signed in. omp-web never holds the secret, and the digest is stripped
from child processes like the other omp-web secrets. Service installers and the
Windows launchers pass it on.

The Windows tray (native and PowerShell) and background service now read
OMP_WEB_PASSWORD_HASH, the legacy OMP_WEB_PASSWORD and the digest fresh from
the user, then system, environment on every server start, so rotating or
removing them with setx takes effect at the next restart. bin/omp-web-tray.exe
is rebuilt from native/tray.

README documents the nginx setup (realip for Cloudflare, Cloudflare Tunnel and
other proxies, DNS-rebinding guard) and generating the secret without it
reaching shell history.
@andrebrait
andrebrait force-pushed the feat/skip-auth-ranges branch from 69a35c3 to a5a14ef Compare October 9, 2026 23:01
… one

The digest is a fast unsalted SHA-256, safe only for a long random secret.
andrebrait added a commit to andrebrait/ompweb that referenced this pull request Oct 10, 2026
… proxy vouches for

Squashed from origin/feat/skip-auth-ranges (40d324f) onto upstream/main.
andrebrait added a commit to andrebrait/ompweb that referenced this pull request Oct 10, 2026
… proxy vouches for

Squashed from origin/feat/skip-auth-ranges (40d324f) onto upstream/main.
@andrebrait
andrebrait merged commit 783a2a2 into kahme247:main Oct 10, 2026
3 checks passed
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