ai-appshield 1.0.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 +21 -0
- package/README.md +222 -0
- package/browser/shield.min.js +19 -0
- package/dist/browser/BotDetector.d.ts +9 -0
- package/dist/browser/BotDetector.d.ts.map +1 -0
- package/dist/browser/BotDetector.js +99 -0
- package/dist/browser/BotDetector.js.map +1 -0
- package/dist/browser/ContentProtector.d.ts +27 -0
- package/dist/browser/ContentProtector.d.ts.map +1 -0
- package/dist/browser/ContentProtector.js +136 -0
- package/dist/browser/ContentProtector.js.map +1 -0
- package/dist/browser/DevToolsGuard.d.ts +16 -0
- package/dist/browser/DevToolsGuard.d.ts.map +1 -0
- package/dist/browser/DevToolsGuard.js +89 -0
- package/dist/browser/DevToolsGuard.js.map +1 -0
- package/dist/browser/Fingerprint.d.ts +30 -0
- package/dist/browser/Fingerprint.d.ts.map +1 -0
- package/dist/browser/Fingerprint.js +143 -0
- package/dist/browser/Fingerprint.js.map +1 -0
- package/dist/browser/Shield.d.ts +35 -0
- package/dist/browser/Shield.d.ts.map +1 -0
- package/dist/browser/Shield.js +177 -0
- package/dist/browser/Shield.js.map +1 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +11 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +269 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/config.d.ts +65 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +21 -0
- package/dist/core/config.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/server/BotAnalyzer.d.ts +24 -0
- package/dist/server/BotAnalyzer.d.ts.map +1 -0
- package/dist/server/BotAnalyzer.js +124 -0
- package/dist/server/BotAnalyzer.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +10 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +28 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +63 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/utils/obfuscate.d.ts +21 -0
- package/dist/utils/obfuscate.d.ts.map +1 -0
- package/dist/utils/obfuscate.js +59 -0
- package/dist/utils/obfuscate.js.map +1 -0
- package/dist/utils/signatures.d.ts +13 -0
- package/dist/utils/signatures.d.ts.map +1 -0
- package/dist/utils/signatures.js +87 -0
- package/dist/utils/signatures.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# ai-appshield
|
|
2
|
+
|
|
3
|
+
**NPM wrapper to protect your applications from AI scrapers, bots, and reverse-engineering attempts.**
|
|
4
|
+
|
|
5
|
+
Install it like any npm package, use the CLI to scaffold protection into your project, and enable client + server guards with a few lines of code.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
| Layer | Protection |
|
|
10
|
+
|-------|-----------|
|
|
11
|
+
| **Bot Detection** | Detects Puppeteer, Selenium, Playwright, headless Chrome, and 40+ known bot user-agents |
|
|
12
|
+
| **AI Scraper Blocking** | Blocks GPTBot, ClaudeBot, Bytespider, CCBot, PerplexityBot, and other AI crawlers |
|
|
13
|
+
| **DevTools Guard** | Detects open developer tools via size differential, debugger timing, and console traps |
|
|
14
|
+
| **Content Protection** | Disables right-click, copy, text selection, drag, and print |
|
|
15
|
+
| **Honeypot Traps** | Injects hidden form fields and links that only bots fill/click |
|
|
16
|
+
| **Fingerprinting** | Canvas + WebGL fingerprinting for server-side validation |
|
|
17
|
+
| **Content Obfuscation** | XOR-encode sensitive text in the DOM (`data-shield` attributes) |
|
|
18
|
+
| **Rate Limiting** | Server-side sliding-window rate limiter |
|
|
19
|
+
| **CLI Wrapper** | `ai-appshield init`, `wrap`, `encode`, and `check` commands |
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install ai-appshield
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### CLI — Scaffold protection into your project
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Initialize for your framework
|
|
33
|
+
npx ai-appshield init --framework react
|
|
34
|
+
npx ai-appshield init --framework express
|
|
35
|
+
npx ai-appshield init --framework next
|
|
36
|
+
npx ai-appshield init --framework vanilla
|
|
37
|
+
|
|
38
|
+
# Inject protection script into HTML
|
|
39
|
+
npx ai-appshield wrap --entry index.html
|
|
40
|
+
|
|
41
|
+
# Encode sensitive text for DOM obfuscation
|
|
42
|
+
npx ai-appshield encode "secret API key value"
|
|
43
|
+
|
|
44
|
+
# Check if a user-agent is a bot
|
|
45
|
+
npx ai-appshield check "Mozilla/5.0 (compatible; GPTBot/1.0)"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Browser (Client-Side)
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { protect } from 'ai-appshield/browser';
|
|
52
|
+
|
|
53
|
+
const shield = protect({
|
|
54
|
+
botDetection: true,
|
|
55
|
+
devToolsGuard: true,
|
|
56
|
+
contentProtection: true,
|
|
57
|
+
honeypot: true,
|
|
58
|
+
fingerprint: true,
|
|
59
|
+
threatAction: 'warn', // 'block' | 'warn' | 'redirect'
|
|
60
|
+
onThreatDetected: (threat) => {
|
|
61
|
+
console.warn('Threat:', threat.type, threat.message);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### HTML Script Tag (No Build Step)
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<script src="node_modules/ai-appshield/browser/shield.min.js"></script>
|
|
70
|
+
<script>
|
|
71
|
+
AppShield.protect({
|
|
72
|
+
botDetection: true,
|
|
73
|
+
devToolsGuard: true,
|
|
74
|
+
contentProtection: true,
|
|
75
|
+
threatAction: 'block'
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### React Hook
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { useAppShield } from './hooks/useAppShield';
|
|
84
|
+
|
|
85
|
+
function App() {
|
|
86
|
+
useAppShield();
|
|
87
|
+
return <div>Protected content</div>;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Server (Express / Node.js)
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
const express = require('express');
|
|
95
|
+
const { createShieldMiddleware } = require('ai-appshield/server');
|
|
96
|
+
|
|
97
|
+
const app = express();
|
|
98
|
+
|
|
99
|
+
app.use(createShieldMiddleware({
|
|
100
|
+
botAnalysis: true,
|
|
101
|
+
blockBots: true,
|
|
102
|
+
rateLimit: { maxRequests: 100, windowMs: 60000 },
|
|
103
|
+
blockStatusCode: 403,
|
|
104
|
+
excludePaths: ['/health'],
|
|
105
|
+
onBlocked: (info) => {
|
|
106
|
+
console.warn('Blocked:', info.ip, info.reason);
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
app.get('/api/data', (req, res) => {
|
|
111
|
+
res.json({ secret: 'protected data' });
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Content Obfuscation
|
|
116
|
+
|
|
117
|
+
Encode sensitive text so it's not readable in page source:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npx ai-appshield encode "Premium feature details"
|
|
121
|
+
# Output: <span data-shield="encoded-string"></span>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<span data-shield="KlkPGBEJDw=="></span>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Enable `obfuscateContent: true` in your Shield config to auto-decode at runtime.
|
|
129
|
+
|
|
130
|
+
## Configuration
|
|
131
|
+
|
|
132
|
+
After running `ai-appshield init`, edit `ai-appshield.config.js`:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
module.exports = {
|
|
136
|
+
client: {
|
|
137
|
+
botDetection: true,
|
|
138
|
+
devToolsGuard: true,
|
|
139
|
+
contentProtection: true,
|
|
140
|
+
honeypot: true,
|
|
141
|
+
fingerprint: true,
|
|
142
|
+
obfuscateContent: false,
|
|
143
|
+
threatAction: 'warn',
|
|
144
|
+
redirectUrl: '/blocked',
|
|
145
|
+
reportEndpoint: '/api/shield-report',
|
|
146
|
+
debug: false,
|
|
147
|
+
},
|
|
148
|
+
server: {
|
|
149
|
+
botAnalysis: true,
|
|
150
|
+
blockBots: true,
|
|
151
|
+
requireFingerprint: false,
|
|
152
|
+
rateLimit: { maxRequests: 100, windowMs: 60000 },
|
|
153
|
+
blockStatusCode: 403,
|
|
154
|
+
excludePaths: ['/health', '/api/public'],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## API Reference
|
|
160
|
+
|
|
161
|
+
### `protect(config?)` / `new Shield(config)`
|
|
162
|
+
|
|
163
|
+
| Option | Type | Default | Description |
|
|
164
|
+
|--------|------|---------|-------------|
|
|
165
|
+
| `botDetection` | boolean | `true` | Detect automation tools and headless browsers |
|
|
166
|
+
| `devToolsGuard` | boolean | `true` | Detect open DevTools |
|
|
167
|
+
| `contentProtection` | boolean | `true` | Block copy, right-click, selection |
|
|
168
|
+
| `honeypot` | boolean | `true` | Inject hidden honeypot traps |
|
|
169
|
+
| `fingerprint` | boolean | `true` | Collect browser fingerprint |
|
|
170
|
+
| `obfuscateContent` | boolean | `false` | Decode `data-shield` elements |
|
|
171
|
+
| `threatAction` | string | `'warn'` | `'block'`, `'warn'`, or `'redirect'` |
|
|
172
|
+
| `onThreatDetected` | function | — | Callback on threat detection |
|
|
173
|
+
|
|
174
|
+
### `createShieldMiddleware(config)`
|
|
175
|
+
|
|
176
|
+
| Option | Type | Default | Description |
|
|
177
|
+
|--------|------|---------|-------------|
|
|
178
|
+
| `botAnalysis` | boolean | `true` | Analyze request headers for bot signals |
|
|
179
|
+
| `blockBots` | boolean | `true` | Block known bot user-agents |
|
|
180
|
+
| `rateLimit` | object | — | `{ maxRequests, windowMs }` |
|
|
181
|
+
| `requireFingerprint` | boolean | `false` | Require `X-Ai-Appshield-Fingerprint` header |
|
|
182
|
+
| `excludePaths` | string[] | — | Paths to skip protection |
|
|
183
|
+
|
|
184
|
+
## Architecture
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
┌─────────────────────────────────────────────┐
|
|
188
|
+
│ Your App │
|
|
189
|
+
├──────────────────┬──────────────────────────┤
|
|
190
|
+
│ Browser Shield │ Server Shield │
|
|
191
|
+
│ ├ Bot Detector │ ├ Bot Analyzer │
|
|
192
|
+
│ ├ DevTools Guard│ ├ Rate Limiter │
|
|
193
|
+
│ ├ Content Guard │ ├ Middleware │
|
|
194
|
+
│ ├ Honeypot │ └ Request Validator │
|
|
195
|
+
│ └ Fingerprint │ │
|
|
196
|
+
└──────────────────┴──────────────────────────┘
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Important Notes
|
|
200
|
+
|
|
201
|
+
- **Defense in depth**: No client-side protection is 100% bypassable. Combine browser + server guards for best results.
|
|
202
|
+
- **AI scrapers**: Server-side bot user-agent blocking is the most effective layer against AI crawlers (GPTBot, ClaudeBot, etc.).
|
|
203
|
+
- **Legitimate bots**: Use `excludePaths` or customize `BOT_USER_AGENTS` for search engines you want to allow.
|
|
204
|
+
- **Privacy**: Fingerprinting collects browser characteristics. Disclose this in your privacy policy if required.
|
|
205
|
+
|
|
206
|
+
## Test
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
npm test
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Uses Node.js built-in test runner (`node:test`). Tests live alongside source as `*.test.ts` files and cover:
|
|
213
|
+
|
|
214
|
+
- Content obfuscation encode/decode
|
|
215
|
+
- Bot user-agent analysis and AI crawler blocking
|
|
216
|
+
- Rate limiting and middleware behavior
|
|
217
|
+
- Honeypot validation
|
|
218
|
+
- Default configuration values
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";var AppShield=(()=>{var f=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var E=Object.prototype.hasOwnProperty;var x=(n,e)=>{for(var t in e)f(n,t,{get:e[t],enumerable:!0})},D=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of T(e))!E.call(n,i)&&i!==t&&f(n,i,{get:()=>e[i],enumerable:!(o=_(e,i))||o.enumerable});return n};var C=n=>D(f({},"__esModule",{value:!0}),n);var S={};x(S,{ContentProtector:()=>a,DevToolsGuard:()=>s,Honeypot:()=>c,Shield:()=>u,collectFingerprint:()=>l,createBotThreatEvent:()=>d,detectBot:()=>r,protect:()=>A});var g={botDetection:!0,devToolsGuard:!0,contentProtection:!0,honeypot:!0,fingerprint:!0,obfuscateContent:!1,protectSelector:"body",threatAction:"warn",debug:!1};var m=["webdriver","__webdriver_evaluate","__selenium_evaluate","__webdriver_script_function","__webdriver_script_func","__webdriver_script_fn","__fxdriver_evaluate","__driver_unwrapped","__webdriver_unwrapped","__driver_evaluate","__selenium_unwrapped","__fxdriver_unwrapped","_Selenium_IDE_Recorder","_selenium","calledSelenium","$cdc_asdjflasutopfhvcZLmcfl_","$chrome_asyncScriptInfo","__$webdriverAsyncExecutor","__lastWatirAlert","__lastWatirConfirm","__lastWatirPrompt","_WEBDRIVER_ELEM_CACHE","ChromeDriverw","domAutomation","domAutomationController"];var p={missingPlugins:!0,zeroLanguages:!0,webdriverProperty:!0,chromeRuntimeMissing:!0,permissionsInconsistency:!0};function r(){let n=[],e=0;if(typeof navigator<"u"&&navigator.webdriver&&(n.push("navigator.webdriver is true"),e+=40),typeof window<"u"){for(let o of m)if(o in window||window[o]!==void 0){n.push(`Automation signature found: ${o}`),e+=25;break}}if(typeof navigator<"u"){let o=navigator.userAgent.toLowerCase();o.includes("headless")&&(n.push("Headless user agent detected"),e+=50),p.missingPlugins&&navigator.plugins?.length===0&&(n.push("No browser plugins (headless indicator)"),e+=15),p.zeroLanguages&&navigator.languages?.length===0&&(n.push("No languages configured"),e+=10),p.chromeRuntimeMissing&&o.includes("chrome")&&typeof window.chrome>"u"&&(n.push("Chrome UA without chrome runtime"),e+=20)}if(typeof navigator<"u"&&"permissions"in navigator)try{let o=navigator.permissions;o&&o.query({name:"notifications"}).then(i=>{i.state==="prompt"&&Notification.permission==="denied"&&n.push("Permissions API inconsistency")}).catch(()=>{n.push("Permissions API unavailable"),e+=10})}catch{e+=5}if(typeof window<"u"){let o=window;(o.__playwright||o.__pw_manual||o._phantom||o.callPhantom)&&(n.push("Automation framework global detected"),e+=45)}typeof window<"u"&&(window.outerWidth===0||window.outerHeight===0)&&(n.push("Zero outer window dimensions"),e+=15);let t=Math.min(e,100);return{isBot:t>=40,confidence:t,reasons:n}}function d(n){return{type:n.confidence>=70?"automation_tool":"bot_detected",severity:n.confidence>=70?"critical":n.confidence>=40?"high":"medium",message:`Bot detected (confidence: ${n.confidence}%): ${n.reasons.join(", ")}`,timestamp:Date.now(),metadata:{confidence:n.confidence,reasons:n.reasons}}}var s=class{constructor(e={}){this.intervalId=null;this.options={checkInterval:e.checkInterval??1e3,threshold:e.threshold??160,onDetected:e.onDetected??(()=>{})}}start(){typeof window>"u"||(this.intervalId=setInterval(()=>{let e=window.outerWidth-window.innerWidth,t=window.outerHeight-window.innerHeight;(e>this.options.threshold||t>this.options.threshold)&&this.options.onDetected({type:"devtools_open",severity:"high",message:"Developer tools detected via window size differential",timestamp:Date.now(),metadata:{widthDiff:e,heightDiff:t}})},this.options.checkInterval),this.setupDebuggerTrap(),this.setupConsoleDetection())}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null)}setupDebuggerTrap(){typeof window>"u"||setInterval(()=>{let e=performance.now(),t=new Function("debugger");try{t()}catch{}let o=performance.now()-e;o>100&&this.options.onDetected({type:"devtools_open",severity:"high",message:"Developer tools detected via debugger timing",timestamp:Date.now(),metadata:{elapsed:o}})},3e3)}setupConsoleDetection(){if(typeof window>"u")return;let e=new Image;Object.defineProperty(e,"id",{get:()=>(this.options.onDetected({type:"devtools_open",severity:"medium",message:"Developer tools detected via console inspection",timestamp:Date.now()}),"")}),setInterval(()=>{e.id},2e3)}};function v(n,e="ai-appshield"){let t=atob(n),o=[];for(let i=0;i<t.length;i++)o.push(String.fromCharCode(t.charCodeAt(i)^e.charCodeAt(i%e.length)));return o.join("")}function b(){let n=["email","phone","company","website","address","fax"],e=["_confirm","_alt","_2","_backup","_verify"],t=n[Math.floor(Math.random()*n.length)],o=e[Math.floor(Math.random()*e.length)];return`${t}${o}`}function h(n){let e=2166136261;for(let t=0;t<n.length;t++)e^=n.charCodeAt(t),e=e*16777619>>>0;return e.toString(36)}var a=class{constructor(e={}){this.listeners=[];this.options={selector:e.selector??"body",disableRightClick:e.disableRightClick??!0,disableSelection:e.disableSelection??!0,disableCopy:e.disableCopy??!0,disableDrag:e.disableDrag??!0,disablePrint:e.disablePrint??!0,obfuscateElements:e.obfuscateElements??!1,onScrapeAttempt:e.onScrapeAttempt??(()=>{})}}activate(){if(typeof document>"u")return;let e=document.querySelector(this.options.selector)??document.body;this.options.disableRightClick&&this.addListener(e,"contextmenu",t=>{t.preventDefault(),this.reportAttempt("Right-click blocked")}),this.options.disableSelection&&(this.addListener(e,"selectstart",t=>{t.preventDefault()}),this.injectStyle(`
|
|
2
|
+
${this.options.selector} {
|
|
3
|
+
-webkit-user-select: none !important;
|
|
4
|
+
-moz-user-select: none !important;
|
|
5
|
+
-ms-user-select: none !important;
|
|
6
|
+
user-select: none !important;
|
|
7
|
+
}
|
|
8
|
+
`)),this.options.disableCopy&&(this.addListener(e,"copy",t=>{t.preventDefault(),this.reportAttempt("Copy attempt blocked")}),this.addListener(e,"cut",t=>{t.preventDefault(),this.reportAttempt("Cut attempt blocked")})),this.options.disableDrag&&this.addListener(e,"dragstart",t=>{t.preventDefault()}),this.options.disablePrint&&(this.addListener(window,"beforeprint",()=>{this.reportAttempt("Print attempt detected")}),this.injectStyle(`
|
|
9
|
+
@media print {
|
|
10
|
+
${this.options.selector} { display: none !important; }
|
|
11
|
+
body::after {
|
|
12
|
+
content: "Printing is disabled.";
|
|
13
|
+
display: block;
|
|
14
|
+
font-size: 24px;
|
|
15
|
+
text-align: center;
|
|
16
|
+
padding: 50px;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
`)),this.addListener(document,"keydown",t=>{let o=t,i=o.key?.toLowerCase();(o.key==="F12"||o.ctrlKey&&o.shiftKey&&["i","j","c","k"].includes(i)||o.ctrlKey&&i==="u"||o.metaKey&&o.altKey&&i==="i")&&(o.preventDefault(),this.reportAttempt(`Blocked shortcut: ${i}`))}),this.options.obfuscateElements&&this.obfuscateProtectedElements()}deactivate(){for(let{target:e,type:t,handler:o}of this.listeners)e.removeEventListener(t,o);this.listeners=[]}obfuscateProtectedElements(){if(typeof document>"u")return;document.querySelectorAll("[data-shield]").forEach(t=>{let o=t.getAttribute("data-shield");if(o)try{t.textContent=v(o)}catch{}})}addListener(e,t,o){e.addEventListener(t,o),this.listeners.push({target:e,type:t,handler:o})}injectStyle(e){let t=document.createElement("style");t.setAttribute("data-ai-appshield","true"),t.textContent=e,document.head.appendChild(t)}reportAttempt(e){this.options.onScrapeAttempt({type:"content_scrape_attempt",severity:"medium",message:e,timestamp:Date.now()})}};function l(){let n=typeof navigator<"u"?navigator:{},e=typeof globalThis.screen<"u"?globalThis.screen:{},o=[n.userAgent??"",n.language??"",n.platform??"",`${e.width??0}x${e.height??0}`,Intl.DateTimeFormat().resolvedOptions().timeZone??"",String(e.colorDepth??0),String(n.hardwareConcurrency??0),y(),w()].join("|");return{hash:h(o),userAgent:n.userAgent??"",language:n.language??"",platform:n.platform??"",screenResolution:`${e.width??0}x${e.height??0}`,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone??"",colorDepth:e.colorDepth??0,hardwareConcurrency:n.hardwareConcurrency??0,canvas:y(),webgl:w(),timestamp:Date.now()}}function y(){if(typeof document>"u")return"";try{let n=document.createElement("canvas"),e=n.getContext("2d");return e?(e.textBaseline="top",e.font="14px Arial",e.fillStyle="#f60",e.fillRect(125,1,62,20),e.fillStyle="#069",e.fillText("ai-appshield",2,15),e.fillStyle="rgba(102, 204, 0, 0.7)",e.fillText("fingerprint",4,17),h(n.toDataURL())):""}catch{return""}}function w(){if(typeof document>"u")return"";try{let n=document.createElement("canvas"),e=n.getContext("webgl")??n.getContext("experimental-webgl");if(!e||!(e instanceof WebGLRenderingContext))return"";let t=e.getExtension("WEBGL_debug_renderer_info");if(!t)return"";let o=e.getParameter(t.UNMASKED_VENDOR_WEBGL),i=e.getParameter(t.UNMASKED_RENDERER_WEBGL);return h(`${o}|${i}`)}catch{return""}}var c=class{constructor(e={}){this.fieldName=b(),this.options={containerSelector:e.containerSelector??"body",onTriggered:e.onTriggered??(()=>{})}}inject(){if(typeof document>"u")return this.fieldName;let e=document.querySelector(this.options.containerSelector)??document.body,t=document.createElement("input");t.type="text",t.name=this.fieldName,t.id=this.fieldName,t.setAttribute("autocomplete","off"),t.setAttribute("tabindex","-1"),t.setAttribute("aria-hidden","true"),t.style.cssText="position:absolute;left:-9999px;top:-9999px;opacity:0;height:0;width:0;z-index:-1;pointer-events:none;";let o=document.createElement("a");return o.href="/api/internal-data-export",o.textContent="Admin Data Export",o.style.cssText="position:absolute;left:-9999px;opacity:0;font-size:0;",o.setAttribute("data-honeypot","true"),e.appendChild(t),e.appendChild(o),t.addEventListener("input",()=>{t.value.length>0&&this.options.onTriggered({type:"honeypot_triggered",severity:"critical",message:`Honeypot field "${this.fieldName}" was filled`,timestamp:Date.now(),metadata:{fieldName:this.fieldName,value:t.value}})}),this.fieldName}validate(e){let t=e instanceof FormData?e.get(this.fieldName):e[this.fieldName];return t&&String(t).length>0?(this.options.onTriggered({type:"honeypot_triggered",severity:"critical",message:"Honeypot triggered on validation",timestamp:Date.now(),metadata:{fieldName:this.fieldName}}),!0):!1}getFieldName(){return this.fieldName}};var u=class{constructor(e={}){this.devToolsGuard=null;this.contentProtector=null;this.honeypot=null;this.active=!1;this.threats=[];this.config={...g,...e}}activate(){return this.active?this:typeof window>"u"?(console.warn("[ai-appshield] Browser environment required for client-side protection"),this):(this.active=!0,this.log("Activating ai-appshield protection..."),this.config.botDetection&&this.runBotDetection(),this.config.devToolsGuard&&(this.devToolsGuard=new s({onDetected:e=>this.handleThreat(e)}),this.devToolsGuard.start()),(this.config.contentProtection||this.config.obfuscateContent)&&(this.contentProtector=new a({selector:this.config.protectSelector,obfuscateElements:this.config.obfuscateContent,onScrapeAttempt:e=>this.handleThreat(e)}),this.contentProtector.activate()),this.config.honeypot&&(this.honeypot=new c({onTriggered:e=>this.handleThreat(e)}),this.honeypot.inject()),this.config.fingerprint&&this.sendFingerprint(),this.log("ai-appshield protection active"),this)}deactivate(){return this.devToolsGuard?.stop(),this.contentProtector?.deactivate(),this.active=!1,this.log("ai-appshield protection deactivated"),this}getThreats(){return[...this.threats]}getFingerprint(){return l()}getHoneypotFieldName(){return this.honeypot?.getFieldName()??null}checkBot(){return r()}runBotDetection(){let e=r();e.isBot&&this.handleThreat(d(e)),setInterval(()=>{let t=r();t.isBot&&this.handleThreat(d(t))},5e3)}handleThreat(e){switch(this.threats.push(e),this.log(`Threat detected: ${e.type} \u2014 ${e.message}`),this.config.onThreatDetected?.(e),this.config.reportEndpoint&&this.reportThreat(e),this.config.threatAction){case"block":this.blockPage();break;case"redirect":this.config.redirectUrl&&(window.location.href=this.config.redirectUrl);break;case"warn":default:break}}blockPage(){if(!(typeof document>"u"))throw document.body.innerHTML='<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;color:#333;"><div style="text-align:center;"><h1>Access Denied</h1><p>Automated access is not permitted.</p></div></div>',new Error("[ai-appshield] Access blocked")}async reportThreat(e){try{await fetch(this.config.reportEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch{this.log("Failed to report threat to server")}}async sendFingerprint(){let e=l();if(this.config.reportEndpoint)try{await fetch(this.config.reportEndpoint,{method:"POST",headers:{"Content-Type":"application/json","X-Ai-Appshield-Fingerprint":e.hash},body:JSON.stringify({type:"fingerprint",data:e})})}catch{this.log("Failed to send fingerprint")}}log(e){this.config.debug&&console.log(`[ai-appshield] ${e}`)}};function A(n){let e=new u(n);return e.activate(),e}return C(S);})();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ThreatEvent } from '../core/config';
|
|
2
|
+
export interface BotDetectionResult {
|
|
3
|
+
isBot: boolean;
|
|
4
|
+
confidence: number;
|
|
5
|
+
reasons: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function detectBot(): BotDetectionResult;
|
|
8
|
+
export declare function createBotThreatEvent(result: BotDetectionResult): ThreatEvent;
|
|
9
|
+
//# sourceMappingURL=BotDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BotDetector.d.ts","sourceRoot":"","sources":["../../src/browser/BotDetector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAgB,SAAS,IAAI,kBAAkB,CA+F9C;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAQ5E"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectBot = detectBot;
|
|
4
|
+
exports.createBotThreatEvent = createBotThreatEvent;
|
|
5
|
+
const signatures_1 = require("../utils/signatures");
|
|
6
|
+
function detectBot() {
|
|
7
|
+
const reasons = [];
|
|
8
|
+
let score = 0;
|
|
9
|
+
// Check navigator.webdriver
|
|
10
|
+
if (typeof navigator !== 'undefined' && navigator.webdriver) {
|
|
11
|
+
reasons.push('navigator.webdriver is true');
|
|
12
|
+
score += 40;
|
|
13
|
+
}
|
|
14
|
+
// Check automation signatures on window
|
|
15
|
+
if (typeof window !== 'undefined') {
|
|
16
|
+
for (const sig of signatures_1.AUTOMATION_SIGNATURES) {
|
|
17
|
+
if (sig in window || window[sig] !== undefined) {
|
|
18
|
+
reasons.push(`Automation signature found: ${sig}`);
|
|
19
|
+
score += 25;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Headless Chrome detection
|
|
25
|
+
if (typeof navigator !== 'undefined') {
|
|
26
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
27
|
+
if (ua.includes('headless')) {
|
|
28
|
+
reasons.push('Headless user agent detected');
|
|
29
|
+
score += 50;
|
|
30
|
+
}
|
|
31
|
+
// Plugin count check
|
|
32
|
+
if (signatures_1.HEADLESS_INDICATORS.missingPlugins && navigator.plugins?.length === 0) {
|
|
33
|
+
reasons.push('No browser plugins (headless indicator)');
|
|
34
|
+
score += 15;
|
|
35
|
+
}
|
|
36
|
+
// Language check
|
|
37
|
+
if (signatures_1.HEADLESS_INDICATORS.zeroLanguages && navigator.languages?.length === 0) {
|
|
38
|
+
reasons.push('No languages configured');
|
|
39
|
+
score += 10;
|
|
40
|
+
}
|
|
41
|
+
// Chrome without chrome runtime
|
|
42
|
+
if (signatures_1.HEADLESS_INDICATORS.chromeRuntimeMissing &&
|
|
43
|
+
ua.includes('chrome') &&
|
|
44
|
+
typeof window.chrome === 'undefined') {
|
|
45
|
+
reasons.push('Chrome UA without chrome runtime');
|
|
46
|
+
score += 20;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Permissions API inconsistency (headless browsers often fail this)
|
|
50
|
+
if (typeof navigator !== 'undefined' && 'permissions' in navigator) {
|
|
51
|
+
try {
|
|
52
|
+
const notifPerm = navigator.permissions;
|
|
53
|
+
if (notifPerm) {
|
|
54
|
+
notifPerm.query({ name: 'notifications' }).then((result) => {
|
|
55
|
+
if (result.state === 'prompt' && Notification.permission === 'denied') {
|
|
56
|
+
reasons.push('Permissions API inconsistency');
|
|
57
|
+
}
|
|
58
|
+
}).catch(() => {
|
|
59
|
+
reasons.push('Permissions API unavailable');
|
|
60
|
+
score += 10;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
score += 5;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Check for Puppeteer/Playwright specific properties
|
|
69
|
+
if (typeof window !== 'undefined') {
|
|
70
|
+
const win = window;
|
|
71
|
+
if (win.__playwright || win.__pw_manual || win._phantom || win.callPhantom) {
|
|
72
|
+
reasons.push('Automation framework global detected');
|
|
73
|
+
score += 45;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Outer/inner dimension mismatch (common in headless)
|
|
77
|
+
if (typeof window !== 'undefined') {
|
|
78
|
+
if (window.outerWidth === 0 || window.outerHeight === 0) {
|
|
79
|
+
reasons.push('Zero outer window dimensions');
|
|
80
|
+
score += 15;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const confidence = Math.min(score, 100);
|
|
84
|
+
return {
|
|
85
|
+
isBot: confidence >= 40,
|
|
86
|
+
confidence,
|
|
87
|
+
reasons,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function createBotThreatEvent(result) {
|
|
91
|
+
return {
|
|
92
|
+
type: result.confidence >= 70 ? 'automation_tool' : 'bot_detected',
|
|
93
|
+
severity: result.confidence >= 70 ? 'critical' : result.confidence >= 40 ? 'high' : 'medium',
|
|
94
|
+
message: `Bot detected (confidence: ${result.confidence}%): ${result.reasons.join(', ')}`,
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
metadata: { confidence: result.confidence, reasons: result.reasons },
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=BotDetector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BotDetector.js","sourceRoot":"","sources":["../../src/browser/BotDetector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ThreatEvent } from '../core/config';
|
|
2
|
+
export interface ContentProtectorOptions {
|
|
3
|
+
selector?: string;
|
|
4
|
+
disableRightClick?: boolean;
|
|
5
|
+
disableSelection?: boolean;
|
|
6
|
+
disableCopy?: boolean;
|
|
7
|
+
disableDrag?: boolean;
|
|
8
|
+
disablePrint?: boolean;
|
|
9
|
+
obfuscateElements?: boolean;
|
|
10
|
+
onScrapeAttempt?: (event: ThreatEvent) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare class ContentProtector {
|
|
13
|
+
private options;
|
|
14
|
+
private listeners;
|
|
15
|
+
constructor(options?: ContentProtectorOptions);
|
|
16
|
+
activate(): void;
|
|
17
|
+
deactivate(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Mark elements with data-shield attribute for obfuscated rendering.
|
|
20
|
+
* Usage: <span data-shield="encoded-content"></span>
|
|
21
|
+
*/
|
|
22
|
+
obfuscateProtectedElements(): void;
|
|
23
|
+
private addListener;
|
|
24
|
+
private injectStyle;
|
|
25
|
+
private reportAttempt;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=ContentProtector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentProtector.d.ts","sourceRoot":"","sources":["../../src/browser/ContentProtector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAChD;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,SAAS,CAA4E;gBAEjF,OAAO,GAAE,uBAA4B;IAajD,QAAQ,IAAI,IAAI;IAkFhB,UAAU,IAAI,IAAI;IAOlB;;;OAGG;IACH,0BAA0B,IAAI,IAAI;IAgBlC,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,aAAa;CAQtB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContentProtector = void 0;
|
|
4
|
+
const obfuscate_1 = require("../utils/obfuscate");
|
|
5
|
+
class ContentProtector {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.listeners = [];
|
|
8
|
+
this.options = {
|
|
9
|
+
selector: options.selector ?? 'body',
|
|
10
|
+
disableRightClick: options.disableRightClick ?? true,
|
|
11
|
+
disableSelection: options.disableSelection ?? true,
|
|
12
|
+
disableCopy: options.disableCopy ?? true,
|
|
13
|
+
disableDrag: options.disableDrag ?? true,
|
|
14
|
+
disablePrint: options.disablePrint ?? true,
|
|
15
|
+
obfuscateElements: options.obfuscateElements ?? false,
|
|
16
|
+
onScrapeAttempt: options.onScrapeAttempt ?? (() => { }),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
activate() {
|
|
20
|
+
if (typeof document === 'undefined')
|
|
21
|
+
return;
|
|
22
|
+
const target = document.querySelector(this.options.selector) ?? document.body;
|
|
23
|
+
if (this.options.disableRightClick) {
|
|
24
|
+
this.addListener(target, 'contextmenu', (e) => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
this.reportAttempt('Right-click blocked');
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (this.options.disableSelection) {
|
|
30
|
+
this.addListener(target, 'selectstart', (e) => {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
});
|
|
33
|
+
this.injectStyle(`
|
|
34
|
+
${this.options.selector} {
|
|
35
|
+
-webkit-user-select: none !important;
|
|
36
|
+
-moz-user-select: none !important;
|
|
37
|
+
-ms-user-select: none !important;
|
|
38
|
+
user-select: none !important;
|
|
39
|
+
}
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
if (this.options.disableCopy) {
|
|
43
|
+
this.addListener(target, 'copy', (e) => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
this.reportAttempt('Copy attempt blocked');
|
|
46
|
+
});
|
|
47
|
+
this.addListener(target, 'cut', (e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
this.reportAttempt('Cut attempt blocked');
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (this.options.disableDrag) {
|
|
53
|
+
this.addListener(target, 'dragstart', (e) => {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (this.options.disablePrint) {
|
|
58
|
+
this.addListener(window, 'beforeprint', () => {
|
|
59
|
+
this.reportAttempt('Print attempt detected');
|
|
60
|
+
});
|
|
61
|
+
this.injectStyle(`
|
|
62
|
+
@media print {
|
|
63
|
+
${this.options.selector} { display: none !important; }
|
|
64
|
+
body::after {
|
|
65
|
+
content: "Printing is disabled.";
|
|
66
|
+
display: block;
|
|
67
|
+
font-size: 24px;
|
|
68
|
+
text-align: center;
|
|
69
|
+
padding: 50px;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
// Detect keyboard shortcuts for dev tools and view source
|
|
75
|
+
this.addListener(document, 'keydown', (e) => {
|
|
76
|
+
const event = e;
|
|
77
|
+
const key = event.key?.toLowerCase();
|
|
78
|
+
if (event.key === 'F12' ||
|
|
79
|
+
(event.ctrlKey && event.shiftKey && ['i', 'j', 'c', 'k'].includes(key)) ||
|
|
80
|
+
(event.ctrlKey && key === 'u') ||
|
|
81
|
+
(event.metaKey && event.altKey && key === 'i')) {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
this.reportAttempt(`Blocked shortcut: ${key}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (this.options.obfuscateElements) {
|
|
87
|
+
this.obfuscateProtectedElements();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
deactivate() {
|
|
91
|
+
for (const { target, type, handler } of this.listeners) {
|
|
92
|
+
target.removeEventListener(type, handler);
|
|
93
|
+
}
|
|
94
|
+
this.listeners = [];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Mark elements with data-shield attribute for obfuscated rendering.
|
|
98
|
+
* Usage: <span data-shield="encoded-content"></span>
|
|
99
|
+
*/
|
|
100
|
+
obfuscateProtectedElements() {
|
|
101
|
+
if (typeof document === 'undefined')
|
|
102
|
+
return;
|
|
103
|
+
const elements = document.querySelectorAll('[data-shield]');
|
|
104
|
+
elements.forEach((el) => {
|
|
105
|
+
const encoded = el.getAttribute('data-shield');
|
|
106
|
+
if (encoded) {
|
|
107
|
+
try {
|
|
108
|
+
el.textContent = (0, obfuscate_1.decodeContent)(encoded);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Invalid encoded content
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
addListener(target, type, handler) {
|
|
117
|
+
target.addEventListener(type, handler);
|
|
118
|
+
this.listeners.push({ target, type, handler });
|
|
119
|
+
}
|
|
120
|
+
injectStyle(css) {
|
|
121
|
+
const style = document.createElement('style');
|
|
122
|
+
style.setAttribute('data-ai-appshield', 'true');
|
|
123
|
+
style.textContent = css;
|
|
124
|
+
document.head.appendChild(style);
|
|
125
|
+
}
|
|
126
|
+
reportAttempt(message) {
|
|
127
|
+
this.options.onScrapeAttempt({
|
|
128
|
+
type: 'content_scrape_attempt',
|
|
129
|
+
severity: 'medium',
|
|
130
|
+
message,
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.ContentProtector = ContentProtector;
|
|
136
|
+
//# sourceMappingURL=ContentProtector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentProtector.js","sourceRoot":"","sources":["../../src/browser/ContentProtector.ts"],"names":[],"mappings":";;;AAAA,kDAAmD;AAcnD,MAAa,gBAAgB;IAI3B,YAAY,UAAmC,EAAE;QAFzC,cAAS,GAAyE,EAAE,CAAC;QAG3F,IAAI,CAAC,OAAO,GAAG;YACb,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,MAAM;YACpC,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,IAAI;YACpD,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,IAAI;YAClD,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;YACxC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;YACxC,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,IAAI;YAC1C,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,KAAK;YACrD,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;SACvD,CAAC;IACJ,CAAC;IAED,QAAQ;QACN,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO;QAE5C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC;QAE9E,IAAI,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YACnC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC5C,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;YAC5C,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAClC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC5C,CAAC,CAAC,cAAc,EAAE,CAAC;YACrB,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,WAAW,CAAC;UACb,IAAI,CAAC,OAAO,CAAC,QAAQ;;;;;;OAMxB,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAC7B,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;gBACrC,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;YAC7C,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;gBACpC,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;YAC5C,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAC7B,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC1C,CAAC,CAAC,cAAc,EAAE,CAAC;YACrB,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAC9B,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE;gBAC3C,IAAI,CAAC,aAAa,CAAC,wBAAwB,CAAC,CAAC;YAC/C,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,WAAW,CAAC;;YAEX,IAAI,CAAC,OAAO,CAAC,QAAQ;;;;;;;;;OAS1B,CAAC,CAAC;QACL,CAAC;QAED,0DAA0D;QAC1D,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YAC1C,MAAM,KAAK,GAAG,CAAkB,CAAC;YACjC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC;YAErC,IACE,KAAK,CAAC,GAAG,KAAK,KAAK;gBACnB,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBACvE,CAAC,KAAK,CAAC,OAAO,IAAI,GAAG,KAAK,GAAG,CAAC;gBAC9B,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,EAC9C,CAAC;gBACD,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,IAAI,CAAC,aAAa,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YACnC,IAAI,CAAC,0BAA0B,EAAE,CAAC;QACpC,CAAC;IACH,CAAC;IAED,UAAU;QACR,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACvD,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;IACtB,CAAC;IAED;;;OAGG;IACH,0BAA0B;QACxB,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAO;QAE5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC;QAC5D,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;YACtB,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;YAC/C,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,CAAC;oBACH,EAAE,CAAC,WAAW,GAAG,IAAA,yBAAa,EAAC,OAAO,CAAC,CAAC;gBAC1C,CAAC;gBAAC,MAAM,CAAC;oBACP,0BAA0B;gBAC5B,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,WAAW,CAAC,MAAmB,EAAE,IAAY,EAAE,OAAsB;QAC3E,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACjD,CAAC;IAEO,WAAW,CAAC,GAAW;QAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,YAAY,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;QAChD,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAEO,aAAa,CAAC,OAAe;QACnC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC;YAC3B,IAAI,EAAE,wBAAwB;YAC9B,QAAQ,EAAE,QAAQ;YAClB,OAAO;YACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;CACF;AAlJD,4CAkJC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ThreatEvent } from '../core/config';
|
|
2
|
+
export interface DevToolsGuardOptions {
|
|
3
|
+
checkInterval?: number;
|
|
4
|
+
threshold?: number;
|
|
5
|
+
onDetected?: (event: ThreatEvent) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare class DevToolsGuard {
|
|
8
|
+
private intervalId;
|
|
9
|
+
private options;
|
|
10
|
+
constructor(options?: DevToolsGuardOptions);
|
|
11
|
+
start(): void;
|
|
12
|
+
stop(): void;
|
|
13
|
+
private setupDebuggerTrap;
|
|
14
|
+
private setupConsoleDetection;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=DevToolsGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DevToolsGuard.d.ts","sourceRoot":"","sources":["../../src/browser/DevToolsGuard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,OAAO,CAAiC;gBAEpC,OAAO,GAAE,oBAAyB;IAQ9C,KAAK,IAAI,IAAI;IA0Bb,IAAI,IAAI,IAAI;IAOZ,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,qBAAqB;CAsB9B"}
|