Skip to content

Commit 084b47c

Browse files
mikewestDevtools-frontend LUCI CQ
authored andcommitted
[Unencoded-Digest] Handle issues delivered to devtools.
This CL follows up on the issues delivered from the network service to devtools in [1], adding issue descriptions and rendering infrastructure so users can see what went wrong. [1]: https://chromium-review.googlesource.com/c/chromium/src/+/6771890 Bug: 431760410 Change-Id: I06e4a8f91705dc30d401024dbdca398bdd9ea75e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6818233 Reviewed-by: Danil Somsikov <[email protected]> Commit-Queue: Mike West <[email protected]> Reviewed-by: Simon Zünd <[email protected]>
1 parent 9d7493e commit 084b47c

10 files changed

+213
-0
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,10 @@ grd_files_bundled_sources = [
575575
"front_end/models/issues_manager/descriptions/stylesheetLateImport.md",
576576
"front_end/models/issues_manager/descriptions/stylesheetRequestFailed.md",
577577
"front_end/models/issues_manager/descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md",
578+
"front_end/models/issues_manager/descriptions/unencodedDigestIncorrectDigestLength.md",
579+
"front_end/models/issues_manager/descriptions/unencodedDigestIncorrectDigestType.md",
580+
"front_end/models/issues_manager/descriptions/unencodedDigestMalformedDictionary.md",
581+
"front_end/models/issues_manager/descriptions/unencodedDigestUnknownAlgorithm.md",
578582
"front_end/models/issues_manager/descriptions/userReidentificationBlocked.md",
579583
"front_end/models/issues_manager/issues_manager.js",
580584
"front_end/models/javascript_metadata/javascript_metadata.js",
@@ -1084,6 +1088,7 @@ grd_files_unbundled_sources = [
10841088
"front_end/models/issues_manager/SharedDictionaryIssue.js",
10851089
"front_end/models/issues_manager/SourceFrameIssuesManager.js",
10861090
"front_end/models/issues_manager/StylesheetLoadingIssue.js",
1091+
"front_end/models/issues_manager/UnencodedDigestIssue.js",
10871092
"front_end/models/issues_manager/UserReidentificationIssue.js",
10881093
"front_end/models/javascript_metadata/JavaScriptMetadata.js",
10891094
"front_end/models/javascript_metadata/NativeFunctions.js",

front_end/models/issues_manager/BUILD.gn

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ devtools_module("issues_manager") {
4242
"SharedDictionaryIssue.ts",
4343
"SourceFrameIssuesManager.ts",
4444
"StylesheetLoadingIssue.ts",
45+
"UnencodedDigestIssue.ts",
4546
"UserReidentificationIssue.ts",
4647
]
4748

@@ -242,6 +243,10 @@ devtools_issue_description_files = [
242243
"placeholderDescriptionForInvisibleIssues.md",
243244
"fetchingPartitionedBlobURL.md",
244245
"navigatingPartitionedBlobURL.md",
246+
"unencodedDigestIncorrectDigestLength.md",
247+
"unencodedDigestIncorrectDigestType.md",
248+
"unencodedDigestMalformedDictionary.md",
249+
"unencodedDigestUnknownAlgorithm.md",
245250
"userReidentificationBlocked.md",
246251
]
247252

@@ -303,6 +308,7 @@ ts_library("unittests") {
303308
"SRIMessageSignatureIssue.test.ts",
304309
"SharedDictionaryIssue.test.ts",
305310
"StylesheetLoadingIssue.test.ts",
311+
"UnencodedDigestIssue.test.ts",
306312
]
307313

308314
deps = [

front_end/models/issues_manager/IssuesManager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {SharedDictionaryIssue} from './SharedDictionaryIssue.js';
3232
import {SourceFrameIssuesManager} from './SourceFrameIssuesManager.js';
3333
import {SRIMessageSignatureIssue} from './SRIMessageSignatureIssue.js';
3434
import {StylesheetLoadingIssue} from './StylesheetLoadingIssue.js';
35+
import {UnencodedDigestIssue} from './UnencodedDigestIssue.js';
3536
import {UserReidentificationIssue} from './UserReidentificationIssue.js';
3637

3738
export {Events} from './IssuesManagerEvents.js';
@@ -140,6 +141,10 @@ const issueCodeHandlers = new Map<
140141
Protocol.Audits.InspectorIssueCode.SRIMessageSignatureIssue,
141142
SRIMessageSignatureIssue.fromInspectorIssue,
142143
],
144+
[
145+
Protocol.Audits.InspectorIssueCode.UnencodedDigestIssue,
146+
UnencodedDigestIssue.fromInspectorIssue,
147+
],
143148
[
144149
Protocol.Audits.InspectorIssueCode.UserReidentificationIssue,
145150
UserReidentificationIssue.fromInspectorIssue,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import type * as SDK from '../../core/sdk/sdk.js';
6+
import * as Protocol from '../../generated/protocol.js';
7+
import {describeWithLocale} from '../../testing/EnvironmentHelpers.js';
8+
import {MockIssuesModel} from '../../testing/MockIssuesModel.js';
9+
import * as IssuesManager from '../issues_manager/issues_manager.js';
10+
11+
function createProtocolIssue(unencodedDigestIssueDetails: Protocol.Audits.UnencodedDigestIssueDetails):
12+
Protocol.Audits.InspectorIssue {
13+
return {
14+
code: Protocol.Audits.InspectorIssueCode.UnencodedDigestIssue,
15+
details: {unencodedDigestIssueDetails},
16+
};
17+
}
18+
19+
describeWithLocale('UnencodedDigestIssue', () => {
20+
const mockModel = new MockIssuesModel([]) as unknown as SDK.IssuesModel.IssuesModel;
21+
22+
it('can be created for various error reasons', () => {
23+
const errorReasons = [
24+
Protocol.Audits.UnencodedDigestError.IncorrectDigestLength,
25+
Protocol.Audits.UnencodedDigestError.IncorrectDigestType,
26+
Protocol.Audits.UnencodedDigestError.MalformedDictionary,
27+
Protocol.Audits.UnencodedDigestError.UnknownAlgorithm,
28+
];
29+
for (const errorReason of errorReasons) {
30+
const issueDetails = {
31+
error: errorReason,
32+
request: {
33+
requestId: 'test-request-id' as Protocol.Network.RequestId,
34+
url: 'https://example.com/',
35+
},
36+
};
37+
const issue = createProtocolIssue(issueDetails);
38+
const unencodedDigestIssues =
39+
IssuesManager.UnencodedDigestIssue.UnencodedDigestIssue.fromInspectorIssue(mockModel, issue);
40+
assert.lengthOf(unencodedDigestIssues, 1);
41+
const unencodedDigestIssue = unencodedDigestIssues[0];
42+
43+
assert.strictEqual(unencodedDigestIssue.getCategory(), IssuesManager.Issue.IssueCategory.OTHER);
44+
assert.deepEqual(unencodedDigestIssue.details(), issueDetails);
45+
assert.strictEqual(unencodedDigestIssue.getKind(), IssuesManager.Issue.IssueKind.PAGE_ERROR);
46+
assert.isNotNull(unencodedDigestIssue.getDescription());
47+
}
48+
});
49+
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as i18n from '../../core/i18n/i18n.js';
6+
import type * as SDK from '../../core/sdk/sdk.js';
7+
import * as Protocol from '../../generated/protocol.js';
8+
9+
import {Issue, IssueCategory, IssueKind} from './Issue.js';
10+
import {
11+
type LazyMarkdownIssueDescription,
12+
type MarkdownIssueDescription,
13+
resolveLazyDescription,
14+
} from './MarkdownIssueDescription.js';
15+
16+
const UIStrings = {
17+
/**
18+
*@description Title for HTTP Unencoded Digest specification url
19+
*/
20+
unencodedDigestHeader: 'HTTP Unencoded Digest specification',
21+
/**
22+
*@description Title for the URL of the integration of unencoded-digest and SRI.
23+
*/
24+
integrityIntegration: 'Server-Initiated Integrity Checks',
25+
} as const;
26+
const str_ = i18n.i18n.registerUIStrings('models/issues_manager/UnencodedDigestIssue.ts', UIStrings);
27+
const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);
28+
29+
export class UnencodedDigestIssue extends Issue<string> {
30+
readonly #issueDetails: Protocol.Audits.UnencodedDigestIssueDetails;
31+
32+
constructor(issueDetails: Protocol.Audits.UnencodedDigestIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel) {
33+
super(
34+
{
35+
code: `${Protocol.Audits.InspectorIssueCode.UnencodedDigestIssue}::${issueDetails.error}`,
36+
umaCode: `${Protocol.Audits.InspectorIssueCode.UnencodedDigestIssue}::${issueDetails.error}`,
37+
},
38+
issuesModel);
39+
this.#issueDetails = issueDetails;
40+
}
41+
42+
details(): Protocol.Audits.UnencodedDigestIssueDetails {
43+
return this.#issueDetails;
44+
}
45+
46+
override primaryKey(): string {
47+
return JSON.stringify(this.details());
48+
}
49+
50+
override getDescription(): MarkdownIssueDescription|null {
51+
const description: LazyMarkdownIssueDescription = {
52+
file: `unencodedDigest${this.details().error}.md`,
53+
links: [
54+
{
55+
link: 'https://www.ietf.org/archive/id/draft-ietf-httpbis-unencoded-digest-01.html',
56+
linkTitle: i18nLazyString(UIStrings.unencodedDigestHeader),
57+
},
58+
{
59+
link: 'https://wicg.github.io/signature-based-sri/#unencoded-digest-validation',
60+
linkTitle: i18nLazyString(UIStrings.integrityIntegration),
61+
},
62+
],
63+
};
64+
return resolveLazyDescription(description);
65+
}
66+
67+
override getCategory(): IssueCategory {
68+
return IssueCategory.OTHER;
69+
}
70+
71+
override getKind(): IssueKind {
72+
return IssueKind.PAGE_ERROR;
73+
}
74+
75+
override requests(): Iterable<Protocol.Audits.AffectedRequest> {
76+
return this.details().request ? [this.details().request] : [];
77+
}
78+
79+
static fromInspectorIssue(issuesModel: SDK.IssuesModel.IssuesModel, inspectorIssue: Protocol.Audits.InspectorIssue):
80+
UnencodedDigestIssue[] {
81+
const details = inspectorIssue.details.unencodedDigestIssueDetails;
82+
if (!details) {
83+
console.warn('Unencoded-Digest issue without details received.');
84+
return [];
85+
}
86+
return [new UnencodedDigestIssue(details, issuesModel)];
87+
}
88+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# An `unencoded-digest` header contains a digest with an incorrect length.
2+
3+
[`unencoded-digest`](unencodedDigestHeader) headers contain SHA2 digests of a
4+
response's body. The digest delivered with this response is not the correct
5+
length for the algorithm specified. SHA-256 digests should be 256 bits long,
6+
SHA-512 digests 512 bits long. For example, if the body was "Hello, world.", the
7+
following might be an appropriate header:
8+
9+
```
10+
Unencoded-Digest: sha-256=:+MO/YqmqPm/BYZwlDkir51GTc9Pt9BvmLrXcRRma8u8=:,
11+
sha-512=:S7LmUoguRQsq3IHIZ0Xhm5jjCDqH6uUQbumuj5CnrIFDk+RyBW/dWuqzEiV4mPaB:
12+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# An `unencoded-digest` header contains a digest with an invalid type.
2+
3+
[`unencoded-digest`](unencodedDigestHeader) headers must be formatted as a
4+
[Dictionary](sfDictionary), wherein each member's label specifies the hashing
5+
algorithm, and the value is a [Byte Sequence](sfByteSequence) containing the
6+
digest. The digest delivered with this response did not deliver the digest as a
7+
byte sequence.
8+
9+
For example, if the body was "Hello, world.":
10+
11+
```
12+
// Correctly formatted header:
13+
Unencoded-Digest: sha-256=:+MO/YqmqPm/BYZwlDkir51GTc9Pt9BvmLrXcRRma8u8=:
14+
15+
// Incorrectly formatted header (quotes rather than colons):
16+
Unencoded-Digest: sha-256="+MO/YqmqPm/BYZwlDkir51GTc9Pt9BvmLrXcRRma8u8="
17+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# An `unencoded-digest` header cannot be parsed.
2+
3+
[`unencoded-digest`](unencodedDigestHeader) headers must be formatted as a
4+
[Dictionary](sfDictionary), wherein each member's label specifies the hashing
5+
algorithm, and the value is a [Byte Sequence](sfByteSequence) containing the
6+
digest. The digest delivered with this response did not format the response
7+
correctly.
8+
9+
For example, if the body was "Hello, world.", the following would be a correctly
10+
formatted header:
11+
12+
```
13+
Unencoded-Digest: sha-256=:+MO/YqmqPm/BYZwlDkir51GTc9Pt9BvmLrXcRRma8u8=:
14+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# An `unencoded-digest` header contains a digest with an invalid algorithm.
2+
3+
[`unencoded-digest`](unencodedDigestHeader) headers support only two hashing
4+
algorithms, SHA-256, and SHA-512. The digests delivered with this response
5+
specified an unsupported algorithm:
6+
7+
For example, if the body was "Hello, world.", the following would be an
8+
appropriate header:
9+
10+
```
11+
Unencoded-Digest: sha-256=:+MO/YqmqPm/BYZwlDkir51GTc9Pt9BvmLrXcRRma8u8=:,
12+
sha-512=:S7LmUoguRQsq3IHIZ0Xhm5jjCDqH6uUQbumuj5CnrIFDk+RyBW/dWuqzEiV4mPaB:
13+
```
14+
15+
Note also that the labels must be lower-case.

front_end/models/issues_manager/issues_manager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import * as SharedDictionaryIssue from './SharedDictionaryIssue.js';
3131
import * as SourceFrameIssuesManager from './SourceFrameIssuesManager.js';
3232
import * as SRIMessageSignatureIssue from './SRIMessageSignatureIssue.js';
3333
import * as StylesheetLoadingIssue from './StylesheetLoadingIssue.js';
34+
import * as UnencodedDigestIssue from './UnencodedDigestIssue.js';
3435

3536
export {
3637
AttributionReportingIssue,
@@ -62,4 +63,5 @@ export {
6263
SourceFrameIssuesManager,
6364
SRIMessageSignatureIssue,
6465
StylesheetLoadingIssue,
66+
UnencodedDigestIssue,
6567
};

0 commit comments

Comments
 (0)