@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.
Files changed (118) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +357 -0
  3. package/dist/assess.d.ts +16 -0
  4. package/dist/assess.js +220 -0
  5. package/dist/config/default-extensions-config.json +103 -0
  6. package/dist/core/ContentProtector.d.ts +63 -0
  7. package/dist/core/ContentProtector.js +281 -0
  8. package/dist/core/index.d.ts +1 -0
  9. package/dist/core/index.js +2 -0
  10. package/dist/core/mediator/ContentProtectionMediator.d.ts +86 -0
  11. package/dist/core/mediator/ContentProtectionMediator.js +238 -0
  12. package/dist/core/mediator/eventDataTypes.d.ts +112 -0
  13. package/dist/core/mediator/eventDataTypes.js +23 -0
  14. package/dist/core/mediator/handlers/abstractEventHandler.d.ts +41 -0
  15. package/dist/core/mediator/handlers/abstractEventHandler.js +59 -0
  16. package/dist/core/mediator/handlers/devToolsEventHandler.d.ts +9 -0
  17. package/dist/core/mediator/handlers/devToolsEventHandler.js +95 -0
  18. package/dist/core/mediator/handlers/eventHandlerRegistry.d.ts +9 -0
  19. package/dist/core/mediator/handlers/eventHandlerRegistry.js +34 -0
  20. package/dist/core/mediator/handlers/extensionEventHandlers.d.ts +40 -0
  21. package/dist/core/mediator/handlers/extensionEventHandlers.js +140 -0
  22. package/dist/core/mediator/handlers/iFrameEventHandlers.d.ts +27 -0
  23. package/dist/core/mediator/handlers/iFrameEventHandlers.js +93 -0
  24. package/dist/core/mediator/handlers/screenShotEventHandlers.d.ts +34 -0
  25. package/dist/core/mediator/handlers/screenShotEventHandlers.js +111 -0
  26. package/dist/core/mediator/protection-event.d.ts +77 -0
  27. package/dist/core/mediator/protection-event.js +32 -0
  28. package/dist/core/mediator/types.d.ts +105 -0
  29. package/dist/core/mediator/types.js +1 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +7 -0
  32. package/dist/otel.d.ts +24 -0
  33. package/dist/otel.js +83 -0
  34. package/dist/policy.d.ts +98 -0
  35. package/dist/policy.js +97 -0
  36. package/dist/strategies/AbstractStrategy.d.ts +124 -0
  37. package/dist/strategies/AbstractStrategy.js +256 -0
  38. package/dist/strategies/ClipboardStrategy.d.ts +67 -0
  39. package/dist/strategies/ClipboardStrategy.js +291 -0
  40. package/dist/strategies/ContextMenuStrategy.d.ts +60 -0
  41. package/dist/strategies/ContextMenuStrategy.js +454 -0
  42. package/dist/strategies/DevToolsStrategy.d.ts +55 -0
  43. package/dist/strategies/DevToolsStrategy.js +314 -0
  44. package/dist/strategies/ExtensionStrategy.d.ts +66 -0
  45. package/dist/strategies/ExtensionStrategy.js +486 -0
  46. package/dist/strategies/IFrameStrategy.d.ts +49 -0
  47. package/dist/strategies/IFrameStrategy.js +255 -0
  48. package/dist/strategies/KeyboardStrategy.d.ts +35 -0
  49. package/dist/strategies/KeyboardStrategy.js +130 -0
  50. package/dist/strategies/PrintStrategy.d.ts +47 -0
  51. package/dist/strategies/PrintStrategy.js +201 -0
  52. package/dist/strategies/ScreenshotStrategy.d.ts +90 -0
  53. package/dist/strategies/ScreenshotStrategy.js +502 -0
  54. package/dist/strategies/SelectionStrategy.d.ts +49 -0
  55. package/dist/strategies/SelectionStrategy.js +216 -0
  56. package/dist/strategies/WatermarkStrategy.d.ts +56 -0
  57. package/dist/strategies/WatermarkStrategy.js +287 -0
  58. package/dist/strategies/index.d.ts +10 -0
  59. package/dist/strategies/index.js +11 -0
  60. package/dist/types/assessment.d.ts +62 -0
  61. package/dist/types/assessment.js +1 -0
  62. package/dist/types/index.d.ts +278 -0
  63. package/dist/types/index.js +17 -0
  64. package/dist/utils/DOMObserver.d.ts +68 -0
  65. package/dist/utils/DOMObserver.js +134 -0
  66. package/dist/utils/base/LoggableComponent.d.ts +44 -0
  67. package/dist/utils/base/LoggableComponent.js +56 -0
  68. package/dist/utils/detectors/AbstractDevToolsDetector.d.ts +98 -0
  69. package/dist/utils/detectors/AbstractDevToolsDetector.js +127 -0
  70. package/dist/utils/detectors/dateToStringDetector.d.ts +43 -0
  71. package/dist/utils/detectors/dateToStringDetector.js +96 -0
  72. package/dist/utils/detectors/debugLibDetector.d.ts +64 -0
  73. package/dist/utils/detectors/debugLibDetector.js +195 -0
  74. package/dist/utils/detectors/debuggerDetector.d.ts +51 -0
  75. package/dist/utils/detectors/debuggerDetector.js +211 -0
  76. package/dist/utils/detectors/defineGetterDetector.d.ts +48 -0
  77. package/dist/utils/detectors/defineGetterDetector.js +150 -0
  78. package/dist/utils/detectors/detectorInterface.d.ts +36 -0
  79. package/dist/utils/detectors/detectorInterface.js +1 -0
  80. package/dist/utils/detectors/devToolsDetectorManager.d.ts +88 -0
  81. package/dist/utils/detectors/devToolsDetectorManager.js +243 -0
  82. package/dist/utils/detectors/funcToStringDetector.d.ts +43 -0
  83. package/dist/utils/detectors/funcToStringDetector.js +90 -0
  84. package/dist/utils/detectors/regToStringDetector.d.ts +43 -0
  85. package/dist/utils/detectors/regToStringDetector.js +129 -0
  86. package/dist/utils/detectors/sizeDetector.d.ts +54 -0
  87. package/dist/utils/detectors/sizeDetector.js +134 -0
  88. package/dist/utils/detectors/timingDetector.d.ts +55 -0
  89. package/dist/utils/detectors/timingDetector.js +143 -0
  90. package/dist/utils/dom.d.ts +20 -0
  91. package/dist/utils/dom.js +83 -0
  92. package/dist/utils/environment.d.ts +29 -0
  93. package/dist/utils/environment.js +267 -0
  94. package/dist/utils/eventManager.d.ts +162 -0
  95. package/dist/utils/eventManager.js +548 -0
  96. package/dist/utils/index.d.ts +2 -0
  97. package/dist/utils/index.js +3 -0
  98. package/dist/utils/intervalManager.d.ts +91 -0
  99. package/dist/utils/intervalManager.js +221 -0
  100. package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.d.ts +41 -0
  101. package/dist/utils/keyboardShortcutManager/keyboardShortcutManager.js +135 -0
  102. package/dist/utils/keyboardShortcutManager/keyboardShortcuts.d.ts +18 -0
  103. package/dist/utils/keyboardShortcutManager/keyboardShortcuts.js +195 -0
  104. package/dist/utils/logging/simple/Loggable.d.ts +33 -0
  105. package/dist/utils/logging/simple/Loggable.js +1 -0
  106. package/dist/utils/logging/simple/LoggingDelegate.d.ts +42 -0
  107. package/dist/utils/logging/simple/LoggingDelegate.js +53 -0
  108. package/dist/utils/logging/simple/SimpleLoggingService.d.ts +39 -0
  109. package/dist/utils/logging/simple/SimpleLoggingService.js +58 -0
  110. package/dist/utils/orientation.d.ts +15 -0
  111. package/dist/utils/orientation.js +32 -0
  112. package/dist/utils/protectedContentManager.d.ts +155 -0
  113. package/dist/utils/protectedContentManager.js +424 -0
  114. package/dist/utils/securityOverlayManager.d.ts +253 -0
  115. package/dist/utils/securityOverlayManager.js +786 -0
  116. package/dist/utils/timeoutManager.d.ts +50 -0
  117. package/dist/utils/timeoutManager.js +113 -0
  118. package/package.json +61 -0
@@ -0,0 +1,216 @@
1
+ import { isBrowser, isMobile } from "../utils/environment";
2
+ import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
3
+ /**
4
+ * Strategy for preventing text selection
5
+ */
6
+ export class SelectionStrategy extends AbstractStrategy {
7
+ /**
8
+ * Create a new SelectionStrategy
9
+ * @param targetElement Element to protect (defaults to document.body)
10
+ * @param customHandler Optional custom handler for selection attempts
11
+ * @param debugMode Enable debug mode for troubleshooting
12
+ */
13
+ constructor(targetElement, customHandler, debugMode = false) {
14
+ super("SelectionStrategy", debugMode);
15
+ this.targetElement = null;
16
+ this.styleElement = null;
17
+ this.preventDrag = true;
18
+ this.targetElement = targetElement || (isBrowser() ? document.body : null);
19
+ this.customHandler = customHandler;
20
+ this.selectionHandler = this.handleSelection.bind(this);
21
+ this.dragHandler = this.handleDrag.bind(this);
22
+ this.log("Initialized with target:", this.targetElement === document.body ? "document.body" : "custom element");
23
+ }
24
+ /**
25
+ * Handle selection event
26
+ */
27
+ handleSelection(e) {
28
+ return (this.safeExecute("handleSelection", StrategyErrorType.EVENT_HANDLING, () => {
29
+ this.log("Selection attempt detected", {
30
+ eventType: e.type,
31
+ target: e.target,
32
+ });
33
+ // Call custom handler if provided
34
+ if (this.customHandler) {
35
+ this.customHandler(e);
36
+ }
37
+ if (isBrowser() && window.getSelection) {
38
+ const selection = window.getSelection();
39
+ if (selection) {
40
+ selection.removeAllRanges();
41
+ }
42
+ }
43
+ e.preventDefault();
44
+ e.stopPropagation();
45
+ return false;
46
+ }) || false);
47
+ }
48
+ /**
49
+ * Handle drag event
50
+ */
51
+ handleDrag(e) {
52
+ this.safeExecute("handleDrag", StrategyErrorType.EVENT_HANDLING, () => {
53
+ this.log("Drag attempt detected", {
54
+ eventType: e.type,
55
+ target: e.target,
56
+ });
57
+ // Call custom handler if provided
58
+ if (this.customHandler) {
59
+ this.customHandler(e);
60
+ }
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ });
64
+ }
65
+ /**
66
+ * Inject CSS to prevent selection
67
+ */
68
+ injectSelectionStyles() {
69
+ this.safeExecute("injectSelectionStyles", StrategyErrorType.APPLICATION, () => {
70
+ if (!isBrowser())
71
+ return;
72
+ this.styleElement = document.createElement("style");
73
+ this.styleElement.setAttribute("type", "text/css");
74
+ this.styleElement.setAttribute("data-content-security", "selection-blocker");
75
+ const selector = this.targetElement === document.body ? "body" : ".protected-content";
76
+ const css = `
77
+ ${selector} {
78
+ -webkit-user-select: none;
79
+ -moz-user-select: none;
80
+ -ms-user-select: none;
81
+ user-select: none;
82
+ }
83
+
84
+ ${selector} ::selection {
85
+ background: transparent;
86
+ }
87
+ `;
88
+ this.styleElement.textContent = css;
89
+ document.head.appendChild(this.styleElement);
90
+ // Add class if not targeting body
91
+ if (this.targetElement !== document.body) {
92
+ this.targetElement?.classList.add("protected-content");
93
+ }
94
+ this.log("Selection-blocking CSS injected");
95
+ });
96
+ }
97
+ /**
98
+ * Remove selection-blocking CSS
99
+ */
100
+ removeSelectionStyles() {
101
+ this.safeExecute("removeSelectionStyles", StrategyErrorType.REMOVAL, () => {
102
+ if (!this.styleElement || !isBrowser())
103
+ return;
104
+ try {
105
+ document.head.removeChild(this.styleElement);
106
+ this.styleElement = null;
107
+ // Remove class if not targeting body
108
+ if (this.targetElement !== document.body) {
109
+ this.targetElement?.classList.remove("protected-content");
110
+ }
111
+ this.log("Selection-blocking CSS removed");
112
+ }
113
+ catch (error) {
114
+ this.handleError(StrategyErrorType.REMOVAL, "Error removing selection styles", error);
115
+ // Try to find and remove by selector as fallback
116
+ try {
117
+ const styles = document.querySelectorAll('style[data-content-security="selection-blocker"]');
118
+ styles.forEach((style) => {
119
+ if (style.parentNode) {
120
+ style.parentNode.removeChild(style);
121
+ }
122
+ });
123
+ this.styleElement = null;
124
+ if (styles.length > 0) {
125
+ this.log(`Removed ${styles.length} selection-blocking styles by selector`);
126
+ }
127
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
128
+ }
129
+ catch (fallbackError) {
130
+ // Last resort fallback
131
+ this.styleElement = null;
132
+ }
133
+ }
134
+ });
135
+ }
136
+ /**
137
+ * Apply selection protection
138
+ */
139
+ apply() {
140
+ this.safeExecute("apply", StrategyErrorType.APPLICATION, () => {
141
+ if (this.isAppliedFlag || !this.targetElement)
142
+ return;
143
+ this.log("Applying selection protection", {
144
+ targetElement: this.targetElement === document.body ? "document.body" : "custom element",
145
+ hasCustomHandler: !!this.customHandler,
146
+ preventDrag: this.preventDrag,
147
+ isMobile: isMobile(),
148
+ });
149
+ // Add CSS
150
+ this.injectSelectionStyles();
151
+ // Add event listeners using the registerEvent method from AbstractStrategy
152
+ this.registerEvent(this.targetElement, "selectstart", this.selectionHandler);
153
+ this.registerEvent(this.targetElement, "mousedown", this.selectionHandler, { capture: true });
154
+ // Disable drag
155
+ if (this.preventDrag) {
156
+ this.registerEvent(this.targetElement, "dragstart", this.dragHandler);
157
+ }
158
+ this.isAppliedFlag = true;
159
+ });
160
+ }
161
+ /**
162
+ * Remove selection protection
163
+ */
164
+ remove() {
165
+ this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
166
+ if (!this.isAppliedFlag || !this.targetElement)
167
+ return;
168
+ // Remove CSS
169
+ this.removeSelectionStyles();
170
+ // Remove all events for this owner using the parent class method
171
+ this.removeEventsByOwner();
172
+ // Second attempt - try direct DOM removal as fallback
173
+ try {
174
+ if (this.targetElement) {
175
+ this.targetElement.removeEventListener("selectstart", this.selectionHandler);
176
+ this.targetElement.removeEventListener("mousedown", this.selectionHandler, { capture: true });
177
+ this.targetElement.removeEventListener("dragstart", this.dragHandler);
178
+ this.log("Removed events via direct DOM API");
179
+ }
180
+ }
181
+ catch (domError) {
182
+ // Ignore errors in direct DOM removal
183
+ this.handleError(StrategyErrorType.REMOVAL, "Error in fallback DOM removal", domError);
184
+ }
185
+ // Clear tracked event IDs
186
+ this.eventIds = [];
187
+ this.isAppliedFlag = false;
188
+ this.log("Selection protection removed");
189
+ });
190
+ }
191
+ /**
192
+ * Update selection protection options
193
+ * @param options New options for selection protection
194
+ */
195
+ updateOptions(options) {
196
+ this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
197
+ this.log("Updating options", options);
198
+ // Update debug mode if specified
199
+ if (options.debugMode !== undefined) {
200
+ this.setDebugMode(!!options.debugMode);
201
+ }
202
+ // Update preventDrag if specified
203
+ if (options.preventDrag !== undefined) {
204
+ const oldPreventDrag = this.preventDrag;
205
+ this.preventDrag = !!options.preventDrag;
206
+ // If we need to update the applied strategy
207
+ if (this.isAppliedFlag && oldPreventDrag !== this.preventDrag) {
208
+ // Remove and reapply to update event listeners
209
+ this.remove();
210
+ this.apply();
211
+ this.log("Reapplied with updated options");
212
+ }
213
+ }
214
+ });
215
+ }
216
+ }
@@ -0,0 +1,56 @@
1
+ import type { WatermarkOptions } from "../types";
2
+ import { AbstractStrategy } from "./AbstractStrategy";
3
+ /**
4
+ * Strategy for adding watermarks to content
5
+ */
6
+ export declare class WatermarkStrategy extends AbstractStrategy {
7
+ private targetElement;
8
+ private options;
9
+ private watermarkElements;
10
+ private domObserver;
11
+ private isFullPageWatermark;
12
+ private watermarkContainer;
13
+ private osInfo;
14
+ /**
15
+ * Unique per-instance id. Watermark containers are tagged with this so that
16
+ * cleanup queries only ever match THIS instance's container. Without it, two
17
+ * concurrent WatermarkStrategy instances (e.g. two ContentProtectors on the
18
+ * same or nested elements) would delete each other's containers, and each
19
+ * auto-restore would re-trigger the other's observer — an infinite
20
+ * MutationObserver loop that freezes the page.
21
+ */
22
+ private readonly instanceId;
23
+ /**
24
+ * Create a new WatermarkStrategy
25
+ * @param targetElement Element to watermark (defaults to document.body)
26
+ * @param options Watermark options
27
+ * @param debugMode Enable debug mode for troubleshooting
28
+ */
29
+ constructor(options?: WatermarkOptions, targetElement?: HTMLElement | null, debugMode?: boolean);
30
+ /**
31
+ * Create watermarks
32
+ */
33
+ private createWatermarks;
34
+ /**
35
+ * Set up DOM observer to detect watermark removal
36
+ */
37
+ private setupObserver;
38
+ /**
39
+ * Remove watermark elements
40
+ */
41
+ private removeWatermarkElements;
42
+ /**
43
+ * Apply watermark protection
44
+ */
45
+ apply(): void;
46
+ /**
47
+ * Remove watermark protection
48
+ * Override the base implementation to handle additional cleanup
49
+ */
50
+ remove(): void;
51
+ /**
52
+ * Update watermark options
53
+ * @param options New watermark options
54
+ */
55
+ updateOptions(options: Record<string, unknown>): void;
56
+ }
@@ -0,0 +1,287 @@
1
+ import { DomObserver } from "../utils/DOMObserver";
2
+ import { isBrowser, getOS } from "../utils/environment";
3
+ import { AbstractStrategy, StrategyErrorType } from "./AbstractStrategy";
4
+ /**
5
+ * Strategy for adding watermarks to content
6
+ */
7
+ export class WatermarkStrategy extends AbstractStrategy {
8
+ /**
9
+ * Create a new WatermarkStrategy
10
+ * @param targetElement Element to watermark (defaults to document.body)
11
+ * @param options Watermark options
12
+ * @param debugMode Enable debug mode for troubleshooting
13
+ */
14
+ constructor(options, targetElement, debugMode = false) {
15
+ super("WatermarkStrategy", debugMode);
16
+ this.targetElement = null;
17
+ this.watermarkElements = [];
18
+ this.domObserver = null;
19
+ this.isFullPageWatermark = false;
20
+ this.watermarkContainer = null;
21
+ /**
22
+ * Unique per-instance id. Watermark containers are tagged with this so that
23
+ * cleanup queries only ever match THIS instance's container. Without it, two
24
+ * concurrent WatermarkStrategy instances (e.g. two ContentProtectors on the
25
+ * same or nested elements) would delete each other's containers, and each
26
+ * auto-restore would re-trigger the other's observer — an infinite
27
+ * MutationObserver loop that freezes the page.
28
+ */
29
+ this.instanceId = `wm-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
30
+ this.targetElement = targetElement || (isBrowser() ? document.body : null);
31
+ this.options = {
32
+ text: "CONFIDENTIAL",
33
+ opacity: 0.15,
34
+ density: 3,
35
+ ...options,
36
+ };
37
+ this.osInfo = getOS();
38
+ this.watermarkElements = [];
39
+ // Determine if we're doing a full-page watermark
40
+ if (isBrowser() && this.targetElement === document.body) {
41
+ this.isFullPageWatermark = true;
42
+ }
43
+ this.log("Initialized with OS:", this.osInfo, "Full-page:", this.isFullPageWatermark);
44
+ }
45
+ /**
46
+ * Create watermarks
47
+ */
48
+ createWatermarks() {
49
+ return this.safeExecute("createWatermarks", StrategyErrorType.APPLICATION, () => {
50
+ if (!this.targetElement || !isBrowser())
51
+ return;
52
+ // Check if THIS instance's watermark container already exists. Scoped to
53
+ // instanceId so we never remove a container owned by another concurrent
54
+ // WatermarkStrategy (doing so would start a cross-instance restore loop).
55
+ const existingContainer = document.querySelector(`[data-watermark-instance="${this.instanceId}"]`);
56
+ if (existingContainer) {
57
+ this.log("Watermark container already exists, removing first");
58
+ if (existingContainer.parentNode) {
59
+ existingContainer.parentNode.removeChild(existingContainer);
60
+ }
61
+ }
62
+ // Clear any existing watermarks
63
+ this.removeWatermarkElements();
64
+ // Calculate density
65
+ const density = Math.min(Math.max(this.options.density || 3, 1), 10);
66
+ const rows = density * 3;
67
+ const cols = density * 3;
68
+ // Create watermark container
69
+ const container = document.createElement("div");
70
+ container.className = "content-security-watermark-container";
71
+ container.setAttribute("data-watermark-id", `watermark-${Date.now()}`);
72
+ container.setAttribute("data-watermark-instance", this.instanceId);
73
+ this.watermarkContainer = container;
74
+ // Set container styles based on whether it's full-page or element-specific
75
+ if (this.isFullPageWatermark) {
76
+ // Full-page watermark (fixed position covering viewport)
77
+ Object.assign(container.style, {
78
+ position: "fixed",
79
+ top: "0",
80
+ left: "0",
81
+ width: "100%",
82
+ height: "100%",
83
+ overflow: "hidden",
84
+ pointerEvents: "none",
85
+ zIndex: "2147483647", // Max z-index
86
+ userSelect: "none",
87
+ });
88
+ }
89
+ else {
90
+ // Element-specific watermark
91
+ // Get the computed style of the target element
92
+ const targetStyle = window.getComputedStyle(this.targetElement);
93
+ const targetPosition = targetStyle.position;
94
+ // If the target element doesn't have a position set, we need to set it to relative
95
+ // so that our absolutely positioned watermark container stays within it
96
+ if (targetPosition === "static") {
97
+ this.targetElement.style.position = "relative";
98
+ }
99
+ Object.assign(container.style, {
100
+ position: "absolute",
101
+ top: "0",
102
+ left: "0",
103
+ width: "100%",
104
+ height: "100%",
105
+ overflow: "hidden",
106
+ pointerEvents: "none",
107
+ zIndex: "999", // High z-index but not max to avoid breaking page layout
108
+ userSelect: "none",
109
+ });
110
+ }
111
+ // Generate timestamp and user info for watermark
112
+ const timestamp = new Date().toISOString();
113
+ const userInfo = this.options.userId ? ` - User: ${this.options.userId}` : "";
114
+ const watermarkText = `${this.options.text}${userInfo} - ${timestamp}`;
115
+ // Create watermark pattern
116
+ for (let i = 0; i < rows; i++) {
117
+ for (let j = 0; j < cols; j++) {
118
+ const watermark = document.createElement("div");
119
+ watermark.className = "content-security-watermark";
120
+ watermark.textContent = watermarkText;
121
+ // Position watermark - use % for element-specific watermarks instead of vh/vw
122
+ const positionUnit = this.isFullPageWatermark ? "vh" : "%";
123
+ const horizontalUnit = this.isFullPageWatermark ? "vw" : "%";
124
+ Object.assign(watermark.style, {
125
+ position: "absolute",
126
+ top: `${(i * 100) / rows}${positionUnit}`,
127
+ left: `${(j * 100) / cols}${horizontalUnit}`,
128
+ transform: "rotate(-45deg) translateX(-30%) translateY(-200%)",
129
+ transformOrigin: "center",
130
+ opacity: String(this.options.opacity || 0.15),
131
+ fontSize: this.isFullPageWatermark ? "16px" : "14px", // Slightly smaller for element watermarks
132
+ color: "rgba(0, 0, 0, 0.7)",
133
+ whiteSpace: "nowrap",
134
+ pointerEvents: "none",
135
+ userSelect: "none",
136
+ ...this.options.style,
137
+ });
138
+ container.appendChild(watermark);
139
+ this.watermarkElements.push(watermark);
140
+ }
141
+ }
142
+ // Add container to target
143
+ this.targetElement.appendChild(container);
144
+ this.watermarkElements.push(container);
145
+ this.log("Created watermark container with", this.watermarkElements.length - 1, "watermarks");
146
+ // Set up observer to detect if watermarks are removed
147
+ this.setupObserver();
148
+ });
149
+ }
150
+ /**
151
+ * Set up DOM observer to detect watermark removal
152
+ */
153
+ setupObserver() {
154
+ return this.safeExecute("setupObserver", StrategyErrorType.APPLICATION, () => {
155
+ if (!this.targetElement || !isBrowser())
156
+ return;
157
+ // Create a handler for element removal
158
+ const handleElementsRemoved = (removedElements) => {
159
+ this.log("Watermark elements removed from DOM", removedElements);
160
+ // Only restore if auto-restore is enabled
161
+ if (this.isAppliedFlag) {
162
+ this.log("Auto-restoring watermarks");
163
+ // Restore the watermarks
164
+ this.createWatermarks();
165
+ }
166
+ };
167
+ // Create a new observer if needed
168
+ if (!this.domObserver) {
169
+ this.domObserver = new DomObserver({
170
+ targetElement: this.targetElement,
171
+ elementsToWatch: this.watermarkContainer ? [this.watermarkContainer] : [],
172
+ onElementsRemoved: handleElementsRemoved,
173
+ observeSubtree: true,
174
+ debugMode: this.debugMode,
175
+ name: "WatermarkStrategy",
176
+ });
177
+ }
178
+ else {
179
+ // Update the elements to watch
180
+ this.domObserver.updateElementsToWatch(this.watermarkContainer ? [this.watermarkContainer] : []);
181
+ }
182
+ // Start observing
183
+ this.domObserver.startObserving();
184
+ this.log("DOM observer set up to detect watermark removal");
185
+ });
186
+ }
187
+ /**
188
+ * Remove watermark elements
189
+ */
190
+ removeWatermarkElements() {
191
+ return this.safeExecute("removeWatermarkElements", StrategyErrorType.REMOVAL, () => {
192
+ if (!isBrowser())
193
+ return;
194
+ // First, try to remove by container reference
195
+ if (this.watermarkContainer && this.watermarkContainer.parentNode) {
196
+ this.watermarkContainer.parentNode.removeChild(this.watermarkContainer);
197
+ this.watermarkContainer = null;
198
+ }
199
+ // Then try to remove individual elements
200
+ for (const element of this.watermarkElements) {
201
+ if (element.parentNode) {
202
+ element.parentNode.removeChild(element);
203
+ }
204
+ }
205
+ // Also try to remove by instance id in case references were lost. Scoped
206
+ // to this instance so we never tear down another concurrent strategy's
207
+ // watermark (which would trigger its auto-restore observer in a loop).
208
+ const containers = document.querySelectorAll(`[data-watermark-instance="${this.instanceId}"]`);
209
+ containers.forEach((container) => {
210
+ if (container.parentNode) {
211
+ container.parentNode.removeChild(container);
212
+ }
213
+ });
214
+ // If we modified the target element's position, restore it
215
+ if (!this.isFullPageWatermark && this.targetElement) {
216
+ // Only remove the position if we added it
217
+ // This is a simplification - ideally we'd store the original position
218
+ if (this.targetElement.style.position === "relative") {
219
+ this.targetElement.style.position = "";
220
+ }
221
+ }
222
+ this.watermarkElements = [];
223
+ this.log("Removed all watermark elements");
224
+ });
225
+ }
226
+ /**
227
+ * Apply watermark protection
228
+ */
229
+ apply() {
230
+ return this.safeExecute("apply", StrategyErrorType.APPLICATION, () => {
231
+ if (this.isAppliedFlag) {
232
+ this.log("Already applied, skipping");
233
+ return;
234
+ }
235
+ this.log("Applying watermark protection", {
236
+ text: this.options.text,
237
+ opacity: this.options.opacity,
238
+ density: this.options.density,
239
+ userId: this.options.userId,
240
+ isFullPage: this.isFullPageWatermark,
241
+ os: this.osInfo.name,
242
+ });
243
+ this.createWatermarks();
244
+ this.isAppliedFlag = true;
245
+ });
246
+ }
247
+ /**
248
+ * Remove watermark protection
249
+ * Override the base implementation to handle additional cleanup
250
+ */
251
+ remove() {
252
+ return this.safeExecute("remove", StrategyErrorType.REMOVAL, () => {
253
+ if (!this.isAppliedFlag) {
254
+ this.log("Not applied, skipping removal");
255
+ return;
256
+ }
257
+ if (this.domObserver) {
258
+ this.domObserver.stopObserving();
259
+ this.domObserver = null;
260
+ this.log("DOM observer stopped");
261
+ }
262
+ this.removeWatermarkElements();
263
+ this.isAppliedFlag = false;
264
+ this.log("Watermark protection removed");
265
+ });
266
+ }
267
+ /**
268
+ * Update watermark options
269
+ * @param options New watermark options
270
+ */
271
+ updateOptions(options) {
272
+ return this.safeExecute("updateOptions", StrategyErrorType.OPTION_UPDATE, () => {
273
+ const typedOptions = options;
274
+ this.log("Updating options", typedOptions);
275
+ this.options = {
276
+ ...this.options,
277
+ ...typedOptions,
278
+ };
279
+ if (this.isAppliedFlag) {
280
+ // Reapply with new options
281
+ this.remove();
282
+ this.apply();
283
+ this.log("Reapplied watermarks with updated options");
284
+ }
285
+ });
286
+ }
287
+ }
@@ -0,0 +1,10 @@
1
+ export * from './KeyboardStrategy';
2
+ export * from './ContextMenuStrategy';
3
+ export * from './PrintStrategy';
4
+ export * from './WatermarkStrategy';
5
+ export * from './SelectionStrategy';
6
+ export * from './DevToolsStrategy';
7
+ export * from './ScreenshotStrategy';
8
+ export * from './ExtensionStrategy';
9
+ export * from './IFrameStrategy';
10
+ export * from './ClipboardStrategy';
@@ -0,0 +1,11 @@
1
+ // BARREL FILE
2
+ export * from './KeyboardStrategy';
3
+ export * from './ContextMenuStrategy';
4
+ export * from './PrintStrategy';
5
+ export * from './WatermarkStrategy';
6
+ export * from './SelectionStrategy';
7
+ export * from './DevToolsStrategy';
8
+ export * from './ScreenshotStrategy';
9
+ export * from './ExtensionStrategy';
10
+ export * from './IFrameStrategy';
11
+ export * from './ClipboardStrategy';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Signal keys produced by assess(). All keys follow the shield.* OTel namespace
3
+ * so they can be attached directly to Blindspot / OpenTelemetry spans.
4
+ */
5
+ export interface ShieldSignals {
6
+ /** DevTools panel is open in the current tab. */
7
+ 'shield.devtools.open': boolean;
8
+ /** navigator.webdriver is set — Selenium, Playwright, Puppeteer, etc. */
9
+ 'shield.automation.webdriver': boolean;
10
+ /** Browser is running in headless mode (no visible UI). */
11
+ 'shield.automation.headless': boolean;
12
+ /** Page is embedded inside a cross-origin or unauthorized iframe. */
13
+ 'shield.frame.embedded': boolean;
14
+ /** At least one known browser extension has been detected. */
15
+ 'shield.extension.detected': boolean;
16
+ /** Comma-separated list of detected extension names (empty string if none). */
17
+ 'shield.extension.names': string;
18
+ }
19
+ export interface ShieldRisk {
20
+ /** Normalised threat score in the [0, 1] range. */
21
+ score: number;
22
+ /** Machine-readable flag codes for each active threat. */
23
+ flags: string[];
24
+ }
25
+ /**
26
+ * Structured result returned by assess().
27
+ */
28
+ export interface ShieldAssessment {
29
+ signals: ShieldSignals;
30
+ risk: ShieldRisk;
31
+ /**
32
+ * OTel-compatible span attributes. Only truthy / non-default signal values
33
+ * are included so spans stay lean. Attach these directly to a Blindspot
34
+ * span via span.setAttributes(result.spanAttributes).
35
+ */
36
+ spanAttributes: Record<string, string | boolean | number>;
37
+ }
38
+ /**
39
+ * Options for the assess() call.
40
+ */
41
+ export interface AssessOptions {
42
+ /**
43
+ * Run DevTools detection. Slightly slower (async timing-based).
44
+ * @default true
45
+ */
46
+ devtools?: boolean;
47
+ /**
48
+ * Run browser-extension detection (async DOM scan).
49
+ * @default true
50
+ */
51
+ extensions?: boolean;
52
+ /**
53
+ * Maximum milliseconds to wait for async detections before resolving.
54
+ * @default 400
55
+ */
56
+ timeout?: number;
57
+ /**
58
+ * Inline extension config to check against. If omitted, uses the built-in
59
+ * signatures bundled with Shield.
60
+ */
61
+ extensionConfig?: import('./index.js').ExtensionConfig[];
62
+ }
@@ -0,0 +1 @@
1
+ export {};