From 751d170c94421fd6e5fe0e8cdabf7f48fdaf2f91 Mon Sep 17 00:00:00 2001 From: Ravi Tharuma Date: Mon, 20 Jul 2026 23:55:43 +0200 Subject: [PATCH] security: restrict plaintext HTTP to loopback --- src/plugin.ts | 11 +++++++++++ test/plugin.test.mjs | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/plugin.ts b/src/plugin.ts index d294271..5121b86 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -246,6 +246,13 @@ async function readAuthFromStore( } } +function isLoopbackHostname(hostname: string): boolean { + const normalized = hostname.replace(/^\[|\]$/g, '').toLowerCase(); + if (normalized === 'localhost' || normalized === '::1') return true; + const octets = normalized.split('.').map(Number); + return octets.length === 4 && octets.every(Number.isInteger) && octets[0] === 127; +} + export function getBaseUrl( options?: Record, authBaseUrl?: string, @@ -266,6 +273,10 @@ export function getBaseUrl( warn(`Ignoring unsupported baseURL protocol: ${sanitizeForLog(parsed.protocol)}`); continue; } + if (parsed.protocol === 'http:' && !isLoopbackHostname(parsed.hostname)) { + warn(`Ignoring insecure remote HTTP baseURL: ${sanitizeForLog(trimmed)}`); + continue; + } return trimmed.replace(/\/+$/, ''); } catch { warn(`Ignoring invalid baseURL: ${sanitizeForLog(trimmed)}`); diff --git a/test/plugin.test.mjs b/test/plugin.test.mjs index 039ca7b..482a2d5 100644 --- a/test/plugin.test.mjs +++ b/test/plugin.test.mjs @@ -320,4 +320,12 @@ test('authorize stores baseURL and optional apiKey as JSON', async () => { const parsed = JSON.parse(result.key); assert.equal(parsed.baseURL, 'http://127.0.0.1:8317/v1'); assert.equal(parsed.apiKey, ''); -}); \ No newline at end of file +}); +test('getBaseUrl rejects plaintext HTTP for remote hosts but allows loopback', async () => { + const { getBaseUrl } = await import('../dist/src/plugin.js'); + assert.equal(getBaseUrl({ baseURL: 'http://localhost:8317/v1' }), 'http://localhost:8317/v1'); + assert.equal(getBaseUrl({ baseURL: 'http://127.42.0.1:8317/v1' }), 'http://127.42.0.1:8317/v1'); + assert.equal(getBaseUrl({ baseURL: 'http://[::1]:8317/v1' }), 'http://[::1]:8317/v1'); + assert.equal(getBaseUrl({ baseURL: 'http://proxy.example/v1' }), 'http://localhost:8317/v1'); + assert.equal(getBaseUrl({ baseURL: 'http://192.168.1.10:8317/v1' }), 'http://localhost:8317/v1'); +});