Chrome · DevTools
CVE-2026-17927
Logic Error in DevTools
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
front_end/panels/common/ExtensionServer.test.ts
Patch
From 921932ce2b7c0d40c8843f23f07f94e03155f2eb Mon Sep 17 00:00:00 2001
From: Danil Somsikov <dsv@chromium.org>
Date: Wed, 24 Jun 2026 12:24:31 -0700
Subject: [PATCH] Block access to resources from blocked targets.
This change updates the ExtensionServer to prevent extensions from accessing network requests and resources if the associated target's inspected URL is blocked, even if the resource or request URL itself is on an allowed domain. This is applied to HAR entries, resource content fetching, and network request finished events. New tests are added to verify these security constraints.
Bug: 513754837
Change-Id: Iaced3448d56b24bd7e55268569303257cd36daf9
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7985744
Auto-Submit: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
Commit-Queue: Danil Somsikov <dsv@chromium.org>
---
diff --git a/front_end/panels/common/ExtensionServer.test.ts b/front_end/panels/common/ExtensionServer.test.ts
index c26f1e4..6606bac 100644
--- a/front_end/panels/common/ExtensionServer.test.ts
+++ b/front_end/panels/common/ExtensionServer.test.ts
@@ -93,11 +93,14 @@
const target = createTarget({url});
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
assert.isNotNull(resourceTreeModel);
- await resourceTreeModel.once(SDK.ResourceTreeModel.Events.CachedResourcesLoaded);
+ if (!resourceTreeModel.cachedResourcesLoaded()) {
+ await resourceTreeModel.once(SDK.ResourceTreeModel.Events.CachedResourcesLoaded);
+ }
target.setInspectedURL(url);
- resourceTreeModel.mainFrame?.addResource(new SDK.Resource.Resource(
- resourceTreeModel, null, url, url, null, null, Common.ResourceType.resourceTypes.Document, 'application/text',
- null, null));
+ const mainFrame = getMainFrame(target, {url});
+ mainFrame.addResource(new SDK.Resource.Resource(resourceTreeModel, null, url, url, null, null,
+ Common.ResourceType.resourceTypes.Document, 'application/text',
+ null, null));
return target;
});
@@ -148,8 +151,8 @@
// create a mock uiSourceCode for the sourceMap script
const scriptUrl = urlString`https://example.com/foo.js.map/foo.js`;
project.addUISourceCode(
- new Workspace.UISourceCode.UISourceCode(
- project, scriptUrl, Common.ResourceType.resourceTypes.SourceMapScript),
+ new Workspace.UISourceCode.UISourceCode(project, scriptUrl,
+ Common.ResourceType.resourceTypes.SourceMapScript),
);
const uiSourceCode = project.uiSourceCodeForURL(scriptUrl);
assert.exists(uiSourceCode);
@@ -195,11 +198,14 @@
const stubScript = sinon.createStubInstance(SDK.Script.Script);
// @ts-expect-error
stubScript.buildId = 'my-build-id';
+ stubScript.target.returns(target);
+ stubScript.contentURL.returns(urlString`http://example.com/index.js`);
+ stubScript.hasSourceURL = false;
sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(), 'scriptsForUISourceCode')
.returns([stubScript]);
project.addUISourceCode(
- new Workspace.UISourceCode.UISourceCode(
- project, urlString`http://example.com/index.js`, Common.ResourceType.resourceTypes.Script),
+ new Workspace.UISourceCode.UISourceCode(project, urlString`http://example.com/index.js`,
+ Common.ResourceType.resourceTypes.Script),
);
const resources =
@@ -681,9 +687,32 @@
}
});
- async function setUpFrame(
- name: string, url: Platform.DevToolsPath.UrlString, parentFrame?: SDK.ResourceTreeModel.ResourceTreeFrame,
- executionContextOrigin?: Platform.DevToolsPath.UrlString) {
+ it('does not include requests from blocked targets in the HAR entries even if request URL is allowed', async () => {
+ Logs.NetworkLog.NetworkLog.instance();
+
+ const parentFrameUrl = allowedUrl;
+ const childFrameUrl = blockedUrl;
+ const parentFrame = await setUpFrame('parent', parentFrameUrl);
+ const childFrame = await setUpFrame('child', childFrameUrl, parentFrame);
+
+ const childTarget = childFrame.resourceTreeModel()?.target();
+ assert.exists(childTarget);
+ childTarget.setInspectedURL(blockedUrl); // Explicitly set to blocked URL
+
+ const networkManager = childTarget.model(SDK.NetworkManager.NetworkManager);
+ assert.exists(networkManager);
+
+ const frameId = 'child-frame-id' as Protocol.Page.FrameId;
+ const requestUrl = urlString`${allowedUrl}?fromBlockedTarget`;
+ createRequest(networkManager, frameId, 'request-from-blocked-target' as Protocol.Network.RequestId, requestUrl);
+
+ const result = await context.chrome.devtools!.network.getHAR() as TestHARLog;
+ assert.notExists(result.entries.find(e => e.request.url === requestUrl));
+ });
+
+ async function setUpFrame(name: string, url: Platform.DevToolsPath.UrlString,
+ parentFrame?: SDK.ResourceTreeModel.ResourceTreeFrame,
+ executionContextOrigin?: Platform.DevToolsPath.UrlString) {
const parentTarget = parentFrame?.resourceTreeModel()?.target();
const target = createTarget({id: `${name}-target-id` as Protocol.Target.TargetID, parentTarget});
const frame = parentFrame ? await addChildFrame(target, {url}) : getMainFrame(target, {url});
@@ -712,8 +741,8 @@
await setUpFrame('child', childFrameUrl, parentFrame);
const result = await new Promise<{result: unknown, error?: {details: unknown[]}}>(
- r => context.chrome.devtools?.inspectedWindow.eval(
- '4', {frameURL: childFrameUrl}, (result, error) => r({result, error})));
+ r => context.chrome.devtools?.inspectedWindow.eval('4', {frameURL: childFrameUrl},
+ (result, error) => r({result, error})));
assert.deepEqual(result.error?.details, ['Permission denied']);
});
@@ -849,15 +878,15 @@
await setUpFrame('child', childFrameUrl, parentFrame, childExeContextOrigin);
const result = await new Promise<{result: unknown, error?: {details: unknown[]}}>(
- r => context.chrome.devtools?.inspectedWindow.eval(
- '4', {frameURL: childFrameUrl}, (result, error) => r({result, error})));
+ r => context.chrome.devtools?.inspectedWindow.eval('4', {frameURL: childFrameUrl},
+ (result, error) => r({result, error})));
assert.deepEqual(result.error?.details, ['Permission denied']);
});
- async function createUISourceCode(
- project: Bindings.ContentProviderBasedProject.ContentProviderBasedProject, url: Platform.DevToolsPath.UrlString,
- contentType = Common.ResourceType.resourceTypes.Document) {
+ async function createUISourceCode(project: Bindings.ContentProviderBasedProject.ContentProviderBasedProject,
+ url: Platform.DevToolsPath.UrlString,
+ contentType = Common.ResourceType.resourceTypes.Document) {
const mimeType = 'text/html';
const dataProvider = () =>
Promise.resolve(new TextUtils.ContentData.ContentData('content', /* isBase64 */ false, mimeType));
@@ -872,8 +901,8 @@
target.setInspectedURL(allowedUrl);
sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
- .returns(sinon.createStubInstance(
- Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, {scriptsForUISourceCode: []}));
+ .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
+ {scriptsForUISourceCode: []}));
const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
Workspace.Workspace.WorkspaceImpl.instance(), target.id(), Workspace.Workspace.projectTypes.Network, '',
false /* isServiceProject */);
@@ -902,6 +931,37 @@
]);
});
+ it('blocks getting resource contents on allowed urls if target is blocked', async () => {
+ const parentTarget = createTarget({id: 'parent' as Protocol.Target.TargetID});
+ parentTarget.setInspectedURL(allowedUrl);
+
+ const blockedTarget = createTarget({id: 'blocked-target' as Protocol.Target.TargetID});
+ blockedTarget.setInspectedURL(blockedUrl);
+ sinon.stub(blockedTarget, 'inspectedURL').returns(blockedUrl);
+
+ sinon.stub(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding, 'instance')
+ .returns(sinon.createStubInstance(Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
+ {scriptsForUISourceCode: []}));
+
+ const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
+ Workspace.Workspace.WorkspaceImpl.instance(), blockedTarget.id(), Workspace.Workspace.projectTypes.Network, '',
+ false /* isServiceProject */);
+
+ Bindings.NetworkProject.NetworkProject.setTargetForProject(project, blockedTarget);
+
+ const uniqueAllowedUrl = urlString`${allowedUrl}?uniqueResourceTest`;
+
+ // Stub targetForUISourceCode to ensure it returns blockedTarget for our unique URL
+ const targetForUISourceCodeStub = sinon.stub(Bindings.NetworkProject.NetworkProject, 'targetForUISourceCode');
+ targetForUISourceCodeStub.returns(blockedTarget);
+
+ await createUISourceCode(project, uniqueAllowedUrl);
+
+ assert.exists(context.chrome.devtools);
+ const resources = await context.chrome.devtools!.inspectedWindow.getResources();
+ assert.notExists(resources.find(r => r.url === uniqueAllowedUrl));
+ });
+
it('allows arbitrary schemes in sourceURL comments, as long as the inspected target is allowed', async () => {
const target = createTarget({id: 'target' as Protocol.Target.TargetID});
target.setInspectedURL(allowedUrl);
@@ -928,10 +988,17 @@
assert.deepEqual(resources.map(r => r.url), [blockedUrl, allowedUrl]);
});
- function createRequest(
- networkManager: SDK.NetworkManager.NetworkManager, frameId: Protocol.Page.FrameId,
- requestId: Protocol.Network.RequestId, url: Platform.DevToolsPath.UrlString): void {
+ const requestToManager = new Map<SDK.NetworkRequest.NetworkRequest, SDK.NetworkManager.NetworkManager>();
+
+ function createRequest(networkManager: SDK.NetworkManager.NetworkManager, frameId: Protocol.Page.FrameId,
+ requestId: Protocol.Network.RequestId, url: Platform.DevToolsPath.UrlString): void {
+ if (!(SDK.NetworkManager.NetworkManager.forRequest as unknown as {isSinonProxy?: boolean}).isSinonProxy) {
+ requestToManager.clear();
+ sinon.stub(SDK.NetworkManager.NetworkManager, 'forRequest')
+ .callsFake(request => requestToManager.get(request) || null);
+ }
const request = SDK.NetworkRequest.NetworkRequest.create(requestId, url, url, frameId, null, null, undefined);
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page