From e092efdf883880de2f8983e917e516bccedc4601 Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Sat, 30 May 2026 16:32:26 -0700 Subject: [PATCH] fix(bootcheck): warn instead of block on public HTTP core URLs (#3031) --- .../BootCheckGate/BootCheckGate.tsx | 37 +++++++++++++++---- .../__tests__/BootCheckGate.test.tsx | 23 +++++++++++- app/src/lib/i18n/ar.ts | 2 + app/src/lib/i18n/bn.ts | 2 + app/src/lib/i18n/de.ts | 2 + app/src/lib/i18n/en.ts | 2 + app/src/lib/i18n/es.ts | 2 + app/src/lib/i18n/fr.ts | 2 + app/src/lib/i18n/hi.ts | 2 + app/src/lib/i18n/id.ts | 2 + app/src/lib/i18n/it.ts | 2 + app/src/lib/i18n/ko.ts | 2 + app/src/lib/i18n/pl.ts | 2 + app/src/lib/i18n/pt.ts | 2 + app/src/lib/i18n/ru.ts | 2 + app/src/lib/i18n/zh-CN.ts | 2 + 16 files changed, 79 insertions(+), 9 deletions(-) diff --git a/app/src/components/BootCheckGate/BootCheckGate.tsx b/app/src/components/BootCheckGate/BootCheckGate.tsx index 57f011e7d..61b6511a5 100644 --- a/app/src/components/BootCheckGate/BootCheckGate.tsx +++ b/app/src/components/BootCheckGate/BootCheckGate.tsx @@ -38,6 +38,29 @@ import LanguageSelect from '../LanguageSelect'; const log = debug('boot-check'); const logError = debug('boot-check:error'); +/** + * Plain HTTP to a public host is insecure (unencrypted traffic), but we no + * longer block it — return a non-blocking warning string so the UI can nudge + * the user toward HTTPS while still letting them proceed. Returns null when the + * URL is empty, unparseable, HTTPS, or points at a local/private host. + */ +function httpPublicHostWarning( + rawUrl: string, + t: (key: string, fallback?: string) => string +): string | null { + const trimmed = rawUrl.trim(); + if (!trimmed) return null; + try { + const parsed = new URL(normalizeRpcUrl(trimmed)); + if (parsed.protocol === 'http:' && !isLocalOrPrivateNetworkHost(parsed.hostname)) { + return t('bootCheck.httpPublicWarning'); + } + } catch { + // Unparseable URL — the error path in validateInputs handles messaging. + } + return null; +} + // --------------------------------------------------------------------------- // Internal types // --------------------------------------------------------------------------- @@ -107,6 +130,7 @@ function ModePicker({ onConfirm }: PickerProps) { const [cloudUrl, setCloudUrl] = useState(''); const [cloudToken, setCloudToken] = useState(''); const [urlError, setUrlError] = useState(null); + const [urlWarning, setUrlWarning] = useState(null); const [tokenError, setTokenError] = useState(null); const [testStatus, setTestStatus] = useState({ kind: 'idle' }); @@ -135,12 +159,6 @@ function ModePicker({ onConfirm }: PickerProps) { setUrlError(t('bootCheck.urlMustStartWith')); return null; } - if (parsed.protocol === 'http:' && !isLocalOrPrivateNetworkHost(parsed.hostname)) { - setUrlError( - 'HTTP core URLs are only allowed for localhost or private network hosts. Use HTTPS for public hosts.' - ); - return null; - } } catch { setUrlError(t('bootCheck.validUrlRequired')); return null; @@ -287,13 +305,18 @@ function ModePicker({ onConfirm }: PickerProps) { placeholder={t('bootCheck.rpcUrlPlaceholder')} value={cloudUrl} onChange={e => { - setCloudUrl(e.target.value); + const next = e.target.value; + setCloudUrl(next); setUrlError(null); + setUrlWarning(httpPublicHostWarning(next, t)); setTestStatus({ kind: 'idle' }); }} className="rounded-lg border border-stone-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-2 text-sm text-stone-900 dark:text-neutral-100 placeholder-stone-400 dark:placeholder-neutral-500 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500" /> {urlError &&

{urlError}

} + {!urlError && urlWarning && ( +

{urlWarning}

+ )}