@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,140 @@
|
|
|
1
|
+
import { ProtectionEventType } from "../protection-event";
|
|
2
|
+
import { isEventType } from "../eventDataTypes";
|
|
3
|
+
import { AbstractEventHandler } from "./abstractEventHandler";
|
|
4
|
+
/**
|
|
5
|
+
* Handler for browser extension detection events
|
|
6
|
+
*/
|
|
7
|
+
export class BrowserExtensionEventHandler extends AbstractEventHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new BrowserExtensionEventHandler
|
|
10
|
+
* @param mediator The protection mediator
|
|
11
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
12
|
+
*/
|
|
13
|
+
constructor(mediator, debugMode = false) {
|
|
14
|
+
super(mediator, "BrowserExtensionEventHandler", debugMode);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Initialize the handler and subscribe to events
|
|
18
|
+
*/
|
|
19
|
+
initialize() {
|
|
20
|
+
this.subscribe(ProtectionEventType.EXTENSION_DETECTED, this.handleExtensionDetected.bind(this));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Handle extension detection
|
|
24
|
+
* @param event the ProtectionEvent
|
|
25
|
+
*/
|
|
26
|
+
handleExtensionDetected(event) {
|
|
27
|
+
// Use type guard for type-safe access
|
|
28
|
+
if (!isEventType(event, ProtectionEventType.EXTENSION_DETECTED)) {
|
|
29
|
+
this.error("Received invalid event type");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const { data } = event;
|
|
33
|
+
this.log(`Handling extension detection state change, name=${data.extension?.name || "unknown"}`);
|
|
34
|
+
if (data.extension) {
|
|
35
|
+
this.applyExtensionProtection(event);
|
|
36
|
+
this.log("Blocking interface due to extension detection");
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.removeExtensionProtection(event);
|
|
40
|
+
this.log("Unlocking interface after extension removal");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Handle extension detection
|
|
45
|
+
* @param event the typed ExtensionDetectedEvent
|
|
46
|
+
*/
|
|
47
|
+
applyExtensionProtection(event) {
|
|
48
|
+
const { data } = event;
|
|
49
|
+
if (data.hideContent) {
|
|
50
|
+
this.mediator.publish({
|
|
51
|
+
type: ProtectionEventType.CONTENT_HIDDEN,
|
|
52
|
+
source: this.COMPONENT_NAME,
|
|
53
|
+
timestamp: Date.now(),
|
|
54
|
+
data: {
|
|
55
|
+
strategyName: this.COMPONENT_NAME,
|
|
56
|
+
reason: "extension_detected",
|
|
57
|
+
targetElement: data.target,
|
|
58
|
+
options: {
|
|
59
|
+
title: data.overlayOptions?.title,
|
|
60
|
+
message: data.overlayOptions?.message,
|
|
61
|
+
secondaryMessage: `"${data.extension?.name}" triggered this warning`,
|
|
62
|
+
textColor: "black",
|
|
63
|
+
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
64
|
+
},
|
|
65
|
+
priority: 8,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (data.showOverlay) {
|
|
70
|
+
const extensionConfig = data.extension;
|
|
71
|
+
const additionalContent = `
|
|
72
|
+
<p style="font-size: 18px; margin-top: 20px;">Detected: ${extensionConfig.name}</p>
|
|
73
|
+
<p style="font-size: 14px; margin-top: 10px;">Risk Level: ${extensionConfig.risk.toUpperCase()}</p>
|
|
74
|
+
<button id="extension-protection-close" style="margin-top: 20px; padding: 10px 20px; background-color: white; color: black; border: none; border-radius: 4px; cursor: pointer; pointer-events: auto;">
|
|
75
|
+
I've Disabled the Extension
|
|
76
|
+
</button>
|
|
77
|
+
`;
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
const closeButton = document.getElementById("extension-protection-close");
|
|
80
|
+
if (closeButton) {
|
|
81
|
+
closeButton.addEventListener("click", () => {
|
|
82
|
+
window.location.reload();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}, 0);
|
|
86
|
+
this.mediator.publish({
|
|
87
|
+
type: ProtectionEventType.OVERLAY_SHOWN,
|
|
88
|
+
source: this.COMPONENT_NAME,
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
data: {
|
|
91
|
+
strategyName: this.COMPONENT_NAME,
|
|
92
|
+
overlayType: "extension",
|
|
93
|
+
options: {
|
|
94
|
+
...data.overlayOptions,
|
|
95
|
+
blockEvents: true,
|
|
96
|
+
autoRestore: true,
|
|
97
|
+
additionalContent,
|
|
98
|
+
},
|
|
99
|
+
priority: 8,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Remove extension protection
|
|
106
|
+
* @param event The typed ExtensionDetectedEvent
|
|
107
|
+
*/
|
|
108
|
+
removeExtensionProtection(event) {
|
|
109
|
+
const { data } = event;
|
|
110
|
+
if (data.hideContent) {
|
|
111
|
+
this.mediator.publish({
|
|
112
|
+
type: ProtectionEventType.CONTENT_RESTORED,
|
|
113
|
+
source: this.COMPONENT_NAME,
|
|
114
|
+
timestamp: Date.now(),
|
|
115
|
+
data: {
|
|
116
|
+
strategyName: this.COMPONENT_NAME,
|
|
117
|
+
targetElement: data.target,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (data.showOverlay) {
|
|
122
|
+
this.mediator.publish({
|
|
123
|
+
type: ProtectionEventType.OVERLAY_REMOVED,
|
|
124
|
+
source: this.COMPONENT_NAME,
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
data: {
|
|
127
|
+
strategyName: this.COMPONENT_NAME,
|
|
128
|
+
overlayType: "extension",
|
|
129
|
+
reason: "extension_removed",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Additional cleanup to be performed on disposal
|
|
136
|
+
*/
|
|
137
|
+
onDispose() {
|
|
138
|
+
// No additional cleanup needed for this handler
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ProtectionMediator } from "../../mediator/types";
|
|
2
|
+
import { type ProtectionEvent } from "../../mediator/protection-event";
|
|
3
|
+
import { AbstractEventHandler } from "./abstractEventHandler";
|
|
4
|
+
/**
|
|
5
|
+
* Handler for frame embedding events
|
|
6
|
+
*/
|
|
7
|
+
export declare class FrameEmbeddingEventHandler extends AbstractEventHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new FrameEmbeddingEventHandler
|
|
10
|
+
* @param mediator The protection mediator
|
|
11
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
12
|
+
*/
|
|
13
|
+
constructor(mediator: ProtectionMediator, debugMode?: boolean);
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the handler and set up event subscriptions
|
|
16
|
+
*/
|
|
17
|
+
protected initialize(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Handle frame embedding detection event
|
|
20
|
+
* @param event The protection event containing frame embedding data
|
|
21
|
+
*/
|
|
22
|
+
handleFrameEmbeddingDetected(event: ProtectionEvent): void;
|
|
23
|
+
/**
|
|
24
|
+
* Clean up resources when the handler is disposed
|
|
25
|
+
*/
|
|
26
|
+
protected onDispose(): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ProtectionEventType } from "../../mediator/protection-event";
|
|
2
|
+
import { AbstractEventHandler } from "./abstractEventHandler";
|
|
3
|
+
/**
|
|
4
|
+
* Handler for frame embedding events
|
|
5
|
+
*/
|
|
6
|
+
export class FrameEmbeddingEventHandler extends AbstractEventHandler {
|
|
7
|
+
/**
|
|
8
|
+
* Create a new FrameEmbeddingEventHandler
|
|
9
|
+
* @param mediator The protection mediator
|
|
10
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
11
|
+
*/
|
|
12
|
+
constructor(mediator, debugMode = false) {
|
|
13
|
+
super(mediator, "FrameEmbeddingEventHandler", debugMode);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the handler and set up event subscriptions
|
|
17
|
+
*/
|
|
18
|
+
initialize() {
|
|
19
|
+
// Subscribe to the FRAME_EMBEDDING_DETECTED event
|
|
20
|
+
this.subscribe(ProtectionEventType.FRAME_EMBEDDING_DETECTED, this.handleFrameEmbeddingDetected.bind(this));
|
|
21
|
+
this.log("Initialized and subscribed to frame embedding events");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Handle frame embedding detection event
|
|
25
|
+
* @param event The protection event containing frame embedding data
|
|
26
|
+
*/
|
|
27
|
+
handleFrameEmbeddingDetected(event) {
|
|
28
|
+
try {
|
|
29
|
+
const frameEvent = event;
|
|
30
|
+
this.log("Handling frame embedding detected event", frameEvent);
|
|
31
|
+
const data = frameEvent.data;
|
|
32
|
+
if (!data) {
|
|
33
|
+
this.warn("Received invalid frame embedding event data");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Only handle external frames
|
|
37
|
+
if (data.isEmbedded && data.isExternalFrame) {
|
|
38
|
+
this.log(`Applying protection for external frame embedding from ${data.parentDomain || "unknown domain"}`);
|
|
39
|
+
// Show overlay if configured
|
|
40
|
+
if (data.showOverlay) {
|
|
41
|
+
this.mediator.publish({
|
|
42
|
+
type: ProtectionEventType.OVERLAY_SHOWN,
|
|
43
|
+
source: this.COMPONENT_NAME,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
data: {
|
|
46
|
+
strategyName: this.COMPONENT_NAME,
|
|
47
|
+
overlayType: "frame_embedding",
|
|
48
|
+
options: {
|
|
49
|
+
...data.overlayOptions,
|
|
50
|
+
blockEvents: true,
|
|
51
|
+
autoRestore: true,
|
|
52
|
+
},
|
|
53
|
+
priority: 9, // High priority for frame embedding
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Hide content if configured
|
|
58
|
+
if (data.hideContent) {
|
|
59
|
+
this.mediator.publish({
|
|
60
|
+
type: ProtectionEventType.CONTENT_HIDDEN,
|
|
61
|
+
source: this.COMPONENT_NAME,
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
data: {
|
|
64
|
+
strategyName: this.COMPONENT_NAME,
|
|
65
|
+
reason: "frame_embedding",
|
|
66
|
+
targetElement: data.targetElement,
|
|
67
|
+
options: {
|
|
68
|
+
title: data.overlayOptions?.title,
|
|
69
|
+
message: data.overlayOptions?.message,
|
|
70
|
+
secondaryMessage: data.overlayOptions?.secondaryMessage,
|
|
71
|
+
textColor: "black",
|
|
72
|
+
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
73
|
+
},
|
|
74
|
+
priority: 9, // High priority for frame embedding
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.log("Skipping protection for non-external frame embedding");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.error("Error handling frame embedding detected event:", error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Clean up resources when the handler is disposed
|
|
89
|
+
*/
|
|
90
|
+
onDispose() {
|
|
91
|
+
// No additional cleanup needed beyond what the base class does
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ProtectionMediator } from "../../mediator/types";
|
|
2
|
+
import { type ProtectionEvent } from "../protection-event";
|
|
3
|
+
import { AbstractEventHandler } from "./abstractEventHandler";
|
|
4
|
+
/**
|
|
5
|
+
* Handler for screenshot detection events
|
|
6
|
+
*/
|
|
7
|
+
export declare class ScreenshotEventHandler extends AbstractEventHandler {
|
|
8
|
+
private timeoutManager;
|
|
9
|
+
private activeTimeouts;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new ScreenshotEventHandler
|
|
12
|
+
* @param mediator The protection mediator
|
|
13
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
14
|
+
*/
|
|
15
|
+
constructor(mediator: ProtectionMediator, debugMode?: boolean);
|
|
16
|
+
/**
|
|
17
|
+
* Initialize the handler and subscribe to events
|
|
18
|
+
*/
|
|
19
|
+
protected initialize(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Handle screenshot attempt event
|
|
22
|
+
* @param event The protection event
|
|
23
|
+
*/
|
|
24
|
+
handleScreenshotAttempt(event: ProtectionEvent): void;
|
|
25
|
+
/**
|
|
26
|
+
* Set debug mode
|
|
27
|
+
* @param enabled Whether debug mode should be enabled
|
|
28
|
+
*/
|
|
29
|
+
setDebugMode(enabled: boolean): void;
|
|
30
|
+
/**
|
|
31
|
+
* Additional cleanup to be performed on disposal
|
|
32
|
+
*/
|
|
33
|
+
protected onDispose(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ProtectionEventType } from "../protection-event";
|
|
2
|
+
import { TimeoutManager } from "../../../utils/timeoutManager";
|
|
3
|
+
import { AbstractEventHandler } from "./abstractEventHandler";
|
|
4
|
+
/**
|
|
5
|
+
* Handler for screenshot detection events
|
|
6
|
+
*/
|
|
7
|
+
export class ScreenshotEventHandler extends AbstractEventHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new ScreenshotEventHandler
|
|
10
|
+
* @param mediator The protection mediator
|
|
11
|
+
* @param debugMode Enable debug mode for troubleshooting
|
|
12
|
+
*/
|
|
13
|
+
constructor(mediator, debugMode = false) {
|
|
14
|
+
super(mediator, "ScreenshotEventHandler", debugMode);
|
|
15
|
+
this.activeTimeouts = new Set(); // Track active timeouts
|
|
16
|
+
this.timeoutManager = TimeoutManager.getInstance();
|
|
17
|
+
this.timeoutManager.setDebugMode(this.debugMode);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the handler and subscribe to events
|
|
21
|
+
*/
|
|
22
|
+
initialize() {
|
|
23
|
+
// Subscribe to the SCREENSHOT_ATTEMPT event
|
|
24
|
+
this.subscribe(ProtectionEventType.SCREENSHOT_ATTEMPT, this.handleScreenshotAttempt.bind(this));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Handle screenshot attempt event
|
|
28
|
+
* @param event The protection event
|
|
29
|
+
*/
|
|
30
|
+
handleScreenshotAttempt(event) {
|
|
31
|
+
const screenshotEvent = event;
|
|
32
|
+
this.log("Handling screenshot attempt event", screenshotEvent);
|
|
33
|
+
// Generate a unique ID for this protection instance
|
|
34
|
+
const protectionId = `screenshot_${Date.now()}`;
|
|
35
|
+
if (screenshotEvent.data.showOverlay) {
|
|
36
|
+
this.mediator.publish({
|
|
37
|
+
type: ProtectionEventType.OVERLAY_SHOWN,
|
|
38
|
+
source: screenshotEvent.source,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
data: {
|
|
41
|
+
strategyName: screenshotEvent.source,
|
|
42
|
+
overlayType: "screenshot",
|
|
43
|
+
options: {
|
|
44
|
+
...screenshotEvent.data.overlayOptions,
|
|
45
|
+
blockEvents: false, // Don't block events, just show a notification
|
|
46
|
+
autoRestore: true,
|
|
47
|
+
},
|
|
48
|
+
priority: screenshotEvent.data.priority || 5,
|
|
49
|
+
duration: screenshotEvent.data.overlayOptions?.duration || 3000
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (screenshotEvent.data.hideContent) {
|
|
54
|
+
this.mediator.publish({
|
|
55
|
+
type: ProtectionEventType.CONTENT_HIDDEN,
|
|
56
|
+
source: screenshotEvent.source,
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
data: {
|
|
59
|
+
strategyName: screenshotEvent.source,
|
|
60
|
+
reason: "screenshot_attempt",
|
|
61
|
+
options: {
|
|
62
|
+
title: screenshotEvent.data.overlayOptions?.title,
|
|
63
|
+
message: screenshotEvent.data.overlayOptions?.message,
|
|
64
|
+
secondaryMessage: screenshotEvent.data.overlayOptions?.secondaryMessage,
|
|
65
|
+
textColor: 'black',
|
|
66
|
+
backgroundColor: screenshotEvent.data.overlayOptions?.backgroundColor || "rgba(0, 0, 0, 0.05)",
|
|
67
|
+
},
|
|
68
|
+
targetElement: screenshotEvent.data.target,
|
|
69
|
+
priority: screenshotEvent.data.priority || 5,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
// Create a unique timeout ID for this screenshot event
|
|
73
|
+
const contentTimeoutId = `${protectionId}_content`;
|
|
74
|
+
this.activeTimeouts.add(contentTimeoutId);
|
|
75
|
+
// If duration is specified, set a timeout to restore content
|
|
76
|
+
if (screenshotEvent.data.overlayOptions?.duration) {
|
|
77
|
+
this.timeoutManager.setTimeout(contentTimeoutId, () => {
|
|
78
|
+
this.mediator.publish({
|
|
79
|
+
type: ProtectionEventType.CONTENT_RESTORED,
|
|
80
|
+
source: screenshotEvent.source,
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
data: {
|
|
83
|
+
strategyName: screenshotEvent.source,
|
|
84
|
+
targetElement: screenshotEvent.data.target,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
this.activeTimeouts.delete(contentTimeoutId);
|
|
88
|
+
}, screenshotEvent.data.overlayOptions?.duration);
|
|
89
|
+
this.log(`Set timeout ${contentTimeoutId} to restore content after ${screenshotEvent.data.overlayOptions.duration}s`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Set debug mode
|
|
95
|
+
* @param enabled Whether debug mode should be enabled
|
|
96
|
+
*/
|
|
97
|
+
setDebugMode(enabled) {
|
|
98
|
+
super.setDebugMode(enabled);
|
|
99
|
+
this.timeoutManager.setDebugMode(enabled);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Additional cleanup to be performed on disposal
|
|
103
|
+
*/
|
|
104
|
+
onDispose() {
|
|
105
|
+
// Clear all active timeouts
|
|
106
|
+
for (const timeoutId of this.activeTimeouts) {
|
|
107
|
+
this.timeoutManager.clearTimeout(timeoutId);
|
|
108
|
+
}
|
|
109
|
+
this.activeTimeouts.clear();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { EventDataMap } from "./eventDataTypes";
|
|
2
|
+
/**
|
|
3
|
+
* Types of protection events that can be published and subscribed to.
|
|
4
|
+
*
|
|
5
|
+
* Only the events listed below are actually wired through the mediator today.
|
|
6
|
+
* The mediator pattern serves a specific case: a detect-and-react fan-out where
|
|
7
|
+
* one detection signal needs to coordinate multiple visible reactions (overlay,
|
|
8
|
+
* content hiding, telemetry). That's why only 4 of the 10 strategies use it —
|
|
9
|
+
* DevTools, Extension, IFrame, and Screenshot. The other 6 (Clipboard,
|
|
10
|
+
* Selection, ContextMenu, Keyboard, Print, Watermark) are direct-blocking
|
|
11
|
+
* strategies where the handler IS the strategy; routing them through the
|
|
12
|
+
* mediator would add ceremony for no decoupling benefit.
|
|
13
|
+
*
|
|
14
|
+
* If you need a NEW event type, add it here AND give it a `[NewType]: { ... }`
|
|
15
|
+
* entry in {@link EventDataMap}.
|
|
16
|
+
*/
|
|
17
|
+
export declare enum ProtectionEventType {
|
|
18
|
+
STRATEGY_REMOVED = "strategy:removed",
|
|
19
|
+
DEVTOOLS_STATE_CHANGE = "protection:devtools",
|
|
20
|
+
EXTENSION_DETECTED = "protection:extension",
|
|
21
|
+
FRAME_EMBEDDING_DETECTED = "protection:frame",
|
|
22
|
+
SCREENSHOT_ATTEMPT = "protection:screenshot",
|
|
23
|
+
OVERLAY_SHOWN = "overlay:shown",
|
|
24
|
+
OVERLAY_REMOVED = "overlay:removed",
|
|
25
|
+
OVERLAY_RESTORED = "overlay:restored",
|
|
26
|
+
CONTENT_HIDDEN = "content:hidden",
|
|
27
|
+
CONTENT_RESTORED = "content:restored"
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Base protection event interface
|
|
31
|
+
*/
|
|
32
|
+
export interface ProtectionEvent {
|
|
33
|
+
/**
|
|
34
|
+
* Type of the event
|
|
35
|
+
*/
|
|
36
|
+
type: ProtectionEventType;
|
|
37
|
+
/**
|
|
38
|
+
* Source of the event (usually strategy name)
|
|
39
|
+
*/
|
|
40
|
+
source: string;
|
|
41
|
+
/**
|
|
42
|
+
* Timestamp when the event occurred
|
|
43
|
+
*/
|
|
44
|
+
timestamp: number;
|
|
45
|
+
/**
|
|
46
|
+
* Additional data specific to the event type
|
|
47
|
+
*/
|
|
48
|
+
data?: unknown;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Event for DevTools state changes
|
|
52
|
+
*/
|
|
53
|
+
export interface DevToolsEvent extends ProtectionEvent {
|
|
54
|
+
type: ProtectionEventType.DEVTOOLS_STATE_CHANGE;
|
|
55
|
+
data: EventDataMap[ProtectionEventType.DEVTOOLS_STATE_CHANGE];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Event for extension detection
|
|
59
|
+
*/
|
|
60
|
+
export interface ExtensionEvent extends ProtectionEvent {
|
|
61
|
+
type: ProtectionEventType.EXTENSION_DETECTED;
|
|
62
|
+
data: EventDataMap[ProtectionEventType.EXTENSION_DETECTED];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Event for screenshot detection
|
|
66
|
+
*/
|
|
67
|
+
export interface ScreenshotEvent extends ProtectionEvent {
|
|
68
|
+
type: ProtectionEventType.SCREENSHOT_ATTEMPT;
|
|
69
|
+
data: EventDataMap[ProtectionEventType.SCREENSHOT_ATTEMPT];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Event for frame embedding detection
|
|
73
|
+
*/
|
|
74
|
+
export interface FrameEmbeddingEvent extends ProtectionEvent {
|
|
75
|
+
type: ProtectionEventType.FRAME_EMBEDDING_DETECTED;
|
|
76
|
+
data: EventDataMap[ProtectionEventType.FRAME_EMBEDDING_DETECTED];
|
|
77
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types of protection events that can be published and subscribed to.
|
|
3
|
+
*
|
|
4
|
+
* Only the events listed below are actually wired through the mediator today.
|
|
5
|
+
* The mediator pattern serves a specific case: a detect-and-react fan-out where
|
|
6
|
+
* one detection signal needs to coordinate multiple visible reactions (overlay,
|
|
7
|
+
* content hiding, telemetry). That's why only 4 of the 10 strategies use it —
|
|
8
|
+
* DevTools, Extension, IFrame, and Screenshot. The other 6 (Clipboard,
|
|
9
|
+
* Selection, ContextMenu, Keyboard, Print, Watermark) are direct-blocking
|
|
10
|
+
* strategies where the handler IS the strategy; routing them through the
|
|
11
|
+
* mediator would add ceremony for no decoupling benefit.
|
|
12
|
+
*
|
|
13
|
+
* If you need a NEW event type, add it here AND give it a `[NewType]: { ... }`
|
|
14
|
+
* entry in {@link EventDataMap}.
|
|
15
|
+
*/
|
|
16
|
+
export var ProtectionEventType;
|
|
17
|
+
(function (ProtectionEventType) {
|
|
18
|
+
// Strategy lifecycle
|
|
19
|
+
ProtectionEventType["STRATEGY_REMOVED"] = "strategy:removed";
|
|
20
|
+
// Detection signals (published by the detect-and-react strategies)
|
|
21
|
+
ProtectionEventType["DEVTOOLS_STATE_CHANGE"] = "protection:devtools";
|
|
22
|
+
ProtectionEventType["EXTENSION_DETECTED"] = "protection:extension";
|
|
23
|
+
ProtectionEventType["FRAME_EMBEDDING_DETECTED"] = "protection:frame";
|
|
24
|
+
ProtectionEventType["SCREENSHOT_ATTEMPT"] = "protection:screenshot";
|
|
25
|
+
// Overlay coordination
|
|
26
|
+
ProtectionEventType["OVERLAY_SHOWN"] = "overlay:shown";
|
|
27
|
+
ProtectionEventType["OVERLAY_REMOVED"] = "overlay:removed";
|
|
28
|
+
ProtectionEventType["OVERLAY_RESTORED"] = "overlay:restored";
|
|
29
|
+
// Content coordination
|
|
30
|
+
ProtectionEventType["CONTENT_HIDDEN"] = "content:hidden";
|
|
31
|
+
ProtectionEventType["CONTENT_RESTORED"] = "content:restored";
|
|
32
|
+
})(ProtectionEventType || (ProtectionEventType = {}));
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { EventDataMap } from './eventDataTypes';
|
|
2
|
+
import { ProtectionEvent, ProtectionEventType } from './protection-event';
|
|
3
|
+
/**
|
|
4
|
+
* Event handler function type
|
|
5
|
+
*/
|
|
6
|
+
export type ProtectionEventHandler = (event: ProtectionEvent) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Subscription information
|
|
9
|
+
*/
|
|
10
|
+
export interface Subscription {
|
|
11
|
+
/**
|
|
12
|
+
* Unique ID for the subscription
|
|
13
|
+
*/
|
|
14
|
+
id: string;
|
|
15
|
+
/**
|
|
16
|
+
* Event type being subscribed to
|
|
17
|
+
*/
|
|
18
|
+
eventType: ProtectionEventType;
|
|
19
|
+
/**
|
|
20
|
+
* Handler function for the event
|
|
21
|
+
*/
|
|
22
|
+
handler: ProtectionEventHandler;
|
|
23
|
+
/**
|
|
24
|
+
* Optional filter function to determine if the handler should be called
|
|
25
|
+
*/
|
|
26
|
+
filter?: (event: ProtectionEvent) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Optional priority for the handler (higher numbers execute first)
|
|
29
|
+
*/
|
|
30
|
+
priority?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Optional context information (e.g., strategy name)
|
|
33
|
+
*/
|
|
34
|
+
context?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Subscription options
|
|
38
|
+
*/
|
|
39
|
+
export interface SubscriptionOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Optional filter function to determine if the handler should be called
|
|
42
|
+
*/
|
|
43
|
+
filter?: (event: ProtectionEvent) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Optional priority for the handler (higher numbers execute first)
|
|
46
|
+
*/
|
|
47
|
+
priority?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Optional context information (e.g., strategy name)
|
|
50
|
+
*/
|
|
51
|
+
context?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Interface for the protection mediator
|
|
55
|
+
*/
|
|
56
|
+
export interface ProtectionMediator {
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to an event
|
|
59
|
+
* @param eventType Type of event to subscribe to
|
|
60
|
+
* @param handler Handler function for the event
|
|
61
|
+
* @param options Optional subscription options
|
|
62
|
+
* @returns Subscription ID for later unsubscribing
|
|
63
|
+
*/
|
|
64
|
+
subscribe(eventType: ProtectionEventType, handler: ProtectionEventHandler, options?: SubscriptionOptions): string;
|
|
65
|
+
/**
|
|
66
|
+
* Unsubscribe from an event
|
|
67
|
+
* @param subscriptionId ID of the subscription to remove
|
|
68
|
+
* @returns True if the subscription was found and removed
|
|
69
|
+
*/
|
|
70
|
+
unsubscribe(subscriptionId: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Publish an event to all subscribers
|
|
73
|
+
* @param event Event to publish
|
|
74
|
+
*/
|
|
75
|
+
publish<T extends ProtectionEventType | string>(event: Omit<ProtectionEvent, "data"> & {
|
|
76
|
+
type: T;
|
|
77
|
+
data?: T extends ProtectionEventType ? EventDataMap[T] : Record<string, unknown>;
|
|
78
|
+
}): void;
|
|
79
|
+
/**
|
|
80
|
+
* Get all subscriptions for a specific event type
|
|
81
|
+
* @param eventType Type of event to get subscriptions for
|
|
82
|
+
* @returns Array of subscriptions
|
|
83
|
+
*/
|
|
84
|
+
getSubscriptions(eventType: ProtectionEventType): Subscription[];
|
|
85
|
+
/**
|
|
86
|
+
* Set debug mode
|
|
87
|
+
* @param enabled Whether debug mode should be enabled
|
|
88
|
+
*/
|
|
89
|
+
setDebugMode(enabled: boolean): void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Interface for components that can work with the mediator
|
|
93
|
+
*/
|
|
94
|
+
export interface MediatorAware {
|
|
95
|
+
/**
|
|
96
|
+
* Set the mediator for this component
|
|
97
|
+
* @param mediator The protection mediator
|
|
98
|
+
*/
|
|
99
|
+
setMediator(mediator: ProtectionMediator): void;
|
|
100
|
+
/**
|
|
101
|
+
* Get the component name
|
|
102
|
+
* Used to identify the component in mediator communications
|
|
103
|
+
*/
|
|
104
|
+
readonly COMPONENT_NAME: string;
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { assess } from './assess.js';
|
|
2
|
+
export type { ShieldAssessment, ShieldSignals, ShieldRisk, AssessOptions } from './types/assessment.js';
|
|
3
|
+
export { ContentProtector } from './core/index.js';
|
|
4
|
+
export * from './types/index.js';
|
|
5
|
+
export * from './strategies/index.js';
|
|
6
|
+
export * from './utils/index.js';
|
|
7
|
+
export { attachShieldToSpan } from './otel.js';
|
|
8
|
+
export type { SpanEmitter } from './otel.js';
|
|
9
|
+
export { assessAndProtect } from './policy.js';
|
|
10
|
+
export type { PolicyRule, PolicyCondition, PolicyEngineOptions, PolicyResult, StrategyKey } from './policy.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { assess } from './assess.js';
|
|
2
|
+
export { ContentProtector } from './core/index.js';
|
|
3
|
+
export * from './types/index.js';
|
|
4
|
+
export * from './strategies/index.js';
|
|
5
|
+
export * from './utils/index.js';
|
|
6
|
+
export { attachShieldToSpan } from './otel.js';
|
|
7
|
+
export { assessAndProtect } from './policy.js';
|
package/dist/otel.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ContentProtector } from '@/core/index.js';
|
|
2
|
+
import type { ContentProtectionOptions } from '@/types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* A function that records one Shield security event as an immediately-ending
|
|
5
|
+
* span (or any other sink). Keeping it framework-agnostic — Shield does not
|
|
6
|
+
* depend on @opentelemetry/api; callers provide the emitter.
|
|
7
|
+
*
|
|
8
|
+
* @example with Blindspot:
|
|
9
|
+
* import { getTracer, getRouteContext } from '@tindalabs/blindspot';
|
|
10
|
+
* const emitter: SpanEmitter = (name, attrs) => {
|
|
11
|
+
* const span = getTracer().startSpan(name, { attributes: attrs }, getRouteContext());
|
|
12
|
+
* span.end();
|
|
13
|
+
* };
|
|
14
|
+
*/
|
|
15
|
+
export type SpanEmitter = (name: string, attrs?: Record<string, string | number | boolean>) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a ContentProtector with all callbacks wired to the provided SpanEmitter.
|
|
18
|
+
* Each security event fires a call to emitter(), which should create and immediately
|
|
19
|
+
* end a child span — so events are exported to Tempo without waiting for the
|
|
20
|
+
* long-lived navigation span to close.
|
|
21
|
+
*
|
|
22
|
+
* Any existing customHandlers in options are preserved and called after the emit.
|
|
23
|
+
*/
|
|
24
|
+
export declare function attachShieldToSpan(options: ContentProtectionOptions, emitter: SpanEmitter): ContentProtector;
|