CVE-2026-17899
Overview
Background
- `chrome.devtools.inspectedWindow.eval`
- a DevTools extension API that evaluates a JavaScript expression in the context of the inspected page.
- `ExtensionServer`
- the DevTools frontend component that services requests from DevTools extensions, including
eval. - `HostConfig`
- a configuration object the Chromium backend passes to the DevTools frontend describing enabled features and permissions.
- `--extensions-on-chrome-urls`
- a Chrome switch permitting extensions to operate on otherwise-restricted extension/chrome URLs.
Root Cause Analysis
4ad0d072cba9. An earlier analysis was withdrawn because it had been written against a commit that was not the security fix (48f965098f72).In the pre-fix front_end/panels/common/ExtensionServer.ts evaluate path, when the inspected frame’s URL used the chrome-extension: scheme and its origin differed from the requesting extension’s origin, the code only classified the target as Host.UserMetrics.ExtensionEvalTarget.OTHER_EXTENSION for metrics (ExtensionServer.ts:1617-1619) and then proceeded to evaluate (ExtensionServer.ts:1626 void context.evaluate(...)). The handler does run extension?.isAllowedOnTarget(...) checks earlier (ExtensionServer.ts:1571, :1607), but their implementation is not shown. Per the commit message and the new EvalRestrictedOnOtherExtension test (which expects E_FAILED only after the fix), those checks did not block a different extension’s chrome-extension: page, so pre-fix nothing in the shown code denied the OTHER_EXTENSION case and a DevTools extension could run script in another extension’s context — insufficient policy enforcement.
The fix (commit 4ad0d072…, devtools/devtools-frontend) adds, in the OTHER_EXTENSION branch, a guard that returns this.status.E_FAILED('Access to extension URLs is restricted; use --extensions-on-chrome-urls to enable.') unless Root.Runtime.hostConfig.extensionsOnChromeUrls?.enabled is true (added diff lines). The companion chromium/src CL (a68c336b…) supplies that state by setting response_dict.Set("extensionsOnChromeUrls", base::DictValue().Set("enabled", extensions::switches::AreExtensionsOnExtensionURLsAllowed())) in the host-config dictionary (added diff), and the frontend declares the new ExtensionsOnChromeUrls interface plus the extensionsOnChromeUrls HostConfig member in front_end/core/root/Runtime.ts (added diff). This tightens the path: cross-extension evaluation is now blocked by default and only allowed when the backend reports the switch is enabled. The SAME_EXTENSION and web-page cases are unchanged.
eval was only measured, never gated; the fix adds an authorization check keyed to the backend-reported extensionsOnChromeUrls permission so it is denied unless explicitly enabled.Attack Path
- Load a DevTools extension
An installed DevTools extension registers a
devtools_pageand uses theinspectedWindow.evalAPI. - Target another extension's page
It arranges for the inspected frame to be a different extension’s
chrome-extension:origin, hitting theOTHER_EXTENSIONbranch (ExtensionServer.ts:1617-1619). (inferred) - Evaluate without restriction
Pre-fix the frontend proceeds to
context.evaluate(...)(ExtensionServer.ts:1626), running the expression in the other extension’s context. - Privilege escalation Script executes with the victim extension’s origin/privileges. (inferred)
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
iffront_end/panels/common/ExtensionServer.ts |
modified |
Files Changed
front_end/core/root/Runtime.tsfront_end/panels/common/ExtensionServer.ts
Audit Directions
- Metrics vs enforcementTreat code that only classifies or records a sensitive case (e.g.
OTHER_EXTENSION) as a red flag that an authorization decision may be missing. - Frontend-trusted configVerify that DevTools frontend permission gates read a
HostConfigflag the browser actually sets, so an absent/stale flag fails closed. - Cross-origin evalAudit extension
eval/injection paths for a check that the target origin belongs to the requesting extension before evaluating.
Patch
diff --git a/front_end/core/root/Runtime.ts b/front_end/core/root/Runtime.ts
index cc64bd5..b68121a 100644
--- a/front_end/core/root/Runtime.ts
+++ b/front_end/core/root/Runtime.ts
@@ -600,6 +600,10 @@
enabled: boolean;
}
+interface ExtensionsOnChromeUrls {
+ enabled: boolean;
+}
+
interface DevToolsFlexibleLayout {
verticalDrawerEnabled: boolean;
}
@@ -702,6 +706,7 @@
devToolsAdsPanel: DevToolsAdsPanel,
devToolsUseGcaApi: UseGcaApi,
devToolsPlusButton: DevToolsPlusButton,
+ extensionsOnChromeUrls: ExtensionsOnChromeUrls,
}>;
/**
diff --git a/front_end/panels/common/ExtensionServer.ts b/front_end/panels/common/ExtensionServer.ts
index 3ba18b4..ec2342a 100644
--- a/front_end/panels/common/ExtensionServer.ts
+++ b/front_end/panels/common/ExtensionServer.ts
@@ -12,6 +12,7 @@
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as _ProtocolClient from '../../core/protocol_client/protocol_client.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
+import * as Root from '../../core/root/root.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import * as Bindings from '../../models/bindings/bindings.js';
@@ -1616,6 +1617,10 @@
targetType = Host.UserMetrics.ExtensionEvalTarget.SAME_EXTENSION;
} else {
targetType = Host.UserMetrics.ExtensionEvalTarget.OTHER_EXTENSION;
+ if (!Root.Runtime.hostConfig.extensionsOnChromeUrls?.enabled) {
+ return this.status.E_FAILED(
+ 'Access to extension URLs is restricted; use --extensions-on-chrome-urls to enable.');
+ }
}
}
Host.userMetrics.extensionEvalTarget(targetType);