@tindalabs/shield 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +357 -0
- package/dist/assess.d.ts +16 -0
- package/dist/assess.js +220 -0
- package/dist/config/default-extensions-config.json +103 -0
- package/dist/core/ContentProtector.d.ts +63 -0
- package/dist/core/ContentProtector.js +281 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +2 -0
- package/dist/core/mediator/ContentProtectionMediator.d.ts +86 -0
- package/dist/core/mediator/ContentProtectionMediator.js +238 -0
- package/dist/core/mediator/eventDataTypes.d.ts +112 -0
- package/dist/core/mediator/eventDataTypes.js +23 -0
- package/dist/core/mediator/handlers/abstractEventHandler.d.ts +41 -0
- package/dist/core/mediator/handlers/abstractEventHandler.js +59 -0
- package/dist/core/mediator/handlers/devToolsEventHandler.d.ts +9 -0
- package/dist/core/mediator/handlers/devToolsEventHandler.js +95 -0
- package/dist/core/mediator/handlers/eventHandlerRegistry.d.ts +9 -0
- package/dist/core/mediator/handlers/eventHandlerRegistry.js +34 -0
- package/dist/core/mediator/handlers/extensionEventHandlers.d.ts +40 -0
- package/dist/core/mediator/handlers/extensionEventHandlers.js +140 -0
- package/dist/core/mediator/handlers/iFrameEventHandlers.d.ts +27 -0
- package/dist/core/mediator/handlers/iFrameEventHandlers.js +93 -0
- package/dist/core/mediator/handlers/screenShotEventHandlers.d.ts +34 -0
- package/dist/core/mediator/handlers/screenShotEventHandlers.js +111 -0
- package/dist/core/mediator/protection-event.d.ts +77 -0
- package/dist/core/mediator/protection-event.js +32 -0
- package/dist/core/mediator/types.d.ts +105 -0
- package/dist/core/mediator/types.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/otel.d.ts +24 -0
- package/dist/otel.js +83 -0
- package/dist/policy.d.ts +98 -0
- package/dist/policy.js +97 -0
- package/dist/strategies/AbstractStrategy.d.ts +124 -0
- package/dist/strategies/AbstractStrategy.js +256 -0
- package/dist/strategies/ClipboardStrategy.d.ts +67 -0
- package/dist/strategies/ClipboardStrategy.js +291 -0
- package/dist/strategies/ContextMenuStrategy.d.ts +60 -0
- package/dist/strategies/ContextMenuStrategy.js +454 -0
- package/dist/strategies/DevToolsStrategy.d.ts +55 -0
- package/dist/strategies/DevToolsStrategy.js +314 -0
- package/dist/strategies/ExtensionStrategy.d.ts +66 -0
- package/dist/strategies/ExtensionStrategy.js +486 -0
- package/dist/strategies/IFrameStrategy.d.ts +49 -0
- package/dist/strategies/IFrameStrategy.js +255 -0
- package/dist/strategies/KeyboardStrategy.d.ts +35 -0
- package/dist/strategies/KeyboardStrategy.js +130 -0
- package/dist/strategies/PrintStrategy.d.ts +47 -0
- package/dist/strategies/PrintStrategy.js +201 -0
- package/dist/strategies/ScreenshotStrategy.d.ts +90 -0
- package/dist/strategies/ScreenshotStrategy.js +502 -0
- package/dist/strategies/SelectionStrategy.d.ts +49 -0
- package/dist/strategies/SelectionStrategy.js +216 -0
- package/dist/strategies/WatermarkStrategy.d.ts +56 -0
- package/dist/strategies/WatermarkStrategy.js +287 -0
- package/dist/strategies/index.d.ts +10 -0
- package/dist/strategies/index.js +11 -0
- package/dist/types/assessment.d.ts +62 -0
- package/dist/types/assessment.js +1 -0
- package/dist/types/index.d.ts +278 -0
- package/dist/types/index.js +17 -0
- package/dist/utils/DOMObserver.d.ts +68 -0
- package/dist/utils/DOMObserver.js +134 -0
- package/dist/utils/base/LoggableComponent.d.ts +44 -0
- package/dist/utils/base/LoggableComponent.js +56 -0
- package/dist/utils/detectors/AbstractDevToolsDetector.d.ts +98 -0
- package/dist/utils/detectors/AbstractDevToolsDetector.js +127 -0
- package/dist/utils/detectors/dateToStringDetector.d.ts +43 -0
- package/dist/utils/detectors/dateToStringDetector.js +96 -0
- package/dist/utils/detectors/debugLibDetector.d.ts +64 -0
- package/dist/utils/detectors/debugLibDetector.js +195 -0
- package/dist/utils/detectors/debuggerDetector.d.ts +51 -0
- package/dist/utils/detectors/debuggerDetector.js +211 -0
- package/dist/utils/detectors/defineGetterDetector.d.ts +48 -0
- package/dist/utils/detectors/defineGetterDetector.js +150 -0
- package/dist/utils/detectors/detectorInterface.d.ts +36 -0
- package/dist/utils/detectors/detectorInterface.js +1 -0
- package/dist/utils/detectors/devToolsDetectorManager.d.ts +88 -0
- package/dist/utils/detectors/devToolsDetectorManager.js +243 -0
- package/dist/utils/detectors/funcToStringDetector.d.ts +43 -0
- package/dist/utils/detectors/funcToStringDetector.js +90 -0
- package/dist/utils/detectors/regToStringDetector.d.ts +43 -0
- package/dist/utils/detectors/regToStringDetector.js +129 -0
- package/dist/utils/detectors/sizeDetector.d.ts +54 -0
- package/dist/utils/detectors/sizeDetector.js +134 -0
- package/dist/utils/detectors/timingDetector.d.ts +55 -0
- package/dist/utils/detectors/timingDetector.js +143 -0
- package/dist/utils/dom.d.ts +20 -0
- package/dist/utils/dom.js +83 -0
- package/dist/utils/environment.d.ts +29 -0
- package/dist/utils/environment.js +267 -0
- package/dist/utils/eventManager.d.ts +162 -0
- package/dist/utils/eventManager.js +548 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/intervalManager.d.ts +91 -0
- package/dist/utils/intervalManager.js +221 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.d.ts +41 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.js +135 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcuts.d.ts +18 -0
- package/dist/utils/keyboardShortcutManager/keyboardShortcuts.js +195 -0
- package/dist/utils/logging/simple/Loggable.d.ts +33 -0
- package/dist/utils/logging/simple/Loggable.js +1 -0
- package/dist/utils/logging/simple/LoggingDelegate.d.ts +42 -0
- package/dist/utils/logging/simple/LoggingDelegate.js +53 -0
- package/dist/utils/logging/simple/SimpleLoggingService.d.ts +39 -0
- package/dist/utils/logging/simple/SimpleLoggingService.js +58 -0
- package/dist/utils/orientation.d.ts +15 -0
- package/dist/utils/orientation.js +32 -0
- package/dist/utils/protectedContentManager.d.ts +155 -0
- package/dist/utils/protectedContentManager.js +424 -0
- package/dist/utils/securityOverlayManager.d.ts +253 -0
- package/dist/utils/securityOverlayManager.js +786 -0
- package/dist/utils/timeoutManager.d.ts +50 -0
- package/dist/utils/timeoutManager.js +113 -0
- package/package.json +61 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { isBrowser } from "../utils/environment";
|
|
2
|
+
import defaultConfig from "../config/default-extensions-config.json";
|
|
3
|
+
import { intervalManager } from "../utils/intervalManager";
|
|
4
|
+
import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
|
|
5
|
+
import { ProtectionEventType } from "../core/mediator/protection-event";
|
|
6
|
+
/**
|
|
7
|
+
* Strategy for detecting and responding to browser extensions that might scrape content
|
|
8
|
+
*/
|
|
9
|
+
export class BrowserExtensionDetectionStrategy extends AbstractStrategy {
|
|
10
|
+
/**
|
|
11
|
+
* Create a new BrowserExtensionDetectionStrategy
|
|
12
|
+
* @param options Configuration options
|
|
13
|
+
* @param targetElement Element containing sensitive content to protect
|
|
14
|
+
* @param customHandler Optional custom handler for extension detection
|
|
15
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
16
|
+
*/
|
|
17
|
+
constructor(options, targetElement = null, customHandler, debugMode = false) {
|
|
18
|
+
super("BrowserExtensionDetectionStrategy", debugMode);
|
|
19
|
+
this.intervalId = null;
|
|
20
|
+
this.taskId = null;
|
|
21
|
+
this.extensionsConfig = {};
|
|
22
|
+
this.detectedExtensions = new Set();
|
|
23
|
+
this.targetElement = null;
|
|
24
|
+
this.configLoaded = false;
|
|
25
|
+
this.configLoadPromise = null;
|
|
26
|
+
this.defaultConfigPath = "/src/data/default-extensions-config.json";
|
|
27
|
+
this.options = {
|
|
28
|
+
showOverlay: true,
|
|
29
|
+
detectionInterval: 5000, // Check every 5 seconds
|
|
30
|
+
overlayOptions: {
|
|
31
|
+
title: "Content Protection Active",
|
|
32
|
+
message: "Please disable content scraping extensions to view this content.",
|
|
33
|
+
backgroundColor: "rgba(220, 38, 38, 0.9)", // Red with opacity
|
|
34
|
+
textColor: "white",
|
|
35
|
+
},
|
|
36
|
+
hideContent: true,
|
|
37
|
+
...options,
|
|
38
|
+
};
|
|
39
|
+
this.targetElement = targetElement;
|
|
40
|
+
this.customHandler = customHandler;
|
|
41
|
+
this.log("Initialized with detection interval:", this.options.detectionInterval);
|
|
42
|
+
// If inline config is provided, use it immediately
|
|
43
|
+
if (this.options.extensionsConfig) {
|
|
44
|
+
this.extensionsConfig = this.options.extensionsConfig;
|
|
45
|
+
this.configLoaded = true;
|
|
46
|
+
this.log("Using provided inline extensions config");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Load extension configuration from a JSON file
|
|
51
|
+
*/
|
|
52
|
+
async loadConfiguration() {
|
|
53
|
+
return this.safeExecuteAsync("loadConfiguration", StrategyErrorType.INITIALIZATION, async () => {
|
|
54
|
+
if (this.configLoaded)
|
|
55
|
+
return;
|
|
56
|
+
// If we're already loading, wait for that to complete
|
|
57
|
+
if (this.configLoadPromise) {
|
|
58
|
+
return this.configLoadPromise;
|
|
59
|
+
}
|
|
60
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
61
|
+
this.configLoadPromise = new Promise(async (resolve) => {
|
|
62
|
+
// If a custom config path is provided, use it
|
|
63
|
+
if (this.options.configPath) {
|
|
64
|
+
this.log(`Loading configuration from ${this.options.configPath}`);
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(this.options.configPath);
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new Error(`Failed to load configuration: ${response.statusText}`);
|
|
69
|
+
}
|
|
70
|
+
const config = await response.json();
|
|
71
|
+
// Validate the configuration
|
|
72
|
+
if (!config.extensions || typeof config.extensions !== "object") {
|
|
73
|
+
throw new Error('Invalid configuration format: missing or invalid "extensions" object');
|
|
74
|
+
}
|
|
75
|
+
this.extensionsConfig = config.extensions;
|
|
76
|
+
this.configLoaded = true;
|
|
77
|
+
this.log(`Loaded configuration with ${Object.keys(this.extensionsConfig).length} extensions`);
|
|
78
|
+
}
|
|
79
|
+
catch (fetchError) {
|
|
80
|
+
this.handleError(StrategyErrorType.INITIALIZATION, "Error fetching configuration", fetchError);
|
|
81
|
+
// Fall back to default configuration
|
|
82
|
+
this.extensionsConfig = defaultConfig.extensions;
|
|
83
|
+
this.configLoaded = true;
|
|
84
|
+
this.log("Falling back to default configuration after fetch error");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Use the imported default configuration
|
|
89
|
+
this.extensionsConfig = defaultConfig.extensions;
|
|
90
|
+
this.configLoaded = true;
|
|
91
|
+
this.log(`Using default configuration with ${Object.keys(this.extensionsConfig).length} extensions`);
|
|
92
|
+
}
|
|
93
|
+
resolve();
|
|
94
|
+
});
|
|
95
|
+
return this.configLoadPromise;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Apply the protection strategy
|
|
100
|
+
*/
|
|
101
|
+
async apply() {
|
|
102
|
+
return this.safeExecuteAsync("apply", StrategyErrorType.APPLICATION, async () => {
|
|
103
|
+
if (this.isAppliedFlag) {
|
|
104
|
+
this.log("Protection already applied");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Load configuration if not already loaded
|
|
108
|
+
await this.loadConfiguration();
|
|
109
|
+
// Run initial detection
|
|
110
|
+
await this.runAllDetectionMethods();
|
|
111
|
+
// Register with IntervalManager
|
|
112
|
+
try {
|
|
113
|
+
this.taskId = intervalManager.registerTask("extension-detection", async () => {
|
|
114
|
+
await this.runAllDetectionMethods();
|
|
115
|
+
// Always check overlay state if we have detected extensions
|
|
116
|
+
if (this.detectedExtensions.size > 0 && this.mediator) {
|
|
117
|
+
// Publish event to check overlay state
|
|
118
|
+
this.mediator.publish({
|
|
119
|
+
type: ProtectionEventType.OVERLAY_RESTORED,
|
|
120
|
+
source: this.STRATEGY_NAME,
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
data: {
|
|
123
|
+
strategyName: this.STRATEGY_NAME,
|
|
124
|
+
overlayType: "extension",
|
|
125
|
+
reason: "periodic_check",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}, this.options.detectionInterval);
|
|
130
|
+
if (!this.taskId) {
|
|
131
|
+
this.handleError(StrategyErrorType.APPLICATION, "Failed to register interval task", new Error("Task registration returned empty ID"));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (intervalError) {
|
|
135
|
+
this.handleError(StrategyErrorType.APPLICATION, "Error registering interval task", intervalError);
|
|
136
|
+
// Try to use a fallback interval method if IntervalManager fails
|
|
137
|
+
this.intervalId = window.setInterval(async () => {
|
|
138
|
+
await this.safeExecuteAsync("fallbackInterval", StrategyErrorType.APPLICATION, async () => {
|
|
139
|
+
await this.runAllDetectionMethods();
|
|
140
|
+
// Always check overlay state if we have detected extensions
|
|
141
|
+
if (this.detectedExtensions.size > 0 && this.mediator) {
|
|
142
|
+
// Publish event to check overlay state
|
|
143
|
+
this.mediator.publish({
|
|
144
|
+
type: ProtectionEventType.OVERLAY_RESTORED,
|
|
145
|
+
source: this.STRATEGY_NAME,
|
|
146
|
+
timestamp: Date.now(),
|
|
147
|
+
data: {
|
|
148
|
+
strategyName: this.STRATEGY_NAME,
|
|
149
|
+
overlayType: "extension",
|
|
150
|
+
reason: "periodic_check",
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}, this.options.detectionInterval);
|
|
156
|
+
this.log("Using fallback interval method after IntervalManager failure");
|
|
157
|
+
}
|
|
158
|
+
this.isAppliedFlag = true;
|
|
159
|
+
this.log("Protection applied with detection interval:", this.options.detectionInterval);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Remove the protection strategy
|
|
164
|
+
*/
|
|
165
|
+
remove() {
|
|
166
|
+
return this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
|
|
167
|
+
if (!this.isAppliedFlag) {
|
|
168
|
+
this.log("Protection not applied");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Clear interval via IntervalManager
|
|
172
|
+
if (this.taskId !== null) {
|
|
173
|
+
intervalManager.unregisterTask(this.taskId);
|
|
174
|
+
this.taskId = null;
|
|
175
|
+
this.log("Interval task unregistered");
|
|
176
|
+
}
|
|
177
|
+
// Also clear the old interval for backwards compatibility or if both are used
|
|
178
|
+
if (this.intervalId !== null) {
|
|
179
|
+
window.clearInterval(this.intervalId);
|
|
180
|
+
this.intervalId = null;
|
|
181
|
+
this.log("Legacy interval cleared");
|
|
182
|
+
}
|
|
183
|
+
// Call parent class remove method to handle event cleanup
|
|
184
|
+
super.remove();
|
|
185
|
+
// If extensions are currently detected, publish events to restore content and remove overlay
|
|
186
|
+
if (this.detectedExtensions.size > 0 && this.mediator) {
|
|
187
|
+
if (this.options.hideContent) {
|
|
188
|
+
this.mediator.publish({
|
|
189
|
+
type: ProtectionEventType.CONTENT_RESTORED,
|
|
190
|
+
source: this.STRATEGY_NAME,
|
|
191
|
+
timestamp: Date.now(),
|
|
192
|
+
data: {
|
|
193
|
+
strategyName: this.STRATEGY_NAME,
|
|
194
|
+
targetElement: this.targetElement,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (this.options.showOverlay) {
|
|
199
|
+
this.mediator.publish({
|
|
200
|
+
type: ProtectionEventType.OVERLAY_REMOVED,
|
|
201
|
+
source: this.STRATEGY_NAME,
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
data: {
|
|
204
|
+
strategyName: this.STRATEGY_NAME,
|
|
205
|
+
overlayType: "devtools",
|
|
206
|
+
reason: "strategy_removed",
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Always reset state
|
|
212
|
+
this.isAppliedFlag = false;
|
|
213
|
+
this.detectedExtensions.clear();
|
|
214
|
+
this.log("Protection removed");
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Run all detection methods to check for extensions
|
|
219
|
+
*/
|
|
220
|
+
async runAllDetectionMethods() {
|
|
221
|
+
return this.safeExecuteAsync("runAllDetectionMethods", StrategyErrorType.APPLICATION, async () => {
|
|
222
|
+
this.log("Running detection methods");
|
|
223
|
+
if (!this.configLoaded) {
|
|
224
|
+
await this.loadConfiguration();
|
|
225
|
+
}
|
|
226
|
+
// Run each detection method
|
|
227
|
+
await this.safeExecuteAsync("detectDOMInjections", StrategyErrorType.APPLICATION, async () => {
|
|
228
|
+
await this.detectDOMInjections();
|
|
229
|
+
});
|
|
230
|
+
await this.safeExecute("checkJavaScriptSignatures", StrategyErrorType.APPLICATION, () => {
|
|
231
|
+
this.checkJavaScriptSignatures();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Detect DOM elements injected by extensions
|
|
237
|
+
*/
|
|
238
|
+
async detectDOMInjections() {
|
|
239
|
+
if (!isBrowser() || !document)
|
|
240
|
+
return;
|
|
241
|
+
if (!this.extensionsConfig || Object.keys(this.extensionsConfig).length === 0) {
|
|
242
|
+
this.log("No extension configuration available for DOM injection detection");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const [extensionId, config] of Object.entries(this.extensionsConfig)) {
|
|
246
|
+
if (!config ||
|
|
247
|
+
!config.detectionMethods ||
|
|
248
|
+
!config.detectionMethods.domSelectors ||
|
|
249
|
+
config.detectionMethods.domSelectors.length === 0) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// Create a combined selector from all selectors for this extension
|
|
253
|
+
const selector = config.detectionMethods.domSelectors.join(", ");
|
|
254
|
+
try {
|
|
255
|
+
const elements = document.querySelectorAll(selector);
|
|
256
|
+
if (elements.length > 0) {
|
|
257
|
+
this.handleDetection(extensionId, "dom-injection", {
|
|
258
|
+
selector,
|
|
259
|
+
count: elements.length,
|
|
260
|
+
elements: Array.from(elements).map((el) => ({
|
|
261
|
+
tagName: el.tagName,
|
|
262
|
+
id: el.id || "",
|
|
263
|
+
className: el.className || "",
|
|
264
|
+
})),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (queryError) {
|
|
269
|
+
this.handleError(StrategyErrorType.APPLICATION, `Error querying DOM for extension ${extensionId} with selector "${selector}"`, queryError);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Check for JavaScript signatures injected by extensions
|
|
275
|
+
*/
|
|
276
|
+
checkJavaScriptSignatures() {
|
|
277
|
+
if (!isBrowser() || !window)
|
|
278
|
+
return;
|
|
279
|
+
if (!this.extensionsConfig || Object.keys(this.extensionsConfig).length === 0) {
|
|
280
|
+
this.log("No extension configuration available for JavaScript signature detection");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
for (const [extensionId, config] of Object.entries(this.extensionsConfig)) {
|
|
284
|
+
if (!config ||
|
|
285
|
+
!config.detectionMethods ||
|
|
286
|
+
!config.detectionMethods.jsSignatures ||
|
|
287
|
+
config.detectionMethods.jsSignatures.length === 0) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
for (const signature of config.detectionMethods.jsSignatures) {
|
|
291
|
+
if (!signature)
|
|
292
|
+
continue;
|
|
293
|
+
// Check if the signature exists in the global scope
|
|
294
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
295
|
+
const exists = this.checkPropertyExists(window, signature);
|
|
296
|
+
if (exists) {
|
|
297
|
+
this.handleDetection(extensionId, "js-signature", {
|
|
298
|
+
signature,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Check if a property exists in an object, supporting dot notation
|
|
306
|
+
*/
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
308
|
+
checkPropertyExists(obj, path) {
|
|
309
|
+
return (this.safeExecute("checkPropertyExists", StrategyErrorType.APPLICATION, () => {
|
|
310
|
+
if (!obj || !path)
|
|
311
|
+
return false;
|
|
312
|
+
const parts = path.split(".");
|
|
313
|
+
let current = obj;
|
|
314
|
+
for (const part of parts) {
|
|
315
|
+
if (!part)
|
|
316
|
+
continue;
|
|
317
|
+
if (current === undefined || current === null) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
current = current[part];
|
|
322
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return current !== undefined;
|
|
329
|
+
}) || false);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Handle detection of an extension
|
|
333
|
+
*/
|
|
334
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
335
|
+
handleDetection(extensionId, detectionType, details) {
|
|
336
|
+
return this.safeExecute("handleDetection", StrategyErrorType.APPLICATION, () => {
|
|
337
|
+
if (!extensionId) {
|
|
338
|
+
this.handleError(StrategyErrorType.APPLICATION, "Invalid extension ID in handleDetection", new Error("Extension ID is empty or undefined"));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Skip if already detected
|
|
342
|
+
if (this.detectedExtensions.has(extensionId))
|
|
343
|
+
return;
|
|
344
|
+
// Add to detected extensions
|
|
345
|
+
this.detectedExtensions.add(extensionId);
|
|
346
|
+
const extensionConfig = this.extensionsConfig[extensionId] || {
|
|
347
|
+
name: "Unknown Extension",
|
|
348
|
+
risk: "medium",
|
|
349
|
+
};
|
|
350
|
+
this.log(`Detected extension ${extensionConfig.name} (${extensionId}) via ${detectionType}`, details);
|
|
351
|
+
// Call custom handler if provided
|
|
352
|
+
if (this.customHandler) {
|
|
353
|
+
this.safeExecute("customHandler", StrategyErrorType.APPLICATION, () => {
|
|
354
|
+
if (this.customHandler) {
|
|
355
|
+
this.customHandler(extensionId, extensionConfig.name, extensionConfig.risk);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// PUBLISH DETECTION EVENT
|
|
360
|
+
if (this.mediator) {
|
|
361
|
+
this.mediator.publish({
|
|
362
|
+
type: ProtectionEventType.EXTENSION_DETECTED,
|
|
363
|
+
source: this.STRATEGY_NAME,
|
|
364
|
+
timestamp: Date.now(),
|
|
365
|
+
data: {
|
|
366
|
+
extension: extensionConfig,
|
|
367
|
+
hideContent: this.options.hideContent,
|
|
368
|
+
showOverlay: this.options.showOverlay,
|
|
369
|
+
overlayOptions: this.options.overlayOptions,
|
|
370
|
+
target: this.targetElement,
|
|
371
|
+
priority: 8,
|
|
372
|
+
reason: "extension_detected",
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
this.log(`Published EXTENSION_DETECTED event for ${extensionConfig.name}`);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Update options for the strategy
|
|
381
|
+
*/
|
|
382
|
+
updateOptions(options) {
|
|
383
|
+
return this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
|
|
384
|
+
if (!options) {
|
|
385
|
+
this.handleError(StrategyErrorType.OPTION_UPDATE, "Invalid options in updateOptions", new Error("Options is null or undefined"));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const typedOptions = options;
|
|
389
|
+
this.log("Updating options", typedOptions);
|
|
390
|
+
// Store previous options for comparison
|
|
391
|
+
const previousOptions = { ...this.options };
|
|
392
|
+
// Update options
|
|
393
|
+
this.options = {
|
|
394
|
+
...this.options,
|
|
395
|
+
...typedOptions,
|
|
396
|
+
};
|
|
397
|
+
// Handle extension config updates
|
|
398
|
+
if (typedOptions.extensionsConfig) {
|
|
399
|
+
this.extensionsConfig = typedOptions.extensionsConfig;
|
|
400
|
+
this.configLoaded = true;
|
|
401
|
+
this.log("Updated to use new inline extensions config");
|
|
402
|
+
}
|
|
403
|
+
// Handle config path updates
|
|
404
|
+
if (typedOptions.configPath && this.isAppliedFlag) {
|
|
405
|
+
// Check if the path actually changed
|
|
406
|
+
if (typedOptions.configPath !== previousOptions.configPath) {
|
|
407
|
+
// Reload configuration
|
|
408
|
+
this.configLoaded = false;
|
|
409
|
+
this.loadConfiguration()
|
|
410
|
+
.then(() => {
|
|
411
|
+
// Re-run detection with new configuration
|
|
412
|
+
this.runAllDetectionMethods();
|
|
413
|
+
})
|
|
414
|
+
.catch((detectionError) => {
|
|
415
|
+
this.handleError(StrategyErrorType.OPTION_UPDATE, "Error running detection after config update", detectionError);
|
|
416
|
+
});
|
|
417
|
+
this.log("Reloading configuration from new path:", typedOptions.configPath);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Update detection interval if it changed and we're using the IntervalManager
|
|
421
|
+
if (typedOptions.detectionInterval &&
|
|
422
|
+
this.taskId !== null &&
|
|
423
|
+
this.isAppliedFlag &&
|
|
424
|
+
typedOptions.detectionInterval !== previousOptions.detectionInterval) {
|
|
425
|
+
// Unregister and re-register with new frequency
|
|
426
|
+
intervalManager.unregisterTask(this.taskId);
|
|
427
|
+
this.taskId = intervalManager.registerTask("extension-detection", async () => {
|
|
428
|
+
await this.runAllDetectionMethods();
|
|
429
|
+
// Always check overlay state if we have detected extensions
|
|
430
|
+
if (this.detectedExtensions.size > 0 && this.mediator) {
|
|
431
|
+
// Publish event to check overlay state
|
|
432
|
+
this.mediator.publish({
|
|
433
|
+
type: ProtectionEventType.OVERLAY_RESTORED,
|
|
434
|
+
source: this.STRATEGY_NAME,
|
|
435
|
+
timestamp: Date.now(),
|
|
436
|
+
data: {
|
|
437
|
+
strategyName: this.STRATEGY_NAME,
|
|
438
|
+
overlayType: "extension",
|
|
439
|
+
reason: "periodic_check",
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}, typedOptions.detectionInterval);
|
|
444
|
+
this.log(`Detection interval updated to ${typedOptions.detectionInterval}ms`);
|
|
445
|
+
}
|
|
446
|
+
// If protection is already applied and we have detected extensions, update the overlay if needed
|
|
447
|
+
if (this.isAppliedFlag && this.detectedExtensions.size > 0 && this.mediator) {
|
|
448
|
+
// Check if any visual options changed
|
|
449
|
+
const visualOptionsChanged = previousOptions.overlayOptions?.title !== this.options.overlayOptions?.title ||
|
|
450
|
+
previousOptions.overlayOptions?.message !== this.options.overlayOptions?.message ||
|
|
451
|
+
previousOptions.overlayOptions?.backgroundColor !== this.options.overlayOptions?.backgroundColor ||
|
|
452
|
+
previousOptions.overlayOptions?.textColor !== this.options.overlayOptions?.textColor;
|
|
453
|
+
if (visualOptionsChanged) {
|
|
454
|
+
// For each detected extension, republish the event with updated options
|
|
455
|
+
for (const extensionId of this.detectedExtensions) {
|
|
456
|
+
const extensionConfig = this.extensionsConfig[extensionId];
|
|
457
|
+
if (extensionConfig && extensionConfig.risk === "high") {
|
|
458
|
+
this.mediator.publish({
|
|
459
|
+
type: ProtectionEventType.EXTENSION_DETECTED,
|
|
460
|
+
source: this.STRATEGY_NAME,
|
|
461
|
+
timestamp: Date.now(),
|
|
462
|
+
data: {
|
|
463
|
+
extension: extensionConfig,
|
|
464
|
+
hideContent: this.options.hideContent,
|
|
465
|
+
showOverlay: this.options.showOverlay,
|
|
466
|
+
overlayOptions: this.options.overlayOptions,
|
|
467
|
+
target: this.targetElement,
|
|
468
|
+
priority: 8,
|
|
469
|
+
reason: "options_updated",
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
break; // Just need to update once
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Set debug mode
|
|
481
|
+
* @param enabled Whether debug mode should be enabled
|
|
482
|
+
*/
|
|
483
|
+
setDebugMode(enabled) {
|
|
484
|
+
super.setDebugMode(enabled);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FrameEmbeddingOptions, CustomEventHandlers } from "../types";
|
|
2
|
+
import { AbstractStrategy } from "./AbstractStrategy";
|
|
3
|
+
/**
|
|
4
|
+
* Strategy for preventing content from being embedded in external iframes
|
|
5
|
+
*/
|
|
6
|
+
export declare class FrameEmbeddingProtectionStrategy extends AbstractStrategy {
|
|
7
|
+
private options;
|
|
8
|
+
private targetElement;
|
|
9
|
+
private customHandler?;
|
|
10
|
+
private isEmbedded;
|
|
11
|
+
private isExternalFrame;
|
|
12
|
+
private intervalId;
|
|
13
|
+
private taskId;
|
|
14
|
+
private parentDomain;
|
|
15
|
+
/**
|
|
16
|
+
* Create a new FrameEmbeddingProtectionStrategy
|
|
17
|
+
* @param options Configuration options
|
|
18
|
+
* @param targetElement Element containing sensitive content to protect
|
|
19
|
+
* @param customHandler Optional custom handler for frame embedding detection
|
|
20
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
21
|
+
*/
|
|
22
|
+
constructor(options?: FrameEmbeddingOptions, targetElement?: HTMLElement | null, customHandler?: CustomEventHandlers["onFrameEmbeddingDetected"], debugMode?: boolean);
|
|
23
|
+
/**
|
|
24
|
+
* Check if the page is embedded in an iframe
|
|
25
|
+
*/
|
|
26
|
+
private checkIfEmbedded;
|
|
27
|
+
/**
|
|
28
|
+
* Publish frame embedding detection event
|
|
29
|
+
*/
|
|
30
|
+
private publishFrameEmbeddingEvent;
|
|
31
|
+
/**
|
|
32
|
+
* Apply the protection strategy
|
|
33
|
+
*/
|
|
34
|
+
apply(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Remove the protection strategy
|
|
37
|
+
*/
|
|
38
|
+
remove(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Update strategy options
|
|
41
|
+
* @param options Options to update
|
|
42
|
+
*/
|
|
43
|
+
updateOptions(options: Record<string, unknown>): void;
|
|
44
|
+
/**
|
|
45
|
+
* Set debug mode
|
|
46
|
+
* @param enabled Whether debug mode should be enabled
|
|
47
|
+
*/
|
|
48
|
+
setDebugMode(enabled: boolean): void;
|
|
49
|
+
}
|