Skip to content

Commit 63d38db

Browse files
authored
Merge pull request #328 from aselbie/ast-parsing
Enable validation of service workers
2 parents 089967f + b0f9cfa commit 63d38db

File tree

8 files changed

+187
-8
lines changed

8 files changed

+187
-8
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"devDependencies": {
2222
"@rollup/plugin-eslint": "^9.0.5",
23+
"@rollup/plugin-node-resolve": "^15.2.3",
2324
"@rollup/plugin-typescript": "^11.1.5",
2425
"@types/chrome": "^0.0.248",
2526
"@types/jest": "^29.4.0",
@@ -33,5 +34,8 @@
3334
"ts-jest": "^29.0.0",
3435
"tslib": "^2.5.0",
3536
"typescript": "^5.2.2"
37+
},
38+
"dependencies": {
39+
"acorn": "^8.11.3"
3640
}
3741
}

rollup.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {Plugin, RollupOptions} from 'rollup';
1010
import cleanOnce from './build/rollup-plugin-clean-once';
1111
import eslintPlugin from '@rollup/plugin-eslint';
1212
import typescript from '@rollup/plugin-typescript';
13+
import nodeResolve from '@rollup/plugin-node-resolve';
1314
import prettierBuildStart from './build/rollup-plugin-prettier-build-start';
1415
import staticFiles from './build/rollup-plugin-static-files';
1516
import watch from './build/rollup-plugin-watch-additional';
@@ -45,7 +46,7 @@ const config: Array<RollupOptions> = contentScriptSteps.concat([
4546
file: `dist/${target}/background.js`,
4647
format: 'iife',
4748
})),
48-
plugins: [typescript(), prettierSrc(), eslint()],
49+
plugins: [typescript(), prettierSrc(), eslint(), nodeResolve()],
4950
},
5051
{
5152
input: 'src/js/popup.ts',
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
'use strict';
9+
10+
import {removeDynamicStrings} from '../background/removeDynamicStrings';
11+
12+
describe('removeDynamicStrings', () => {
13+
it('Handles different quote types', () => {
14+
expect(
15+
removeDynamicStrings(`const foo = /*BTDS*/'dynamic string';`),
16+
).toEqual(`const foo = /*BTDS*/'';`);
17+
expect(
18+
removeDynamicStrings(`const foo = /*BTDS*/"dynamic string";`),
19+
).toEqual(`const foo = /*BTDS*/"";`);
20+
});
21+
it('Handles empty strings', () => {
22+
expect(
23+
removeDynamicStrings(`const foo = /*BTDS*/'';`),
24+
).toEqual(`const foo = /*BTDS*/'';`);
25+
});
26+
it('Handles strings in different scenarios', () => {
27+
expect(removeDynamicStrings(`/*BTDS*/'dynamic string';`)).toEqual(
28+
`/*BTDS*/'';`,
29+
);
30+
expect(
31+
removeDynamicStrings(
32+
`/*BTDS*/'dynamic string' + /*BTDS*/'dynamic string';`,
33+
),
34+
).toEqual(`/*BTDS*/'' + /*BTDS*/'';`);
35+
expect(
36+
removeDynamicStrings(`const foo = JSON.parse(/*BTDS*/'dynamic string');`),
37+
).toEqual(`const foo = JSON.parse(/*BTDS*/'');`);
38+
expect(
39+
removeDynamicStrings("`before ${/*BTDS*/'dynamic string'} after`;"),
40+
).toEqual("`before ${/*BTDS*/''} after`;");
41+
});
42+
it('Handles multiple strings', () => {
43+
expect(
44+
removeDynamicStrings(
45+
`/*BTDS*/'dynamic string';/*BTDS*/'dynamic string';/*BTDS*/'dynamic string';`,
46+
),
47+
).toEqual(`/*BTDS*/'';/*BTDS*/'';/*BTDS*/'';`);
48+
});
49+
it('Handles strings across line breaks', () => {
50+
expect(
51+
removeDynamicStrings(`/*BTDS*/'dynamic \
52+
string';`),
53+
).toEqual(`/*BTDS*/'';`);
54+
});
55+
it('Throws if parsing fails', () => {
56+
expect(() =>
57+
removeDynamicStrings(`const foo = JSON.parse(/*BTDS*/'dynamic string';`),
58+
).toThrow();
59+
});
60+
});

src/js/background.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {Origin, STATES} from './config';
8+
import {DYNAMIC_STRING_MARKER, Origin, STATES} from './config';
99
import {MESSAGE_TYPE, ORIGIN_HOST, ORIGIN_TIMEOUT} from './config';
1010

1111
import {
@@ -16,6 +16,7 @@ import setupCSPListener from './background/setupCSPListener';
1616
import setUpWebRequestsListener from './background/setUpWebRequestsListener';
1717
import {validateMetaCompanyManifest} from './background/validateMetaCompanyManifest';
1818
import {validateSender} from './background/validateSender';
19+
import {removeDynamicStrings} from './background/removeDynamicStrings';
1920
import {MessagePayload, MessageResponse} from './shared/MessageTypes';
2021
import {setOrUpdateSetInMap} from './shared/nestedDataHelpers';
2122

@@ -149,6 +150,15 @@ function handleMessages(
149150
return;
150151
}
151152

153+
if (message.rawjs.includes(DYNAMIC_STRING_MARKER)) {
154+
try {
155+
message.rawjs = removeDynamicStrings(message.rawjs);
156+
} catch (e) {
157+
sendResponse({valid: false, reason: 'failed parsing AST'});
158+
return;
159+
}
160+
}
161+
152162
// fetch the src
153163
const encoder = new TextEncoder();
154164
const encodedJS = encoder.encode(message.rawjs);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
import {Parser} from 'acorn';
9+
import {DYNAMIC_STRING_MARKER} from '../config';
10+
11+
const markerLength = DYNAMIC_STRING_MARKER.length;
12+
13+
// This variable, and the parser, are reused because extending the
14+
// parser is surprisingly expensive. This is currently safe because
15+
// `removeDynamicStrings` is synchronous.
16+
let ranges = [0];
17+
function plugin(BaseParser: typeof Parser): typeof Parser {
18+
// @ts-ignore third party typing doesn't support extension well.
19+
return class extends BaseParser {
20+
parseLiteral(value: string) {
21+
// @ts-ignore third party typing doesn't support extension well.
22+
const node = super.parseLiteral(value);
23+
const before = this.input.substring(
24+
node.start - markerLength,
25+
node.start,
26+
);
27+
if (before === DYNAMIC_STRING_MARKER) {
28+
// This pushes the index directly after the opening quote and
29+
// before the closing quote so that only the contents of the
30+
// string are removed.
31+
ranges.push(node.start + 1, node.end - 1);
32+
}
33+
return node;
34+
}
35+
};
36+
}
37+
38+
const extendedParser = Parser.extend(plugin);
39+
40+
export function removeDynamicStrings(rawjs: string): string {
41+
ranges = [0];
42+
extendedParser.parse(rawjs, {ecmaVersion: 'latest'});
43+
44+
let result = '';
45+
for (let i = 0; i < ranges.length; i += 2) {
46+
result += rawjs.substring(ranges[i], ranges[i + 1]);
47+
}
48+
49+
return result;
50+
}

src/js/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,5 @@ export const DOWNLOAD_JS_ENABLED =
130130
'CompressionStream' in window && 'showSaveFilePicker' in window;
131131

132132
export const MANIFEST_TIMEOUT = 45000;
133+
134+
export const DYNAMIC_STRING_MARKER = '/*BTDS*/';

src/js/contentUtils.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const FOUND_MANIFEST_VERSIONS = new Set<string>();
6565
type ScriptDetailsWithSrc = {
6666
otherType: string;
6767
src: string;
68+
isServiceWorker?: boolean;
6869
};
6970
type ScriptDetailsRaw = {
7071
type: typeof MESSAGE_TYPE.RAW_JS;
@@ -355,6 +356,11 @@ async function processJSWithSrc(
355356
await alertBackgroundOfImminentFetch(script.src);
356357
const sourceResponse = await fetch(script.src, {
357358
method: 'GET',
359+
// When the browser fetches a service worker it adds this header.
360+
// If this is missing it will cause a cache miss, resulting in invalidation.
361+
headers: script.isServiceWorker
362+
? {'Service-Worker': 'script'}
363+
: undefined,
358364
});
359365
if (DOWNLOAD_JS_ENABLED) {
360366
const fileNameArr = script.src.split('/');
@@ -558,12 +564,6 @@ chrome.runtime.onMessage.addListener(request => {
558564
);
559565
} else if (request.greeting === 'checkIfScriptWasProcessed') {
560566
if (isUserLoggedIn && !ALL_FOUND_SCRIPT_TAGS.has(request.response.url)) {
561-
if (
562-
'serviceWorker' in navigator &&
563-
navigator.serviceWorker.controller?.scriptURL === request.response.url
564-
) {
565-
return;
566-
}
567567
const hostname = window.location.hostname;
568568
const resourceURL = new URL(request.response.url);
569569
if (resourceURL.hostname === hostname) {
@@ -593,6 +593,7 @@ chrome.runtime.onMessage.addListener(request => {
593593
uninitializedScripts.push({
594594
src: request.response.url,
595595
otherType: currentFilterType,
596+
isServiceWorker: hasVaryServiceWorkerHeader(request.response),
596597
});
597598
}
598599
updateCurrentState(STATES.PROCESSING);
@@ -614,3 +615,15 @@ chrome.runtime.onMessage.addListener(request => {
614615
}
615616
}
616617
});
618+
619+
function hasVaryServiceWorkerHeader(
620+
response: chrome.webRequest.WebResponseCacheDetails,
621+
): boolean {
622+
return (
623+
response.responseHeaders?.find(
624+
header =>
625+
header.name.includes('vary') &&
626+
header.value?.includes('Service-Worker'),
627+
) !== undefined
628+
);
629+
}

yarn.lock

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,18 @@
658658
"@rollup/pluginutils" "^5.0.1"
659659
eslint "^8.24.0"
660660

661+
"@rollup/plugin-node-resolve@^15.2.3":
662+
version "15.2.3"
663+
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz#e5e0b059bd85ca57489492f295ce88c2d4b0daf9"
664+
integrity sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==
665+
dependencies:
666+
"@rollup/pluginutils" "^5.0.1"
667+
"@types/resolve" "1.20.2"
668+
deepmerge "^4.2.2"
669+
is-builtin-module "^3.2.1"
670+
is-module "^1.0.0"
671+
resolve "^1.22.1"
672+
661673
"@rollup/plugin-typescript@^11.1.5":
662674
version "11.1.5"
663675
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz#039c763bf943a5921f3f42be255895e75764cb91"
@@ -817,6 +829,11 @@
817829
dependencies:
818830
undici-types "~5.25.1"
819831

832+
"@types/resolve@1.20.2":
833+
version "1.20.2"
834+
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
835+
integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
836+
820837
"@types/semver@^7.5.0":
821838
version "7.5.4"
822839
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff"
@@ -967,6 +984,11 @@ acorn@^8.1.0, acorn@^8.8.1, acorn@^8.9.0:
967984
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
968985
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
969986

987+
acorn@^8.11.3:
988+
version "8.11.3"
989+
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
990+
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
991+
970992
agent-base@6:
971993
version "6.0.2"
972994
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -1174,6 +1196,11 @@ buffer-from@^1.0.0:
11741196
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
11751197
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
11761198

1199+
builtin-modules@^3.3.0:
1200+
version "3.3.0"
1201+
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
1202+
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
1203+
11771204
callsites@^3.0.0:
11781205
version "3.1.0"
11791206
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -1972,6 +1999,13 @@ is-arrayish@^0.2.1:
19721999
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
19732000
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
19742001

2002+
is-builtin-module@^3.2.1:
2003+
version "3.2.1"
2004+
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
2005+
integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==
2006+
dependencies:
2007+
builtin-modules "^3.3.0"
2008+
19752009
is-core-module@^2.13.0:
19762010
version "2.13.0"
19772011
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
@@ -2001,6 +2035,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
20012035
dependencies:
20022036
is-extglob "^2.1.1"
20032037

2038+
is-module@^1.0.0:
2039+
version "1.0.0"
2040+
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
2041+
integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
2042+
20042043
is-number@^7.0.0:
20052044
version "7.0.0"
20062045
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"

0 commit comments

Comments
 (0)