Skip to content

Commit c084f3d

Browse files
authored
Merge pull request #244 from ezzak/improved_coverage
Coverage improvements
2 parents fa6b6f5 + 430615a commit c084f3d

File tree

7 files changed

+160
-40
lines changed

7 files changed

+160
-40
lines changed

config/v2/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"webRequest",
6060
"https://*.privacy-auditability.cloudflare.com/",
6161
"https://static.xx.fbcdn.net/",
62+
"https://static.cdninstagram.com/",
6263
"*://*.messenger.com/*",
6364
"*://*.facebook.com/*",
6465
"*://*.instagram.com/*",

config/v3/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"host_permissions": [
6161
"https://*.privacy-auditability.cloudflare.com/",
6262
"https://static.xx.fbcdn.net/",
63+
"https://static.cdninstagram.com/",
6364
"*://*.messenger.com/*",
6465
"*://*.facebook.com/*",
6566
"*://*.instagram.com/*",

src/js/background.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
updateContentScriptState,
1414
} from './background/tab_state_tracker/tabStateTracker';
1515
import setupCSPListener from './background/setupCSPListener';
16-
import setupNoCacheListeners from './background/setupNoCacheListeners';
16+
import setUpWebRequestsListener from './background/setUpWebRequestsListener';
1717
import {validateMetaCompanyManifest} from './background/validateMetaCompanyManifest';
1818
import {validateManifest} from './background/validateManifest';
1919
import isFbMsgrOrIgOrigin from './shared/isFbMsgrOrIgOrigin';
@@ -38,7 +38,10 @@ type Manifest = {
3838
leaves: Array<string>;
3939
};
4040

41-
function logReceivedMessage(message: MessagePayload): void {
41+
function logReceivedMessage(
42+
message: MessagePayload,
43+
sender: chrome.runtime.MessageSender,
44+
): void {
4245
let logger = console.log;
4346
switch (message.type) {
4447
case MESSAGE_TYPE.UPDATE_STATE:
@@ -52,15 +55,15 @@ function logReceivedMessage(message: MessagePayload): void {
5255
logger = console.debug;
5356
break;
5457
}
55-
logger?.('background, handleMessages', message);
58+
logger?.(`handleMessages from tab:${sender.tab.id}`, message);
5659
}
5760

5861
function handleMessages(
5962
message: MessagePayload,
6063
sender: chrome.runtime.MessageSender,
6164
sendResponse: (_: MessageResponse) => void,
6265
): void | boolean {
63-
logReceivedMessage(message);
66+
logReceivedMessage(message, sender);
6467
if (message.type == MESSAGE_TYPE.LOAD_MANIFEST) {
6568
// validate manifest
6669
if (isFbMsgrOrIgOrigin(message.origin)) {
@@ -207,7 +210,7 @@ function handleMessages(
207210
chrome.runtime.onMessage.addListener(handleMessages);
208211

209212
setupCSPListener(CSP_HEADERS, CSP_REPORT_HEADERS);
210-
setupNoCacheListeners(CACHED_SCRIPTS_URLS);
213+
setUpWebRequestsListener(CACHED_SCRIPTS_URLS);
211214

212215
// Emulate PageActions
213216
chrome.runtime.onInstalled.addListener(() => {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
const isMetaInitiatedResponse = (
9+
response: chrome.webRequest.WebResponseCacheDetails,
10+
) =>
11+
[
12+
'https://www.facebook.com',
13+
'https://www.messenger.com',
14+
'https://www.instagram.com',
15+
'https://web.whatsapp.com',
16+
].some(v => response.initiator.includes(v));
17+
18+
function checkResponseMIMEType(
19+
response: chrome.webRequest.WebResponseCacheDetails,
20+
): void {
21+
// Sniffable MIME types are a violation
22+
if (
23+
response.responseHeaders.find(header =>
24+
header.name.includes('x-content-type-options'),
25+
)?.value !== 'nosniff'
26+
) {
27+
chrome.tabs.sendMessage(
28+
response.tabId,
29+
{
30+
greeting: 'sniffableMimeTypeResource',
31+
src: response.url,
32+
},
33+
{frameId: response.frameId},
34+
);
35+
}
36+
}
37+
38+
export default function setUpWebRequestsListener(
39+
cachedScriptsUrls: Map<number, Set<string>>,
40+
): void {
41+
chrome.webRequest.onResponseStarted.addListener(
42+
response => {
43+
if (response.tabId === -1) {
44+
if (!isMetaInitiatedResponse(response)) {
45+
return;
46+
}
47+
checkResponseMIMEType(response);
48+
49+
// Potential `importScripts` call from Shared or Service Worker
50+
if (
51+
!response.url.startsWith('chrome-extension://') &&
52+
!response.url.startsWith('moz-extension://') &&
53+
response.type === 'script'
54+
) {
55+
const origin = response.initiator;
56+
57+
// Send to all tabs of this origin
58+
chrome.tabs.query({url: `${origin}/*`}, tabs => {
59+
tabs.forEach(tab => {
60+
chrome.tabs.sendMessage(
61+
tab.id,
62+
{
63+
greeting: 'checkIfScriptWasProcessed',
64+
response,
65+
},
66+
// Send this to the topframe since child frames
67+
// might have a different origin
68+
{frameId: 0},
69+
);
70+
});
71+
});
72+
}
73+
return;
74+
}
75+
76+
// Detect uncached responses
77+
if (
78+
response.type === 'xmlhttprequest' &&
79+
cachedScriptsUrls.get(response.tabId)?.has(response.url)
80+
) {
81+
if (!response.fromCache) {
82+
chrome.tabs.sendMessage(
83+
response.tabId,
84+
{
85+
greeting: 'nocacheHeaderFound',
86+
uncachedUrl: response.url,
87+
},
88+
{frameId: response.frameId},
89+
);
90+
}
91+
cachedScriptsUrls.get(response.tabId)?.delete(response.url);
92+
return;
93+
}
94+
95+
if (response.type === 'script') {
96+
checkResponseMIMEType(response);
97+
/*
98+
* Scripts could be from main thread or dedicates WebWorkers.
99+
* Content scripts can't detect scripts from Workers so we need
100+
* to send them back to content script for verification.
101+
* */
102+
chrome.tabs.sendMessage(
103+
response.tabId,
104+
{
105+
greeting: 'checkIfScriptWasProcessed',
106+
response,
107+
},
108+
{frameId: response.frameId},
109+
);
110+
return;
111+
}
112+
},
113+
{urls: ['<all_urls>']},
114+
['responseHeaders'],
115+
);
116+
}

src/js/background/setupNoCacheListeners.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/js/contentUtils.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const INLINE_SCRIPTS: Array<Map<string, string>> = [];
3636

3737
// Map<version, Array<ScriptDetails>>
3838
export const FOUND_SCRIPTS = new Map<string, Array<ScriptDetails>>([['', []]]);
39+
const ALL_FOUND_SCRIPT_TAGS = new Set();
3940

4041
type ScriptDetailsWithSrc = {
4142
otherType: string;
@@ -204,6 +205,7 @@ export function storeFoundJS(scriptNodeMaybe: HTMLScriptElement): void {
204205
src: scriptNodeMaybe.src,
205206
otherType, // TODO: read from DOM when available
206207
};
208+
ALL_FOUND_SCRIPT_TAGS.add(scriptNodeMaybe.src);
207209
} else {
208210
// no src, access innerHTML for the code
209211
const hashLookupAttribute =
@@ -368,16 +370,18 @@ export const processFoundJS = async (version: string): Promise<void> => {
368370
if (response.type === 'EXTENSION') {
369371
updateCurrentState(STATES.RISK);
370372
} else {
371-
updateCurrentState(STATES.INVALID, 'Invalid ScriptDetailsWithSrc');
373+
updateCurrentState(
374+
STATES.INVALID,
375+
`Invalid ScriptDetailsWithSrc ${script.src}`,
376+
);
372377
}
373378
}
374379
sendMessageToBackground({
375380
type: MESSAGE_TYPE.DEBUG,
376381
log:
377-
'processed JS with SRC, ' +
378-
script.src +
379-
',response is ' +
382+
'processed JS with SRC response is ' +
380383
JSON.stringify(response).substring(0, 500),
384+
src: script.src,
381385
});
382386
});
383387
} else {
@@ -471,13 +475,37 @@ export function startFor(
471475
}
472476
}
473477

474-
chrome.runtime.onMessage.addListener(function (request) {
478+
chrome.runtime.onMessage.addListener(request => {
475479
if (request.greeting === 'downloadSource' && DOWNLOAD_JS_ENABLED) {
476480
downloadJSArchive(SOURCE_SCRIPTS, INLINE_SCRIPTS);
477481
} else if (request.greeting === 'nocacheHeaderFound') {
478482
updateCurrentState(
479483
STATES.INVALID,
480484
`Detected uncached script ${request.uncachedUrl}`,
481485
);
486+
} else if (request.greeting === 'checkIfScriptWasProcessed') {
487+
if (!ALL_FOUND_SCRIPT_TAGS.has(request.response.url)) {
488+
if (
489+
'serviceWorker' in navigator &&
490+
navigator.serviceWorker.controller?.scriptURL === request.response.url
491+
) {
492+
return;
493+
}
494+
sendMessageToBackground({
495+
type: MESSAGE_TYPE.DEBUG,
496+
log: `Tab is processing ${request.response.url}`,
497+
});
498+
ALL_FOUND_SCRIPT_TAGS.add(request.response.url);
499+
FOUND_SCRIPTS.get(FOUND_SCRIPTS.keys().next().value).push({
500+
src: request.response.url,
501+
otherType: currentFilterType,
502+
});
503+
updateCurrentState(STATES.PROCESSING);
504+
}
505+
} else if (request.greeting === 'sniffableMimeTypeResource') {
506+
updateCurrentState(
507+
STATES.INVALID,
508+
`Sniffable MIME type resource: ${request.src}`,
509+
);
482510
}
483511
});

src/js/shared/MessageTypes.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type MessagePayload =
2727
| {
2828
type: typeof MESSAGE_TYPE.DEBUG;
2929
log: string;
30+
src?: string;
3031
}
3132
| {
3233
type: typeof MESSAGE_TYPE.STATE_UPDATED;

0 commit comments

Comments
 (0)