You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GET /api/proxy disables TLS certificate verification for the entire Node process, permanently, the first time it proxies any https:// URL. It is never restored, so every later outbound HTTPS call the backend makes — Google OAuth token exchange, Google Photos, Apple CalDAV, calendar feeds, and now Home Assistant — runs unverified for the remaining life of the container.
// For HTTP requests, ensure we don't have HTTPS-specific configurationsif(target.protocol==='http:'){console.log('Making HTTP request (not HTTPS)');// No special HTTPS agent needed for HTTP}else{console.log('Making HTTPS request');// For HTTPS, we might need to handle self-signed certificatesprocess.env.NODE_TLS_REJECT_UNAUTHORIZED='0';// Only for development}
The trailing comment says "Only for development", but there is no guard on NODE_ENV or DEMO_MODE — this runs in production.
Why it is worse than it looks
NODE_TLS_REJECT_UNAUTHORIZED is a process-global that Node reads at connection time, not at startup. So this is not scoped to the proxied request:
It applies to connections to other hosts, not just the proxy target.
It applies to connections made later, including ones already in flight elsewhere.
Nothing ever sets it back to 1.
One proxied HTTPS request permanently downgrades the whole backend.
Reproduction
Standalone script — no HomeGlow needed, just the same line the route executes. It targets self-signed.badssl.com, which exists to serve an invalid certificate:
constTARGET='https://self-signed.badssl.com/';constattempt=async(label)=>{try{constres=awaitfetch(TARGET);console.log(`${label} -> CONNECTED (HTTP ${res.status}) — certificate NOT verified`);}catch(err){console.log(`${label} -> rejected (${err.cause?.code}) — certificate verified`);}};awaitattempt('BEFORE any /api/proxy request');process.env.NODE_TLS_REJECT_UNAUTHORIZED='0';// exactly what index.js:4336 doesawaitattempt('AFTER, on an unrelated later connection');
Output on Node 24:
NODE_TLS_REJECT_UNAUTHORIZED at process start: undefined
BEFORE any /api/proxy request
-> rejected (DEPTH_ZERO_SELF_SIGNED_CERT) — certificate verified
[simulating one HTTPS request through GET /api/proxy]
(node:26104) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0'
makes TLS connections and HTTPS requests insecure by disabling certificate verification.
AFTER, on an unrelated later connection
-> CONNECTED (HTTP 200) — certificate NOT verified
NODE_TLS_REJECT_UNAUTHORIZED now: "0" (never restored)
Node emits its own warning about this, which is a reasonable signal that it is not a supported pattern.
Blast radius
Every outbound HTTPS caller in the backend becomes unverified once the flag flips:
The Google refresh-token exchange is the one that concerns me most: those are long-lived credentials, and a machine-in-the-middle on the LAN could present any certificate once the flag is set.
Mitigating context, so this is not overstated: HomeGlow is LAN-only by design, /api/proxy is hostname-whitelisted (empty by default apart from calapi.inadiutorium.cz), and it is blocked entirely in demo mode. Triggering the flip requires someone to have added an https:// host to PROXY_WHITELIST and hit the route. So this is a latent downgrade rather than an actively exploited hole — but the fix is small and the failure mode is silent.
Why this is worth fixing now rather than later
Home Assistant behind a self-signed certificate is an extremely common self-hosted setup, and it is precisely the case that would tempt someone to reach for this flag again. Fixing it before the Home Assistant integration grows (control panel, entity alerts — #57) means the right pattern is already in place.
Suggested fix
Never touch the global. Scope the relaxation to the one request with an explicit agent:
consthttps=require('node:https');// Opt-in per host, not a process-wide switch.constallowSelfSigned=isSelfSignedAllowed(targetHostname);constaxiosConfig={timeout: 15000,maxRedirects: 5,headers: {/* ... */},validateStatus: (status)=>status<500,
...(allowSelfSigned
? {httpsAgent: newhttps.Agent({rejectUnauthorized: false})}
: {}),};
Design questions worth deciding rather than assuming:
Should self-signed be allowed at all? The simplest correct fix is to delete the line and let self-signed targets fail with a clear error. That is a behaviour change for anyone currently relying on it, though I doubt anyone is, given the whitelist starts effectively empty.
If it stays, make it explicit and per-host — e.g. an allow_insecure_tls list in settings, surfaced in the Admin Panel with a visible warning, rather than silently applying to everything.
I'd lean toward deleting it and adding the per-host opt-in only when a real need appears.
Notes
Found while implementing feat: Home Assistant Integration #57 (Home Assistant weather); deliberately not fixed in that branch to keep the change reviewable and because the "should self-signed be supported" question deserves its own decision.
The same route has two smaller shortcomings worth folding in if this is touched: it is GET-only and forwards no request headers, which is why the Home Assistant integration needed its own authenticated path rather than reusing the proxy.
Summary
GET /api/proxydisables TLS certificate verification for the entire Node process, permanently, the first time it proxies anyhttps://URL. It is never restored, so every later outbound HTTPS call the backend makes — Google OAuth token exchange, Google Photos, Apple CalDAV, calendar feeds, and now Home Assistant — runs unverified for the remaining life of the container.server/index.js:4336:The trailing comment says "Only for development", but there is no guard on
NODE_ENVorDEMO_MODE— this runs in production.Why it is worse than it looks
NODE_TLS_REJECT_UNAUTHORIZEDis a process-global that Node reads at connection time, not at startup. So this is not scoped to the proxied request:1.One proxied HTTPS request permanently downgrades the whole backend.
Reproduction
Standalone script — no HomeGlow needed, just the same line the route executes. It targets
self-signed.badssl.com, which exists to serve an invalid certificate:Output on Node 24:
Node emits its own warning about this, which is a reasonable signal that it is not a supported pattern.
Blast radius
Every outbound HTTPS caller in the backend becomes unverified once the flag flips:
services/googleConnection.jsservices/googlePhotos.js,googlePhotosPicker.js,googleCalendar.jsservices/appleCalDAV.jsservices/calendarSync.jsservices/weather/openweathermap.jsservices/homeAssistant.jsThe Google refresh-token exchange is the one that concerns me most: those are long-lived credentials, and a machine-in-the-middle on the LAN could present any certificate once the flag is set.
Mitigating context, so this is not overstated: HomeGlow is LAN-only by design,
/api/proxyis hostname-whitelisted (empty by default apart fromcalapi.inadiutorium.cz), and it is blocked entirely in demo mode. Triggering the flip requires someone to have added anhttps://host toPROXY_WHITELISTand hit the route. So this is a latent downgrade rather than an actively exploited hole — but the fix is small and the failure mode is silent.Why this is worth fixing now rather than later
Home Assistant behind a self-signed certificate is an extremely common self-hosted setup, and it is precisely the case that would tempt someone to reach for this flag again. Fixing it before the Home Assistant integration grows (control panel, entity alerts — #57) means the right pattern is already in place.
Suggested fix
Never touch the global. Scope the relaxation to the one request with an explicit agent:
Design questions worth deciding rather than assuming:
allow_insecure_tlslist in settings, surfaced in the Admin Panel with a visible warning, rather than silently applying to everything.I'd lean toward deleting it and adding the per-host opt-in only when a real need appears.
Notes