Skip to content

Commit 09756ff

Browse files
ISE Webcopybara-github
authored andcommitted
No public description
PiperOrigin-RevId: 736938691
1 parent 12d5616 commit 09756ff

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {domParserParseFromString} from 'safevalues/dom';
8+
import {htmlSafeByReview} from 'safevalues/restricted/reviewed';
9+
import {
10+
isCallSampled,
11+
isReportingDisabled,
12+
ReportingOptions,
13+
sendBeaconPolyfill,
14+
TEST_ONLY,
15+
} from './reporting.js';
16+
17+
const NETWORK_REQUEST_REPORT_HEARTBEAT_RATE = 0.1;
18+
const NETWORK_REQUEST_REPORT_SAMPLING_RATE = 0.1;
19+
20+
declare interface NetworkRequestElementPayload {
21+
url: string;
22+
element: string;
23+
elementType: ElementType;
24+
type: NetworkRequestElementReportType;
25+
}
26+
27+
// LINT.IfChange
28+
enum NetworkRequestElementReportType {
29+
// An unknown report type.
30+
DEFAULT_UNKNOWN = 'DEFAULT_UNKNOWN',
31+
32+
// A report type that is triggered for a fraction of all function calls no
33+
// matter the contents of the input.
34+
HEARTBEAT = 'HEARTBEAT',
35+
36+
// A report type that is triggered if the markdown contains an element that
37+
// can initiate an external network request.
38+
ELEMENT_DETECTED = 'ELEMENT_DETECTED',
39+
}
40+
41+
enum ElementType {
42+
ELEMENT_TYPE_UNKNOWN = 'ELEMENT_TYPE_UNKNOWN',
43+
ELEMENT_TYPE_IMG = 'ELEMENT_TYPE_IMG',
44+
ELEMENT_TYPE_LINK = 'ELEMENT_TYPE_LINK',
45+
ELEMENT_TYPE_AUDIO = 'ELEMENT_TYPE_AUDIO',
46+
ELEMENT_TYPE_VIDEO = 'ELEMENT_TYPE_VIDEO',
47+
}
48+
// LINT.ThenChange(//depot/google3/java/com/google/security/csp/collector/securityreportservice/proto/network_request_element_reports.proto)
49+
50+
function reportNetworkRequestElement(
51+
options: ReportingOptions,
52+
url: string,
53+
elementType: ElementType,
54+
element: string,
55+
type: NetworkRequestElementReportType,
56+
) {
57+
let sendReport = undefined;
58+
if (TEST_ONLY.sendReport) {
59+
sendReport = TEST_ONLY.sendReport;
60+
} else if (
61+
typeof window !== 'undefined' &&
62+
window.navigator &&
63+
window.navigator.sendBeacon !== undefined
64+
) {
65+
sendReport = navigator.sendBeacon.bind(navigator);
66+
} else {
67+
sendReport = sendBeaconPolyfill;
68+
}
69+
const payload: NetworkRequestElementPayload = {
70+
'url': url,
71+
'element': element,
72+
'elementType': elementType,
73+
'type': type,
74+
};
75+
sendReport(
76+
'https://csp.withgoogle.com/csp/greport/' + options.reportingId,
77+
JSON.stringify(payload),
78+
);
79+
}
80+
81+
/**
82+
* Passes through the given HTML string unchanged, but send reports containing metadata about
83+
* whether the HTML contains elements that can trigger network requests to the security
84+
* collector.
85+
*/
86+
export function reportOnlyNetworkRequestElementPassthrough(
87+
s: string,
88+
options: ReportingOptions,
89+
): string {
90+
if (
91+
!isCallSampled(options, NETWORK_REQUEST_REPORT_SAMPLING_RATE) ||
92+
isReportingDisabled()
93+
) {
94+
return s;
95+
}
96+
// Check for heartbeat
97+
if (
98+
Math.random() <
99+
(options.heartbeatRate ?? NETWORK_REQUEST_REPORT_HEARTBEAT_RATE)
100+
) {
101+
reportNetworkRequestElement(
102+
options,
103+
'',
104+
ElementType.ELEMENT_TYPE_UNKNOWN,
105+
'',
106+
NetworkRequestElementReportType.HEARTBEAT,
107+
);
108+
}
109+
110+
const parser = new DOMParser();
111+
const doc = domParserParseFromString(
112+
parser,
113+
htmlSafeByReview(s, {
114+
justification:
115+
'Parsing just to check if the HTML contains network request elements. b/361350306',
116+
}),
117+
'text/html',
118+
);
119+
const elements = doc.querySelectorAll('img, a, audio, video');
120+
121+
elements.forEach((node) => {
122+
let elementType: ElementType = ElementType.ELEMENT_TYPE_UNKNOWN;
123+
let schema = '';
124+
125+
switch (node.tagName.toUpperCase()) {
126+
case 'IMG':
127+
elementType = ElementType.ELEMENT_TYPE_IMG;
128+
schema = node.getAttribute('src')?.split(':')[0] ?? '';
129+
break;
130+
case 'A':
131+
elementType = ElementType.ELEMENT_TYPE_LINK;
132+
schema = node.getAttribute('href')?.split(':')[0] ?? '';
133+
break;
134+
case 'AUDIO':
135+
elementType = ElementType.ELEMENT_TYPE_AUDIO;
136+
schema = node.getAttribute('src')?.split(':')[0] ?? '';
137+
break;
138+
case 'VIDEO':
139+
elementType = ElementType.ELEMENT_TYPE_VIDEO;
140+
schema = node.getAttribute('src')?.split(':')[0] ?? '';
141+
break;
142+
default:
143+
break;
144+
}
145+
146+
if (elementType) {
147+
reportNetworkRequestElement(
148+
options,
149+
schema,
150+
elementType,
151+
'', // Leaving the element field empty for now. Collecting the element type and URL schema should be enough.
152+
NetworkRequestElementReportType.ELEMENT_DETECTED,
153+
);
154+
}
155+
});
156+
return s;
157+
}

0 commit comments

Comments
 (0)