Skip to content

Commit 27f1dd0

Browse files
committed
Pull request 1165: AG-38557 remove injection of remotely hosted code
Squashed commit of the following: commit 7585d80 Author: Maxim Topciu <mtopciu@adguard.com> Date: Mon Jan 13 17:05:56 2025 +0200 AG-38557 update version in package.json commit 78fc53a Author: Maxim Topciu <mtopciu@adguard.com> Date: Mon Jan 13 17:04:41 2025 +0200 AG-38557 remove unused code commit 7f3e15b Author: Maxim Topciu <mtopciu@adguard.com> Date: Mon Jan 13 17:03:25 2025 +0200 AG-38557 fix changelog commit caf26c6 Author: Maxim Topciu <mtopciu@adguard.com> Date: Mon Jan 13 15:33:54 2025 +0200 AG-38557 update changelog commit 0b43669 Author: Maxim Topciu <mtopciu@adguard.com> Date: Mon Jan 13 15:32:04 2025 +0200 AG-38557 fix eslint errors commit 6889ddc Author: Maxim Topciu <mtopciu@adguard.com> Date: Mon Jan 13 14:21:51 2025 +0200 AG-38557 remove injection of remotely hosted code
1 parent 5164b8d commit 27f1dd0

File tree

9 files changed

+85
-306
lines changed

9 files changed

+85
-306
lines changed

packages/tswebextension/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
[AdguardBrowserExtension#3002]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/3002
1919

20+
## [2.4.0-alpha.9] - 2025-01-13
21+
22+
### Removed
23+
24+
- Injection of remotely hosted script rules.
25+
26+
[2.4.0-alpha.9]: https://github.com/AdguardTeam/tsurlfilter/releases/tag/tswebextension-v2.4.0-alpha.9
27+
2028
## [2.4.0-alpha.8] - 2024-12-23
2129

2230
### Changed

packages/tswebextension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adguard/tswebextension",
3-
"version": "2.4.0-alpha.8",
3+
"version": "2.4.0-alpha.9",
44
"description": "This is a TypeScript library that implements AdGuard's extension API",
55
"main": "dist/index.js",
66
"typings": "dist/types/src/lib/mv2/background/index.d.ts",

packages/tswebextension/src/lib/mv3/background/cosmetic-api.ts

Lines changed: 35 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
} from '@adguard/tsurlfilter';
66
import { CosmeticRuleType } from '@adguard/agtree';
77

8-
import { CUSTOM_FILTERS_START_ID, LF, USER_FILTER_ID } from '../../common/constants';
98
import { appContext } from './app-context';
109
import { engineApi } from './engine-api';
1110
import { tabsApi } from '../tabs/tabs-api';
@@ -18,7 +17,7 @@ import { defaultFilteringLog, FilteringEventType } from '../../common/filtering-
1817
import { getDomain } from '../../common/utils/url';
1918
import { type ContentType } from '../../common/request-type';
2019
import { nanoid } from '../nanoid';
21-
import { localScriptRulesService, type LocalScriptFunction } from './services/local-script-rules-service';
20+
import { localScriptRulesService } from './services/local-script-rules-service';
2221

2322
export type ContentScriptCosmeticData = {
2423
/**
@@ -42,18 +41,9 @@ export type ContentScriptCosmeticData = {
4241
*/
4342
type ScriptsAndScriptletsData = {
4443
/**
45-
* Script text which is combiner from JS rules only from User rules and Custom filters
46-
* since they are added manually by users. That's why they are considered "local".
44+
* Script texts from JS rules.
4745
*/
48-
localScriptText: string,
49-
50-
/**
51-
* Script functions combined from JS rules from filters which are pre-built into the extension.
52-
* That's why they are considered "local".
53-
*
54-
* Should be executed by chrome scripting api.
55-
*/
56-
localScriptFunctions: LocalScriptFunction[],
46+
scriptTexts: string[],
5747

5848
/**
5949
* List of scriptlet data objects. No need to separate them by type since they are all safe.
@@ -179,52 +169,34 @@ export class CosmeticApi extends CosmeticApiCommon {
179169
`;
180170
}
181171

182-
/**
183-
* Checks whether the cosmetic (JS) rule is added manually by user —
184-
* is it located in User rules or Custom filters.
185-
*
186-
* @param rule Rule to check.
187-
*
188-
* @returns True if rule is added manually by user.
189-
*/
190-
private static isUserAddedRule(rule: CosmeticRule): boolean {
191-
const filterListId = rule.getFilterListId();
192-
return filterListId >= CUSTOM_FILTERS_START_ID || filterListId === USER_FILTER_ID;
193-
}
194-
195172
/**
196173
* It is possible to follow all places using this logic by searching JS_RULES_EXECUTION.
197174
*
198175
* This is STEP 3: All previously matched script rules are processed and filtered:
199176
* - JS rules from pre-built filters (previously collected, pre-built and passed to the engine)
200-
* are going to be executed as functions via chrome.scripting API;
201-
* - JS rules manually added by users (from User rules and Custom filters)
202-
* are going to be executed as script text via script tag injection.
177+
* are going to be executed as functions via chrome.scripting API.
203178
*/
204179
/**
205180
* Generates data for scriptlets and local scripts:
206181
* - functions for scriptlets,
207-
* - functions for JS rules from pre-built filters,
208-
* - script text for JS rules from User rules and Custom filters.
182+
* - script texts for JS rules from pre-built filters.
209183
*
210184
* @param cosmeticResult Object containing cosmetic rules.
211185
*
212-
* @returns An object with data for scriptlets and local scripts — script text and functions.
186+
* @returns An object with data for scriptlets and script texts.
213187
*/
214188
public static getScriptsAndScriptletsData(cosmeticResult: CosmeticResult): ScriptsAndScriptletsData {
215189
const rules = cosmeticResult.getScriptRules();
216190

217191
if (rules.length === 0) {
218192
return {
219-
localScriptText: '',
220-
localScriptFunctions: [],
193+
scriptTexts: [],
221194
scriptletDataList: [],
222195
};
223196
}
224197

225-
const uniqueScriptFunctions = new Set<LocalScriptFunction>();
198+
const uniqueScriptTexts = new Set<string>();
226199
const scriptletDataList = [];
227-
const uniqueScriptStrings = new Set<string>();
228200

229201
for (let i = 0; i < rules.length; i += 1) {
230202
const rule = rules[i];
@@ -233,36 +205,19 @@ export class CosmeticApi extends CosmeticApiCommon {
233205
if (scriptletData) {
234206
scriptletDataList.push(scriptletData);
235207
}
236-
} else if (CosmeticApi.isUserAddedRule(rule)) {
237-
// JS rule is manually added by user locally in the extension — save its script text.
238-
const scriptText = rule.getScript();
239-
if (scriptText) {
240-
uniqueScriptStrings.add(scriptText.trim());
241-
}
242208
} else {
243209
// TODO: Optimize script injection by checking if common scripts (e.g., AG_)
244210
// are actually used in the rules. If not, avoid injecting them to reduce overhead.
245211

246-
// JS rule is pre-built into the extension — save its function.
247-
const scriptFunction = localScriptRulesService.getLocalScriptFunction(rule);
248-
if (scriptFunction) {
249-
uniqueScriptFunctions.add(scriptFunction);
212+
const ruleScriptText = rule.getContent();
213+
if (ruleScriptText) {
214+
uniqueScriptTexts.add(ruleScriptText);
250215
}
251216
}
252217
}
253218

254-
let scriptText = '';
255-
uniqueScriptStrings.forEach((script) => {
256-
scriptText += script.endsWith(';')
257-
? `${script}${LF}`
258-
: `${script};${LF}`;
259-
});
260-
261-
const wrappedScriptText = CosmeticApi.wrapScriptText(scriptText);
262-
263219
return {
264-
localScriptText: wrappedScriptText,
265-
localScriptFunctions: [...uniqueScriptFunctions],
220+
scriptTexts: [...uniqueScriptTexts],
266221
scriptletDataList,
267222
};
268223
}
@@ -327,65 +282,48 @@ export class CosmeticApi extends CosmeticApiCommon {
327282
return;
328283
}
329284

330-
const localScriptFunctions = frameContext.preparedCosmeticResult?.localScriptFunctions;
285+
const scriptTexts = frameContext.preparedCosmeticResult?.scriptTexts;
331286

332-
if (!localScriptFunctions || localScriptFunctions.length === 0) {
287+
if (!scriptTexts || scriptTexts.length === 0) {
333288
return;
334289
}
335290

336291
try {
337-
await Promise.all(localScriptFunctions.map((scriptFunction) => {
292+
await Promise.all(scriptTexts.map((scriptText) => {
338293
/**
339294
* It is possible to follow all places using this logic by searching JS_RULES_EXECUTION.
340295
*
341-
* This is STEP 4.1: Apply JS rules from pre-built filters — via chrome.scripting API.
296+
* This is STEP 4.1: Selecting only local script functions which were pre-built into the extension.
297+
*/
298+
299+
/**
300+
* Here we check if the script text is local to guarantee that we don't execute remote code.
342301
*/
302+
const isLocal = localScriptRulesService.isLocal(scriptText);
303+
if (!isLocal) {
304+
return;
305+
}
306+
307+
/**
308+
* Here we get the function associated with the script text.
309+
*/
310+
const localScriptFunction = localScriptRulesService.getLocalScriptFunction(scriptText);
311+
if (!localScriptFunction) {
312+
return;
313+
}
314+
315+
// eslint-disable-next-line consistent-return
343316
return ScriptingApi.executeScriptFunc({
344317
tabId,
345318
frameId,
346-
scriptFunction,
319+
scriptFunction: localScriptFunction,
347320
});
348321
}));
349322
} catch (e) {
350323
logger.debug('[applyJsFuncsByTabAndFrame] error occurred during injection', getErrorMessage(e));
351324
}
352325
}
353326

354-
/**
355-
* Injects js locally added rules by user to specified tab and frame.
356-
*
357-
* @param tabId Tab id.
358-
* @param frameId Frame id.
359-
*/
360-
public static async applyJsTextByTabAndFrame(tabId: number, frameId: number): Promise<void> {
361-
const frameContext = tabsApi.getFrameContext(tabId, frameId);
362-
363-
if (!frameContext) {
364-
return;
365-
}
366-
367-
const localScriptText = frameContext.preparedCosmeticResult?.localScriptText;
368-
369-
if (!localScriptText) {
370-
return;
371-
}
372-
373-
try {
374-
/**
375-
* It is possible to follow all places using this logic by searching JS_RULES_EXECUTION.
376-
*
377-
* This is STEP 4.2: Apply JS rules manually added by users — via script tag injection.
378-
*/
379-
await ScriptingApi.executeScriptText({
380-
tabId,
381-
frameId,
382-
scriptText: localScriptText,
383-
});
384-
} catch (e) {
385-
logger.debug('[applyJsTextByTabAndFrame] error occurred during injection', getErrorMessage(e));
386-
}
387-
}
388-
389327
/**
390328
* Injects js to specified tab and frame.
391329
*

packages/tswebextension/src/lib/mv3/background/cosmetic-frame-processor.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,7 @@ export class CosmeticFrameProcessor {
209209
const cosmeticResult = engineApi.getCosmeticResult(url, result.getCosmeticOption());
210210

211211
const {
212-
localScriptText,
213-
localScriptFunctions,
212+
scriptTexts,
214213
scriptletDataList,
215214
} = CosmeticApi.getScriptsAndScriptletsData(cosmeticResult);
216215

@@ -221,8 +220,7 @@ export class CosmeticFrameProcessor {
221220
matchingResult: result,
222221
cosmeticResult,
223222
preparedCosmeticResult: {
224-
localScriptText,
225-
localScriptFunctions,
223+
scriptTexts,
226224
scriptletDataList,
227225
cssText,
228226
},
@@ -266,8 +264,7 @@ export class CosmeticFrameProcessor {
266264
const cosmeticResult = engineApi.getCosmeticResult(url, result.getCosmeticOption());
267265

268266
const {
269-
localScriptText,
270-
localScriptFunctions,
267+
scriptTexts,
271268
scriptletDataList,
272269
} = CosmeticApi.getScriptsAndScriptletsData(cosmeticResult);
273270

@@ -277,8 +274,7 @@ export class CosmeticFrameProcessor {
277274
matchingResult: result,
278275
cosmeticResult,
279276
preparedCosmeticResult: {
280-
localScriptText,
281-
localScriptFunctions,
277+
scriptTexts,
282278
scriptletDataList,
283279
cssText,
284280
},

0 commit comments

Comments
 (0)