CVE-2026-45245
Description
Summarize prior to 0.15.1 contains a vulnerability in the hover summary feature that allows malicious pages to dispatch synthetic mouseover events over attacker-controlled links, causing the extension to make authenticated daemon requests using stored tokens without verifying event trustworthiness. Attackers can place local or private-network URLs behind hoverable links to route authenticated requests through the daemon, potentially accessing sensitive internal endpoints when users interact with attacker-controlled content.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
The Summarize Chrome extension prior to 0.15.1 allows malicious pages to dispatch synthetic hover events, making authenticated daemon requests to local/private hosts via stored tokens.
Vulnerability
The hover summary feature in the Summarize Chrome extension prior to version 0.15.1 accepts mouseover events without verifying event.isTrusted, allowing page JavaScript to dispatch synthetic events. Additionally, the extension does not validate that hover-summary URLs are restricted to public hosts; localhost, .local, private IPv4/IPv6, and loopback addresses are accepted. This enables attackers to craft malicious pages that, when hovered over, trigger authenticated requests to arbitrary URLs. [1][2][4]
Exploitation
An attacker must lure a user who has the Summarize extension installed and configured with a daemon token, and who has hover summaries enabled. The attacker creates a page with hoverable links pointing to local or private-network URLs (e.g., http://localhost:8080/admin). When the user hovers over such a link, the page dispatches a synthetic mouseover event, which the extension's content script accepts and forwards to the background worker. The background worker then makes an authenticated GET request (using the stored token) to the attacker-supplied URL, bypassing event trust and URL allow list checks. [1][2][4]
Impact
Successful exploitation allows an attacker to force the extension to make authenticated requests to internal endpoints that the user's browser can reach, such as localhost, 127.0.0.1, or private network services. This could lead to disclosure of sensitive information or unintended actions on local daemons or private network resources, depending on the target's functionality. The impact is limited by the fact that the user must have the extension installed, hover summaries enabled, and actively interact with attacker-controlled content. [1][4]
Mitigation
The vulnerability is fixed in Summarize version 0.15.2, released on 2026-05-17. Users should update the Chrome extension to 0.15.2 or later via the Chrome Web Store. The fix adds checks for event.isTrusted and blocks hover-summary requests to local and private literal hosts. No workarounds are available; disabling hover summaries reduces the attack surface but does not eliminate the risk entirely. [3][4]
- [security] fix(extension): harden hover summary trust boundary by Hinotoi-agent · Pull Request #218 · steipete/summarize
- [security] fix(extension): harden hover summary trust boundary (#218) · steipete/summarize@ecbb2c4
- Release v0.15.2 · steipete/summarize
- Summarize < 0.15.1 Unauthorized Daemon Request via Untrusted Events
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1ecbb2c414255[security] fix(extension): harden hover summary trust boundary (#218)
3 files changed · +106 −0
apps/chrome-extension/src/entrypoints/background/hover-controller.ts+67 −0 modified@@ -47,6 +47,61 @@ async function resolveHoverTabId(sender: chrome.runtime.MessageSender): Promise< return active?.id ?? null; } +function isPrivateIpv4(hostname: string): boolean { + const parts = hostname.split("."); + if (parts.length !== 4) return false; + const octets = parts.map((part) => Number.parseInt(part, 10)); + if (octets.some((octet, index) => !/^\d+$/.test(parts[index]) || octet < 0 || octet > 255)) { + return false; + } + const [first, second] = octets; + return ( + first === 10 || + first === 127 || + (first === 172 && second >= 16 && second <= 31) || + (first === 192 && second === 168) || + (first === 169 && second === 254) || + first === 0 + ); +} + +function isPrivateIpv6Hostname(hostname: string): boolean { + const normalized = hostname.toLowerCase(); + if (!normalized.startsWith("[") || !normalized.endsWith("]")) return false; + const address = normalized.slice(1, -1); + return ( + address === "::1" || + address.startsWith("fe8") || + address.startsWith("fe9") || + address.startsWith("fea") || + address.startsWith("feb") || + address.startsWith("fc") || + address.startsWith("fd") || + address === "::" + ); +} + +export function isHoverSummarizeUrlAllowed(rawUrl: string): boolean { + try { + const url = new URL(rawUrl); + if (url.protocol !== "http:" && url.protocol !== "https:") return false; + const hostname = url.hostname.toLowerCase(); + if (!hostname) return false; + if ( + hostname === "localhost" || + hostname.endsWith(".localhost") || + hostname.endsWith(".local") + ) { + return false; + } + if (isPrivateIpv4(hostname)) return false; + if (isPrivateIpv6Hostname(hostname)) return false; + return true; + } catch { + return false; + } +} + export function createHoverController({ hoverControllersByTabId, buildDaemonRequestBody, @@ -109,6 +164,18 @@ export function createHoverController({ return; } + if (!isHoverSummarizeUrlAllowed(msg.url)) { + const message = "Hover summaries can only summarize public HTTP(S) URLs"; + notifyStart({ ok: false, error: message }); + await sendHover(tabId, { + type: "hover:error", + requestId: msg.requestId, + url: msg.url, + message, + }); + return; + } + try { logHover("start", { tabId, requestId: msg.requestId, url: msg.url }); const base = buildDaemonRequestBody({
apps/chrome-extension/src/entrypoints/hover.content.ts+5 −0 modified@@ -114,6 +114,10 @@ type HoverFromBg = | { type: "hover:done"; requestId: string; url: string } | { type: "hover:error"; requestId: string; url: string; message: string }; +export function shouldHandleHoverTriggerEvent(event: Pick<Event, "isTrusted">): boolean { + return event.isTrusted === true; +} + function ensureTooltip(): Tooltip { ensureStyle(); let el = document.getElementById(TOOLTIP_ID) as HTMLDivElement | null; @@ -394,6 +398,7 @@ export default defineContentScript({ }; document.addEventListener("mouseover", (event) => { + if (!shouldHandleHoverTriggerEvent(event)) return; const anchor = getAnchorFromEvent(event); if (!anchor) return; if (activeAnchor === anchor) {
tests/hover.security.test.ts+34 −0 added@@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { isHoverSummarizeUrlAllowed } from "../apps/chrome-extension/src/entrypoints/background/hover-controller.js"; +import { shouldHandleHoverTriggerEvent } from "../apps/chrome-extension/src/entrypoints/hover.content.js"; + +describe("hover summary security boundaries", () => { + it("ignores synthetic hover events from page script", () => { + expect(shouldHandleHoverTriggerEvent({ isTrusted: false })).toBe(false); + }); + + it("accepts browser-trusted hover events", () => { + expect(shouldHandleHoverTriggerEvent({ isTrusted: true })).toBe(true); + }); + + it("rejects hover summaries for localhost and private literal hosts", () => { + expect(isHoverSummarizeUrlAllowed("http://127.0.0.1:8080/admin")).toBe(false); + expect(isHoverSummarizeUrlAllowed("http://[::1]:8080/admin")).toBe(false); + expect(isHoverSummarizeUrlAllowed("http://10.0.0.5/metadata")).toBe(false); + expect(isHoverSummarizeUrlAllowed("http://172.16.0.10/internal")).toBe(false); + expect(isHoverSummarizeUrlAllowed("http://192.168.1.20/router")).toBe(false); + expect(isHoverSummarizeUrlAllowed("http://169.254.169.254/latest/meta-data/")).toBe(false); + expect(isHoverSummarizeUrlAllowed("http://localhost:8787/health")).toBe(false); + }); + + it("allows ordinary public http and https URLs", () => { + expect(isHoverSummarizeUrlAllowed("https://example.com/article")).toBe(true); + expect(isHoverSummarizeUrlAllowed("http://example.com/article")).toBe(true); + }); + + it("rejects non-http hover summary URLs", () => { + expect(isHoverSummarizeUrlAllowed("file:///etc/passwd")).toBe(false); + expect(isHoverSummarizeUrlAllowed("javascript:alert(1)")).toBe(false); + expect(isHoverSummarizeUrlAllowed("notaurl")).toBe(false); + }); +});
Vulnerability mechanics
Root cause
"The extension's hover summary feature accepts untrusted (synthetic) mouseover events and does not validate that the target URL points to a public HTTP(S) resource, allowing attacker-controlled pages to trigger authenticated daemon requests to arbitrary destinations."
Attack vector
An attacker hosts a malicious web page that contains anchor elements pointing to localhost, private-network, or link-local URLs (e.g., `http://169.254.169.254/latest/meta-data/`). When the victim visits this page, the attacker's script dispatches synthetic `mouseover` events (with `isTrusted: false`) over those links. Prior to the patch, the extension's content script [hover.content.ts] did not check `event.isTrusted`, so it processed the synthetic event and sent the private URL to the background script. The background script [hover-controller.ts] then made an authenticated daemon request to fetch the URL, using the user's stored tokens, effectively performing a server-side request forgery (SSRF) [CWE-918] against internal endpoints. The attack requires only that the victim has the extension installed and visits the attacker's page; no additional privileges are needed (CVSS network vector).
Affected code
The vulnerability resides in two files. `apps/chrome-extension/src/entrypoints/hover.content.ts` — the content script's `mouseover` event listener lacked an `isTrusted` check, so it processed synthetic events. `apps/chrome-extension/src/entrypoints/background/hover-controller.ts` — the `createHoverController` function accepted any URL from the content script without validating that it pointed to a public HTTP(S) resource, allowing private-network and localhost URLs to reach the daemon fetch path.
What the fix does
The patch introduces two security checks. First, in `hover.content.ts`, the `shouldHandleHoverTriggerEvent` function rejects any mouseover event where `isTrusted` is `false`, blocking synthetic events dispatched by page scripts [patch_id=424357]. Second, in `hover-controller.ts`, the new `isHoverSummarizeUrlAllowed` function validates that the target URL uses only `http:` or `https:` protocols and that the hostname is not a private IPv4 address (10.x, 127.x, 172.16-31.x, 192.168.x, 169.254.x, 0.x), a private IPv6 address (loopback, link-local, unique-local), or a local hostname (`localhost`, `*.localhost`, `*.local`). URLs failing this check are rejected before the daemon fetch path is reached, preventing SSRF to internal resources [CWE-918][CWE-940].
Preconditions
- authVictim must have the Summarize extension installed and authenticated with the daemon (stored tokens present).
- inputAttacker must craft a web page with anchor elements pointing to private/local URLs and script that dispatches synthetic mouseover events.
- networkVictim must visit the attacker-controlled page in a browser where the extension is active.
Reproduction
1. Install the Summarize extension (version prior to 0.15.1) and authenticate it with the daemon so that stored tokens are available. 2. Create an HTML page containing an `<a href="http://169.254.169.254/latest/meta-data/">hover me</a>` element. 3. Add a script that programmatically dispatches a `mouseover` event (with `isTrusted: false`) on that anchor. 4. Host the page on any public web server and have the victim visit it. 5. Observe that the extension sends an authenticated daemon request to the internal metadata endpoint, exfiltrating sensitive data.
Generated on May 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/steipete/summarize/commit/ecbb2c414255aa480a15d0d8b205224c14cfdbcbnvdPatch
- github.com/steipete/summarize/pull/218nvdExploitIssue TrackingPatch
- www.vulncheck.com/advisories/summarize-unauthorized-daemon-request-via-untrusted-eventsnvdThird Party Advisory
- github.com/steipete/summarize/releases/tag/v0.15.2nvdRelease Notes
News mentions
0No linked articles in our index yet.