feat(dashboard): trust reverse-proxy source and allow declared origins - #248
Open
AndrewMoryakov wants to merge 37 commits into
Open
feat(dashboard): trust reverse-proxy source and allow declared origins#248AndrewMoryakov wants to merge 37 commits into
AndrewMoryakov wants to merge 37 commits into
Conversation
…260713 # Conflicts: # src/dashboard/fragments.ts # tests/dashboard-api.test.ts
Bundles pre-existing local WIP (baseline/proxy/tracker/types/sessions/codex-usage + their tests) with the approved model-scope persistence feature (model-scope-store + node/fragments/dashboard chips). Checkpoint only — reorganize/split later as needed.
…e getter Final-review Minor: DashboardState already had getCompressionEnabled(); Task 3 added a redundant isCompressionEnabled() twin. buildHealthState now consumes the existing getter.
Enabling a model that reads pxpipe-imaged content measurably worse (Opus 4.7/4.8, GPT-5.5, GPT-5.6 Sol, Grok 4.5 — per FINDINGS) now pops an hx-confirm with the concrete numbers; a lit weak reader shows a ⚠ marker. Fable/Sonnet/Terra/Lun and broad gpt-5.6 are unaffected. Disabling never prompts.
…/unmeasured) Replace the ad-hoc weak-reader list with an objective MODEL_READINESS table keyed to committed eval receipts. Severity follows evidence strength: - validated (Fable) -> no warning - below-bar (Opus 4.7/4.8, GPT-5.5, Sol, Grok, broad gpt-5.6->enables Sol) -> blocking confirm on enable + concrete numbers + persistent tooltip - unmeasured (Sonnet, Terra, Lun, and any unlisted id by default) -> non-blocking ⚠ marker + 'unmeasured' tooltip Disabling never prompts. Opus/Sol now sit together honestly (both 0/15 verbatim); Terra/Lun no longer get a false clean pass.
Four new env vars, all default-preserving so existing configs are unchanged: * PXPIPE_LOG_MAX_MB rotate once the active file exceeds this size (default 100) * PXPIPE_LOG_KEEP number of rotated generations to retain on disk (.1, .2, ..., up to .N; default 1) * PXPIPE_LOG_COMPRESS set to 1 to gzip each rotated generation as .1.gz, .2.gz, ... * PXPIPE_LOG_FSYNC_MS periodic fsync interval in ms; 0 = fsync only on shutdown (default 0) Why: the hardcoded 100 MB cap and single .1 rotation silently eat disk on heavy users (~660 MB/day at our load), and emit() never fsyncs, so any non-graceful kill loses the last few seconds of events. PXPIPE_LOG_FSYNC_MS=500 caps the loss to ~0.5s; raising KEEP gives multi-day history; COMPRESS trades ~10 ms of gzip at rotation for ~5x storage. Defaults are byte-for-byte identical to the previous hardcoded behavior. Existing FileTracker / sessions / restart-restore tests pass.
Publishing the dashboard behind a TLS/basic-auth reverse proxy required two
awkward header rewrites, both stemming from isAllowedDashboardClient() demanding
a loopback Host even for the explicitly configured PXPIPE_TRUSTED_DASHBOARD_PROXY:
* the proxy had to forge Host: localhost, which then
* broke the same-origin mutation check (internal origin became http://localhost
while the browser sends the real public origin), forcing the proxy to also
strip Origin.
This lets the operator configure it directly instead:
* a request whose source equals PXPIPE_TRUSTED_DASHBOARD_PROXY is trusted
without a loopback Host (Host validation stays mandatory for every other
source);
* PXPIPE_DASHBOARD_ORIGINS (comma-separated) declares public origins accepted
for dashboard mutations.
CSRF protection is unchanged: cross-site writes are still rejected via
Sec-Fetch-Site, which nothing here touches. Both env vars are documented in
--help.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Running the dashboard behind a TLS + basic-auth reverse proxy currently requires two awkward header rewrites in the proxy config, and both trace back to a single spot:
isAllowedDashboardClient()demands a loopbackHosteven for a request coming from the explicitly configuredPXPIPE_TRUSTED_DASHBOARD_PROXY.Because of that, the proxy has to:
Host: localhostso the request is accepted, which in turnhttp://localhostwhile the browser sends the real public origin — so the proxy must also stripOriginto make dashboard toggles work.Two config hacks fighting one code assumption.
Change
Let the operator configure this directly instead of forging headers:
PXPIPE_TRUSTED_DASHBOARD_PROXYis trusted without a loopbackHost. Host validation stays mandatory for every other source (loopback still requires a loopback Host + loopback address, exactly as before).PXPIPE_DASHBOARD_ORIGINS(comma-separated) declares the public origin(s) accepted for dashboard mutations behind the proxy.Both are documented in
--help.Security
CSRF protection is unchanged. Cross-site writes are still rejected by the
Sec-Fetch-Site: cross-sitecheck, which this PR does not touch. The trusted-proxy bypass only affects a source address the operator explicitly configured as trusted; unconfigured/other sources still require loopback Host and loopback address.Testing
Deployed behind Traefik (TLS + basic-auth), reaching pxpipe from the trusted proxy address with the real public
Host:GET /with publicHost→ 200 (previously 403: non-loopback Host)OrigininPXPIPE_DASHBOARD_ORIGINS→ 200{"compression_enabled":true}Sec-Fetch-Site: cross-site) → 403 Forbidden (CSRF still blocked)With the patch the reverse-proxy config collapses to just router + basicAuth — no
Hostrewrite, noOriginstripping.