Conversation
…nticated npm install RCE
The /api/pxpipe/* routes (install, start, restart, stop) were not
listed in LOCAL_ONLY_PATHS, unlike their /api/headroom counterparts.
When requireLogin was false (fresh install default), a remote attacker
could POST to /api/pxpipe/install to trigger spawn('npm', ['install',
'pxpipe-proxy@latest']), achieving unauthenticated remote code execution
via supply-chain attack.
E2E verified: dev server with requireLogin=false, Host:evil.com → 403
after fix; /api/headroom already protected (no regression).
afandiaziz
pushed a commit
to afandiaziz/9router
that referenced
this pull request
Aug 8, 2026
Cherry-picked from open upstream PRs (none merged upstream as of 2026-08-09): decolua#3078 /api/pxpipe -> LOCAL_ONLY_PATHS (defense in depth) decolua#3085 enforce requireApiKey on GET /v1/models decolua#3063 SSRF guard on search baseUrl + block default-password remote login decolua#3081 inject stream_options.include_usage for OpenAI-compatible upstreams decolua#3083 read cached_tokens from nested prompt_tokens_details Verified: no test regressions vs v0.5.50 baseline (88 pre-existing failures unchanged); +21 new passing tests.
golamrabbi696
added a commit
to golamrabbi696/EzRouter
that referenced
this pull request
Aug 12, 2026
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.
Summary
Consolidated security hardening for three related findings from #3049 (SSRF report with full PoC). All three were verified end-to-end on a fresh Pi 500 + Docker v0.5.50 deployment before writing these fixes.
1. SSRF via
provider_options.baseUrlon/v1/search(searxng)resolveBaseUrl()inopen-sse/handlers/search/callers.jsaccepted a client-suppliedprovider_options.baseUrloverride with no validation. Since searxng is anoAuthprovider (no credentials required), any remote client could make the server issue GET requests to arbitrary internal/private/metadata addresses (e.g.http://169.254.169.254/latest/meta-data,http://10.0.0.1).Fix:
resolveBaseUrl()now rejects non-public URLs for client-supplied overrides via the existingassertPublicUrl()(same guard already used by/v1/fetchand provider-nodes validation). Onlyhttp:/https:protocols are accepted. The provider's own configuredbaseUrl(admin-controlled) remains trusted as-is.2. Default-password remote login issues a valid JWT
On a fresh install (no password hash,
INITIAL_PASSWORDunset), a remote client logging in with the default password123456received a valid dashboard JWT before any password change. ThemustChangePasswordflag was returned in the response body, but the JWT was already set — so a remote attacker could immediatelyPATCH /api/settingsto disablerequireLoginentirely (CVE-2026-56679 class attack chain).Fix: When
mustChangePasswordis true (fresh install + remote + default password), the login route now returns 403 without issuing a JWT. Local logins are unaffected. SettingINITIAL_PASSWORDalso bypasses this check.Known trade-off: this intentionally leaves no remote self-service password-change path — the change-password flow (
PATCH /api/settings) requires a JWT, which we deliberately withhold. A remote fresh-install user must either change the password from the local machine or setINITIAL_PASSWORDbefore first launch. This is a deliberate security trade-off, not an oversight.3.
/api/usage/request-detailsreturns full conversation contentThe endpoint returned complete
request(user prompts, tool calls),providerRequest,providerResponse, andresponsepayloads for every stored request. Any dashboard-authenticated user (or anyone ifrequireLoginis disabled) could read all conversation history.Fix: The four payload fields are replaced with
{ redacted: true }. Metadata (model, tokens, latency, status, timestamps) is preserved.Build fix: declare
chalkandprop-typesBoth are directly imported by source files (
src/lib/oauth/utils/ui.jsimportschalk; dashboard provider pages importprop-types) but were never declared inpackage.json, causingnpm run buildto fail on fresh clones. Added as proper dependencies.Test plan
resolveBaseUrl()SSRF guard (tests/unit/search-ssrf-guard.test.js): public http/https allowed; loopback, private IPs (10.x, 192.168.x, 172.16.x), localhost hostname, cloud metadata (169.254.169.254), and non-http protocols (file, gopher, ftp) all rejected.tests/unit/request-details-redaction.test.js): payloads redacted, metadata preserved, empty/null details handled.npm run buildpasses with the new dependency declarations.Known limitations
next start/ pm2 deployments (nocustom-server.js):isLocalRequest()falls back to Host-header judgment, which is spoofable. This is a pre-existing issue (CVE-2026-56681 class) not introduced by this PR. Docker deployments (the recommended path, usingcustom-server.js) are not affected.assertPublicUrl()validates hostnames but does not perform DNS resolution, so DNS-rebinding TOCTOU is out of scope — consistent with the existing/v1/fetchguard.Supersedes #3050 and #3060.