From 194b2ed1f395742da7ee9285558c4651672e2c58 Mon Sep 17 00:00:00 2001 From: anupamme Date: Tue, 20 Oct 2026 23:16:34 +0000 Subject: [PATCH 1/2] fix: multi_agent.cwe-918 security vulnerability Automated security fix generated by OrbisAI Security --- src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.ts b/src/index.ts index c1818d7..ecf2562 100644 --- a/src/index.ts +++ b/src/index.ts @@ -100,6 +100,12 @@ async function fetchResults( state.currentQuery = query const url = new URL(src, window.location.href) + if (url.origin !== window.location.origin) { + remoteInput.dispatchEvent( + new CustomEvent('remote-input-error', { bubbles: true }) + ) + return + } const params = new URLSearchParams(url.search) params.append(remoteInput.getAttribute('param') || 'q', query) url.search = params.toString() From 17c8dcb8e1fb68b46310ba6b1485726c632f15ce Mon Sep 17 00:00:00 2001 From: Anupam Mediratta Date: Sat, 24 Oct 2026 06:59:33 +0530 Subject: [PATCH 2/2] Enforce same-origin across redirects and cancel pending requests on rejected src Co-Authored-By: Claude Sonnet 5.5 --- src/index.ts | 21 ++++++++++++++++++--- test/karma.config.js | 12 ++++++++++++ test/test.js | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index ecf2562..88064f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,6 +101,13 @@ async function fetchResults( const url = new URL(src, window.location.href) if (url.origin !== window.location.origin) { + // Cancel and invalidate any pending request so it can't render stale results. + if (state.controller) { + state.controller.abort() + state.controller = null + remoteInput.removeAttribute('loading') + remoteInput.dispatchEvent(new CustomEvent('loadend')) + } remoteInput.dispatchEvent( new CustomEvent('remote-input-error', { bubbles: true }) ) @@ -117,21 +124,29 @@ async function fetchResults( remoteInput.setAttribute('loading', '') } - state.controller = makeAbortController() + const controller = makeAbortController() + state.controller = controller let response let html = '' try { response = await fetchWithNetworkEvents(remoteInput, url.toString(), { - signal: state.controller.signal, + signal: controller.signal, credentials: 'same-origin', + mode: 'same-origin', headers: { accept: 'text/fragment+html' } }) html = await response.text() + // A newer request (or a rejected src) superseded this one. + if (state.controller !== controller) return remoteInput.removeAttribute('loading') state.controller = null } catch (error) { - if (error instanceof Error && error.name !== 'AbortError') { + if ( + error instanceof Error && + error.name !== 'AbortError' && + state.controller === controller + ) { remoteInput.removeAttribute('loading') state.controller = null } diff --git a/test/karma.config.js b/test/karma.config.js index 48b6e2f..b1d7605 100644 --- a/test/karma.config.js +++ b/test/karma.config.js @@ -4,6 +4,18 @@ function reply(request, response, next) { response.writeHead(500) response.ok = false response.end('Server error') + } else if (request.url.startsWith('/redirect')) { + const host = request.headers.host || '' + const other = host.startsWith('localhost') ? '127.0.0.1' : 'localhost' + response.writeHead(302, { + location: `http://${other}:${host.split(':')[1]}/results` + }) + response.end() + } else if (request.url.startsWith('/slow')) { + setTimeout(() => { + response.writeHead(200) + response.end('
  1. stale
') + }, 200) } else if (request.url.startsWith('/network-error')) { request.destroy(new Error()) response.end() diff --git a/test/test.js b/test/test.js index 24416c3..a76a410 100644 --- a/test/test.js +++ b/test/test.js @@ -131,6 +131,46 @@ describe('remote-input', function () { ) }) + it('rejects a cross-origin src', async function () { + remoteInput.src = 'https://example.com/results' + const error = once(remoteInput, 'remote-input-error') + changeValue(input, 'test') + await error + assert.equal(results.innerHTML, '') + }) + + it('does not follow cross-origin redirects', async function () { + remoteInput.src = '/redirect' + const error = once(remoteInput, 'error') + let succeeded = false + remoteInput.addEventListener('remote-input-success', () => { + succeeded = true + }) + changeValue(input, 'test') + await error + assert.notOk(succeeded) + assert.equal(results.innerHTML, '') + }) + + it('cancels a pending request when src becomes cross-origin', async function () { + remoteInput.src = '/slow' + let succeeded = false + remoteInput.addEventListener('remote-input-success', () => { + succeeded = true + }) + changeValue(input, 'test') + assert.ok(remoteInput.hasAttribute('loading')) + + const error = once(remoteInput, 'remote-input-error') + remoteInput.src = 'https://example.com/results' + await error + assert.notOk(remoteInput.hasAttribute('loading')) + + await new Promise((resolve) => setTimeout(resolve, 400)) + assert.notOk(succeeded, 'stale response must not emit success') + assert.equal(results.innerHTML, '') + }) + it('repects param attribute', async function () { remoteInput.setAttribute('param', 'robot') assert.equal(results.innerHTML, '')