From 7f7c0ef7b01ca6aaa48518592dac9945066fb917 Mon Sep 17 00:00:00 2001 From: null Date: Sat, 11 Jul 2026 10:51:35 -0500 Subject: [PATCH] feat(mobile): warn on insecure http:// server URLs before native login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SetupScreen now flags a plain http:// address (to anything but loopback) — the native login POST would send the password in cleartext — and requires an explicit second tap ("Connect anyway") to proceed, with an override for trusted LAN setups. Co-Authored-By: Claude Opus 4.8 --- src/SetupScreen.tsx | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/SetupScreen.tsx b/src/SetupScreen.tsx index 8c78b9c..8147667 100644 --- a/src/SetupScreen.tsx +++ b/src/SetupScreen.tsx @@ -21,9 +21,22 @@ function isValidUrl(url: string): boolean { } } +// Plain http:// (to anything but loopback) sends the password in cleartext during +// the native login POST. Warn and require an explicit acknowledgement. +function isInsecureUrl(url: string): boolean { + try { + const u = new URL(url); + if (u.protocol !== 'http:') return false; + return !(u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '::1'); + } catch { + return false; + } +} + export default function SetupScreen({ onConnect, onLocalMode }: Props) { const [url, setUrl] = useState(''); const [error, setError] = useState(''); + const [insecureAck, setInsecureAck] = useState(false); const [connecting, setConnecting] = useState(false); function handleConnect() { @@ -32,6 +45,12 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) { setError('Enter a valid URL, e.g. https://bills.yourdomain.com'); return; } + // First tap on an insecure URL: warn and require a second, explicit tap. + if (isInsecureUrl(normalized) && !insecureAck) { + setError(''); + setInsecureAck(true); + return; + } setError(''); setConnecting(true); onConnect(normalized); @@ -69,11 +88,17 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) { spellCheck={false} placeholder="https://bills.yourdomain.com" value={url} - onChange={e => { setUrl(e.target.value); setError(''); }} + onChange={e => { setUrl(e.target.value); setError(''); setInsecureAck(false); }} onKeyDown={handleKey} disabled={connecting} /> {error &&

{error}

} + {insecureAck && ( +

+ This is an insecure http:// address — your password will be sent + unencrypted. Only continue on a network you trust. Tap again to connect anyway. +

+ )}

The address of your Bill Tracker server. Must be reachable from this device.

@@ -84,7 +109,7 @@ export default function SetupScreen({ onConnect, onLocalMode }: Props) { onClick={handleConnect} disabled={connecting || !url.trim()} > - {connecting ? 'Connecting…' : 'Continue'} + {connecting ? 'Connecting…' : insecureAck ? 'Connect anyway' : 'Continue'}