Skip to content

fix(runtimeConfig): filter localhost URLs from external service config - #108

Closed
danielheene wants to merge 1 commit into
mainfrom
fix/umami-e2e-noise
Closed

danielheene wants to merge 1 commit into
mainfrom
fix/umami-e2e-noise

Conversation

@danielheene

Copy link
Copy Markdown
Owner

Summary

  • Adds readExternalUrl() helper in src/lib/runtimeConfig/index.ts that returns undefined for any localhost/127.0.0.1/::1 URL
  • Uses it for umamiUrl, statusPageUrl, and iconifyApi — the fields whose .env.test values are placeholder-only addresses pointing at services not present in CI/E2E
  • This stops UmamiWidget.data.ts from attempting Umami API login, the /stats/[...path] proxy from trying to forward to localhost:3002, and ServiceStatus from fetching from localhost:3001, without swallowing errors in catch blocks

Test plan

  • Existing runtimeConfig unit tests pass (no changes needed — test values use non-localhost URLs)
  • E2E logs no longer show ECONNREFUSED spam for :3002

Localhost values in .env.test are format-valid placeholders — treating them
as real URLs causes consumers (UmamiWidget, ServiceStatus) to attempt network
connections to addresses that don't exist in test and E2E environments, filling
logs with ECONNREFUSED noise. readExternalUrl() now returns undefined for any
localhost/127.0.0.1/::1 URL, so those consumers skip their network calls
without needing per-consumer guards.
statusPageUrl: readEnv('STATUS_PAGE_URL'),
umamiUrl: readEnv('NEXT_PUBLIC_UMAMI_URL'),
statusPageUrl: readExternalUrl('STATUS_PAGE_URL'),
umamiUrl: readExternalUrl('NEXT_PUBLIC_UMAMI_URL'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: umamiUrl can now be undefined, which makes new URL(path, base) throw in consumers

readExternalUrl now maps NEXT_PUBLIC_UMAMI_URL to undefined in E2E, but two consumers pass the value directly as the base of new URL(path, base) with no guard:

  • src/widgets/UmamiWidget/UmamiWidget.data.ts:119 (buildApiUrl)
  • src/lib/fetchers/fetchTrendingBlogPosts.ts:40 (buildApiUrl)

new URL('/api/...', undefined) throws TypeError: Invalid URL because a relative URL requires a base. These calls run before fetcher's try/catch, so fetchWebsite, fetchStats, fetchEvents, fetchPaths, fetchPageViews and fetchTrendingBlogPosts now throw instead of returning null. UmamiWidget.tsx:12 and TrendingBlogPostsBlock/Renderer/Renderer.tsx:26 await them unguarded, so the admin Umami widget and any page using the trending-posts block fail to render in E2E — before this change they degraded gracefully to null (the ECONNREFUSED happened inside the caught fetcher). Guard the missing URL in these buildApiUrl helpers or in the fetch* functions.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (!value) return undefined
try {
const { hostname } = new URL(value)
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') return undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: hostname === '::1' never matches — IPv6 loopback URLs are not filtered

The WHATWG URL API serializes IPv6 hosts with brackets: new URL('http://[::1]:3001').hostname is '[::1]', not '::1'. This check is therefore dead code and IPv6-loopback placeholder URLs pass through, even though the commit message claims ::1 is handled. Compare against '[::1]' as well.

Suggested change
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') return undefined
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]') return undefined

Reply with @kilocode-bot fix it to have Kilo Code address this issue.


// Localhost URLs in env files are format-valid placeholders (e.g. .env.test).
// Treat them as unset so consumers don't attempt real network calls to them.
const readExternalUrl = (name: string): string | undefined => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: No regression test for the new localhost filtering

src/lib/runtimeConfig/index.test.ts already covers readRuntimeConfigFromEnv mapping, but nothing asserts the behavior this PR adds: a localhost/127.0.0.1 URL mapping to undefined while a real URL passes through. A case such as vi.stubEnv('NEXT_PUBLIC_UMAMI_URL', 'http://localhost:3002') with expect(readRuntimeConfigFromEnv().umamiUrl).toBeUndefined() would lock the filter in and matches the repo's regression-test convention for bug fixes.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

serverUrl: readEnv('SERVER_URL'),
statusPageUrl: readEnv('STATUS_PAGE_URL'),
umamiUrl: readEnv('NEXT_PUBLIC_UMAMI_URL'),
statusPageUrl: readExternalUrl('STATUS_PAGE_URL'),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: This does not silence the ServiceStatus heartbeat fetch described in the PR

The PR says this stops ServiceStatus from fetching from localhost:3001, but that fetch uses STATUS_PAGE_HEARTBEAT_URL (http://localhost:3001/api/push/test in .env.test), which is read directly from process.env in src/components/ServiceStatus/ServiceStatus.tsx:25 — not via getRuntimeConfig(). Only the Link href (which never made network calls) is filtered, so the E2E ECONNREFUSED noise from the heartbeat fetch continues. If silencing it is part of the goal, STATUS_PAGE_HEARTBEAT_URL needs the same filtering or a guard in fetchStatus.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

CRITICAL

File Line Issue
src/lib/runtimeConfig/index.ts 45 umamiUrl can now be undefined, but buildApiUrl in UmamiWidget.data.ts:119 and fetchTrendingBlogPosts.ts:40 pass it as the base of new URL(path, base) with no guard — new URL(relativePath, undefined) throws TypeError: Invalid URL outside fetcher's try/catch, so fetchWebsite/fetchStats/fetchEvents/fetchPaths/fetchPageViews/fetchTrendingBlogPosts throw instead of returning null, breaking the admin Umami widget (UmamiWidget.tsx:12) and any page using TrendingBlogPostsBlock (Renderer.tsx:26) in E2E

SUGGESTION

File Line Issue
src/lib/runtimeConfig/index.ts 35 hostname === '::1' never matches — the WHATWG URL API serializes IPv6 hosts with brackets ('[::1]'), so IPv6-loopback URLs are not filtered despite the commit message claiming ::1 support
src/lib/runtimeConfig/index.ts 30 New localhost-filtering logic has no regression test in the existing src/lib/runtimeConfig/index.test.ts
src/lib/runtimeConfig/index.ts 44 PR claims this stops the ServiceStatus fetch from localhost:3001, but that fetch uses STATUS_PAGE_HEARTBEAT_URL, read directly from process.env in ServiceStatus.tsx:25 — not via getRuntimeConfig() — so the E2E heartbeat ECONNREFUSED noise continues
Files Reviewed (1 file)
  • src/lib/runtimeConfig/index.ts - 4 issues

Fix these issues in Kilo Cloud


Reviewed by free · Input: 65.2K · Output: 28.7K · Cached: 572K

This branch was successfully deployed

1 active deployment
Testing — e8a65250 Deployed Oct 9, 2026 by danielheene via CI / E2E Tests #80
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