diff --git a/extension/background.js b/extension/background.js index af1f32ea..335e5431 100644 --- a/extension/background.js +++ b/extension/background.js @@ -161,6 +161,21 @@ async function fetchAndRelayRefs() { // ─── Message Handling ────────────────────────────────────────── chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + // Security: only accept messages from this extension's own scripts + if (sender.id !== chrome.runtime.id) { + console.warn('[gstack] Rejected message from unknown sender:', sender.id); + return; + } + + const ALLOWED_TYPES = new Set([ + 'getPort', 'setPort', 'getServerUrl', 'fetchRefs', + 'openSidePanel', 'command', 'sidebar-command' + ]); + if (!ALLOWED_TYPES.has(msg.type)) { + console.warn('[gstack] Rejected unknown message type:', msg.type); + return; + } + if (msg.type === 'getPort') { sendResponse({ port: serverPort, connected: isConnected }); return true; diff --git a/test/audit-compliance.test.ts b/test/audit-compliance.test.ts index e08fe31a..9a1980d0 100644 --- a/test/audit-compliance.test.ts +++ b/test/audit-compliance.test.ts @@ -76,6 +76,13 @@ describe('Audit compliance', () => { expect(review).toContain('Data NOT sent'); }); + // Round 2 Fix 3: Extension sender validation + message type allowlist + test('extension background.js validates message sender', () => { + const bg = readFileSync(join(ROOT, 'extension/background.js'), 'utf-8'); + expect(bg).toContain('sender.id !== chrome.runtime.id'); + expect(bg).toContain('ALLOWED_TYPES'); + }); + // Round 2 Fix 4: Chrome CDP binds to localhost only test('chrome-cdp binds to localhost only', () => { const cdp = readFileSync(join(ROOT, 'bin/chrome-cdp'), 'utf-8');