@browsertotal/scanner 2.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/README.md ADDED
@@ -0,0 +1,366 @@
1
+ <img src="https://browsertotal.com/icons/logo.svg" alt="BrowserTotal Logo" width="200" />
2
+
3
+ # @browsertotal/scanner
4
+
5
+ Scan URLs, browser extensions, IDE plugins, and packages for security threats using [BrowserTotal.com](https://browsertotal.com).
6
+
7
+ This package uses Puppeteer to automate browser interactions with BrowserTotal's analysis tools. It leverages a custom event system (`#automationEvent=true`) to receive complete scan results including AI/LLM analysis.
8
+
9
+ ## Supported Platforms
10
+
11
+ | Platform | Method | Description |
12
+ |----------|--------|-------------|
13
+ | **URLs** | `scanUrl()` | Scan any URL for security threats |
14
+ | **Chrome** | `scanExtension()` | Chrome Web Store extensions |
15
+ | **Firefox** | `scanExtension()` | Firefox Add-ons |
16
+ | **Edge** | `scanExtension()` | Microsoft Edge Add-ons |
17
+ | **Opera** | `scanExtension()` | Opera Add-ons |
18
+ | **Safari** | `scanExtension()` | Safari Extensions |
19
+ | **Brave** | `scanExtension()` | Brave Extensions |
20
+ | **VS Code** | `scanVSCodeExtension()` | Visual Studio Marketplace extensions |
21
+ | **Open VSX** | `scanOpenVSXExtension()` | Open VSX Registry extensions |
22
+ | **JetBrains** | `scanJetBrainsPlugin()` | IntelliJ, PyCharm, WebStorm plugins |
23
+ | **npm** | `scanNpmPackage()` | npm packages |
24
+ | **PyPI** | `scanPyPIPackage()` | Python packages |
25
+ | **WordPress** | `scanWordPressPlugin()` | WordPress plugins |
26
+ | **Hugging Face** | `scanHuggingFace()` | Hugging Face models/spaces |
27
+ | **AppSource** | `scanAppSourceAddin()` | Microsoft AppSource add-ins |
28
+ | **PowerShell** | `scanPowerShellModule()` | PowerShell Gallery modules |
29
+ | **Salesforce** | `scanSalesforceApp()` | Salesforce AppExchange apps |
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install @browsertotal/scanner
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ### Scanning URLs
40
+
41
+ ```typescript
42
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
43
+
44
+ const scanner = new BrowserTotalScanner();
45
+
46
+ const result = await scanner.scanUrl('https://example.com');
47
+ console.log(result.status); // 'safe' | 'suspicious' | 'malicious' | 'unknown' | 'error'
48
+ console.log(result.threats);
49
+ console.log(result.raw); // Full analysis data including LLM results
50
+
51
+ await scanner.close();
52
+ ```
53
+
54
+ ### Scanning Browser Extensions
55
+
56
+ ```typescript
57
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
58
+
59
+ const scanner = new BrowserTotalScanner();
60
+
61
+ // Chrome extension
62
+ const chromeResult = await scanner.scanExtension('cjpalhdlnbpafiamejdnhcphjbkeiagm', 'chrome');
63
+
64
+ // Firefox add-on
65
+ const firefoxResult = await scanner.scanExtension('ublock-origin', 'firefox');
66
+
67
+ // Edge extension
68
+ const edgeResult = await scanner.scanExtension('extension-id', 'edge');
69
+
70
+ await scanner.close();
71
+ ```
72
+
73
+ ### Scanning VS Code Extensions
74
+
75
+ ```typescript
76
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
77
+
78
+ const scanner = new BrowserTotalScanner();
79
+
80
+ // VS Code Marketplace extension (publisher.extension-name format)
81
+ const result = await scanner.scanVSCodeExtension('ms-python.python');
82
+ console.log(result.name);
83
+ console.log(result.status);
84
+ console.log(result.permissions);
85
+
86
+ await scanner.close();
87
+ ```
88
+
89
+ ### Scanning JetBrains Plugins
90
+
91
+ ```typescript
92
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
93
+
94
+ const scanner = new BrowserTotalScanner();
95
+
96
+ // JetBrains plugin by ID
97
+ const result = await scanner.scanJetBrainsPlugin('7495'); // Rainbow Brackets
98
+ console.log(result.status);
99
+
100
+ await scanner.close();
101
+ ```
102
+
103
+ ### Scanning npm Packages
104
+
105
+ ```typescript
106
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
107
+
108
+ const scanner = new BrowserTotalScanner();
109
+
110
+ const result = await scanner.scanNpmPackage('lodash');
111
+ console.log(result.status);
112
+ console.log(result.version);
113
+ console.log(result.dependencies);
114
+
115
+ await scanner.close();
116
+ ```
117
+
118
+ ### Scanning PyPI Packages
119
+
120
+ ```typescript
121
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
122
+
123
+ const scanner = new BrowserTotalScanner();
124
+
125
+ const result = await scanner.scanPyPIPackage('requests');
126
+ console.log(result.status);
127
+ console.log(result.version);
128
+
129
+ await scanner.close();
130
+ ```
131
+
132
+ ### Scanning WordPress Plugins
133
+
134
+ ```typescript
135
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
136
+
137
+ const scanner = new BrowserTotalScanner();
138
+
139
+ const result = await scanner.scanWordPressPlugin('akismet');
140
+ console.log(result.status);
141
+
142
+ await scanner.close();
143
+ ```
144
+
145
+ ### Generic Platform Scanning
146
+
147
+ Use `scanByPlatform()` for dynamic platform selection:
148
+
149
+ ```typescript
150
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
151
+
152
+ const scanner = new BrowserTotalScanner();
153
+
154
+ // Scan any platform dynamically
155
+ const result = await scanner.scanByPlatform('lodash', 'npmjs');
156
+ const result2 = await scanner.scanByPlatform('extension-id', 'chrome');
157
+ const result3 = await scanner.scanByPlatform('ms-python.python', 'vscode');
158
+
159
+ await scanner.close();
160
+ ```
161
+
162
+ ### Convenience Functions
163
+
164
+ For one-off scans without managing the scanner lifecycle:
165
+
166
+ ```typescript
167
+ import {
168
+ scanUrl,
169
+ scanExtension,
170
+ scanVSCodeExtension,
171
+ scanJetBrainsPlugin,
172
+ scanNpmPackage,
173
+ scanPyPIPackage,
174
+ scanWordPressPlugin
175
+ } from '@browsertotal/scanner';
176
+
177
+ // One-off URL scan
178
+ const urlResult = await scanUrl('https://example.com');
179
+
180
+ // One-off browser extension scan
181
+ const extResult = await scanExtension('extension-id', 'chrome');
182
+
183
+ // One-off VS Code extension scan
184
+ const vscodeResult = await scanVSCodeExtension('ms-python.python');
185
+
186
+ // One-off npm package scan
187
+ const npmResult = await scanNpmPackage('express');
188
+
189
+ // One-off PyPI package scan
190
+ const pypiResult = await scanPyPIPackage('django');
191
+ ```
192
+
193
+ ### Progress Tracking
194
+
195
+ ```typescript
196
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
197
+
198
+ const scanner = new BrowserTotalScanner();
199
+
200
+ const result = await scanner.scanNpmPackage('express', (progress) => {
201
+ console.log(`[${progress.phase}] ${progress.message}`);
202
+ });
203
+
204
+ await scanner.close();
205
+ ```
206
+
207
+ ### Configuration Options
208
+
209
+ ```typescript
210
+ import { BrowserTotalScanner } from '@browsertotal/scanner';
211
+
212
+ const scanner = new BrowserTotalScanner({
213
+ headless: true, // Run browser in headless mode (default: true)
214
+ timeout: 120000, // Timeout in milliseconds (default: 420000)
215
+ waitForResults: true, // Wait for scan to complete (default: true)
216
+ disableAI: true, // Skip AI/LLM analysis for faster scans (default: true)
217
+ userDataDir: '/tmp/browser', // Custom browser profile directory
218
+ });
219
+ ```
220
+
221
+ ### Authentication
222
+
223
+ The scanner uses a Proof of Work challenge-response system that works automatically in headless browsers. No API key is required - the computational proof of work prevents abuse while allowing automated access.
224
+
225
+ The scanner will automatically:
226
+ 1. Request a challenge from the server
227
+ 2. Solve the Proof of Work challenge
228
+ 3. Submit analysis results with the solution for verification
229
+
230
+ ## How It Works
231
+
232
+ The scanner uses a special automation mode that:
233
+
234
+ 1. Appends `#automationEvent=true` to scan URLs
235
+ 2. BrowserTotal's UI detects this parameter and dispatches a `scan_result` custom event when analysis completes
236
+ 3. Puppeteer captures this event to get the full scan results
237
+
238
+ This approach ensures you get the complete analysis data, including:
239
+ - Static analysis results
240
+ - LLM/AI analysis results
241
+ - Risk scores and threat assessments
242
+ - Permission analysis (for extensions)
243
+ - Dependency analysis (for packages)
244
+ - Vulnerability findings
245
+
246
+ ## API Reference
247
+
248
+ ### `BrowserTotalScanner`
249
+
250
+ #### Constructor Options
251
+
252
+ | Option | Type | Default | Description |
253
+ |--------|------|---------|-------------|
254
+ | `headless` | `boolean` | `true` | Run browser in headless mode |
255
+ | `timeout` | `number` | `420000` | Timeout in milliseconds |
256
+ | `waitForResults` | `boolean` | `true` | Wait for scan results before returning |
257
+ | `disableAI` | `boolean` | `true` | Skip AI/LLM analysis for faster scans |
258
+ | `userDataDir` | `string` | `undefined` | Custom browser profile directory |
259
+
260
+ #### Methods
261
+
262
+ ##### URL Scanning
263
+ - `scanUrl(url: string, onProgress?: ProgressCallback): Promise<UrlScanResult>`
264
+
265
+ ##### Browser Extensions
266
+ - `scanExtension(extensionId: string, store?: BrowserStore, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
267
+
268
+ ##### IDE Extensions
269
+ - `scanVSCodeExtension(extensionId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
270
+ - `scanOpenVSXExtension(extensionId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
271
+ - `scanJetBrainsPlugin(pluginId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
272
+
273
+ ##### Package Managers
274
+ - `scanNpmPackage(packageName: string, onProgress?: ProgressCallback): Promise<PackageScanResult>`
275
+ - `scanPyPIPackage(packageName: string, onProgress?: ProgressCallback): Promise<PackageScanResult>`
276
+ - `scanPowerShellModule(moduleName: string, onProgress?: ProgressCallback): Promise<PackageScanResult>`
277
+
278
+ ##### Other Platforms
279
+ - `scanWordPressPlugin(pluginSlug: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
280
+ - `scanHuggingFace(modelId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
281
+ - `scanAppSourceAddin(addinId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
282
+ - `scanSalesforceApp(appId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>`
283
+
284
+ ##### Generic
285
+ - `scanByPlatform(identifier: string, platform: Platform | BrowserStore, onProgress?: ProgressCallback): Promise<ExtensionScanResult | PackageScanResult>`
286
+
287
+ ##### Lifecycle
288
+ - `close(): Promise<void>` - Closes the browser instance
289
+
290
+ ### Types
291
+
292
+ #### `BrowserStore`
293
+
294
+ ```typescript
295
+ type BrowserStore = 'chrome' | 'firefox' | 'edge' | 'opera' | 'safari' | 'brave';
296
+ ```
297
+
298
+ #### `Platform`
299
+
300
+ ```typescript
301
+ type Platform =
302
+ | 'vscode'
303
+ | 'openvsx'
304
+ | 'jetbrains'
305
+ | 'npmjs'
306
+ | 'pypi'
307
+ | 'wordpress'
308
+ | 'huggingface'
309
+ | 'appsource'
310
+ | 'powershellgallery'
311
+ | 'salesforce';
312
+ ```
313
+
314
+ #### `UrlScanResult`
315
+
316
+ ```typescript
317
+ interface UrlScanResult {
318
+ url: string;
319
+ status: 'safe' | 'suspicious' | 'malicious' | 'unknown' | 'error';
320
+ score?: number;
321
+ threats?: ThreatInfo[];
322
+ categories?: string[];
323
+ scanUrl: string;
324
+ timestamp: Date;
325
+ raw?: Record<string, unknown>; // Full scan data including LLM results
326
+ }
327
+ ```
328
+
329
+ #### `ExtensionScanResult`
330
+
331
+ ```typescript
332
+ interface ExtensionScanResult {
333
+ extensionId: string;
334
+ name?: string;
335
+ status: 'safe' | 'suspicious' | 'malicious' | 'unknown' | 'error';
336
+ score?: number;
337
+ permissions?: string[];
338
+ threats?: ThreatInfo[];
339
+ scanUrl: string;
340
+ timestamp: Date;
341
+ raw?: Record<string, unknown>; // Full scan data including LLM results
342
+ }
343
+ ```
344
+
345
+ #### `PackageScanResult`
346
+
347
+ ```typescript
348
+ interface PackageScanResult {
349
+ packageName: string;
350
+ platform: string;
351
+ name?: string;
352
+ version?: string;
353
+ status: 'safe' | 'suspicious' | 'malicious' | 'unknown' | 'error';
354
+ score?: number;
355
+ dependencies?: Record<string, string>;
356
+ threats?: ThreatInfo[];
357
+ scanUrl: string;
358
+ timestamp: Date;
359
+ raw?: Record<string, unknown>; // Full scan data including LLM results
360
+ }
361
+ ```
362
+
363
+ ## Requirements
364
+
365
+ - Node.js 18+
366
+ - Puppeteer (installed automatically)
@@ -0,0 +1,10 @@
1
+ export { BrowserTotalScanner } from './scanner.js';
2
+ export { ScannerOptions, UrlScanResult, ExtensionScanResult, PackageScanResult, ThreatInfo, ScanProgress, ProgressCallback, BrowserStore, Platform, } from './types.js';
3
+ import type { ScannerOptions, UrlScanResult, ExtensionScanResult, PackageScanResult, BrowserStore } from './types.js';
4
+ export declare function scanUrl(url: string, options?: ScannerOptions): Promise<UrlScanResult>;
5
+ export declare function scanExtension(extensionId: string, store?: BrowserStore, options?: ScannerOptions): Promise<ExtensionScanResult>;
6
+ export declare function scanVSCodeExtension(extensionId: string, options?: ScannerOptions): Promise<ExtensionScanResult>;
7
+ export declare function scanJetBrainsPlugin(pluginId: string, options?: ScannerOptions): Promise<ExtensionScanResult>;
8
+ export declare function scanNpmPackage(packageName: string, options?: ScannerOptions): Promise<PackageScanResult>;
9
+ export declare function scanPyPIPackage(packageName: string, options?: ScannerOptions): Promise<PackageScanResult>;
10
+ export declare function scanWordPressPlugin(pluginSlug: string, options?: ScannerOptions): Promise<ExtensionScanResult>;
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.BrowserTotalScanner=void 0,exports.scanUrl=scanUrl,exports.scanExtension=scanExtension,exports.scanVSCodeExtension=scanVSCodeExtension,exports.scanJetBrainsPlugin=scanJetBrainsPlugin,exports.scanNpmPackage=scanNpmPackage,exports.scanPyPIPackage=scanPyPIPackage,exports.scanWordPressPlugin=scanWordPressPlugin;var scanner_js_1=require("./scanner.js");async function scanUrl(n,a){const{BrowserTotalScanner:s}=await import("./scanner.js"),e=new s(a);try{return await e.scanUrl(n)}finally{await e.close()}}async function scanExtension(n,a="chrome",s){const{BrowserTotalScanner:e}=await import("./scanner.js"),r=new e(s);try{return await r.scanExtension(n,a)}finally{await r.close()}}async function scanVSCodeExtension(n,a){const{BrowserTotalScanner:s}=await import("./scanner.js"),e=new s(a);try{return await e.scanVSCodeExtension(n)}finally{await e.close()}}async function scanJetBrainsPlugin(n,a){const{BrowserTotalScanner:s}=await import("./scanner.js"),e=new s(a);try{return await e.scanJetBrainsPlugin(n)}finally{await e.close()}}async function scanNpmPackage(n,a){const{BrowserTotalScanner:s}=await import("./scanner.js"),e=new s(a);try{return await e.scanNpmPackage(n)}finally{await e.close()}}async function scanPyPIPackage(n,a){const{BrowserTotalScanner:s}=await import("./scanner.js"),e=new s(a);try{return await e.scanPyPIPackage(n)}finally{await e.close()}}async function scanWordPressPlugin(n,a){const{BrowserTotalScanner:s}=await import("./scanner.js"),e=new s(a);try{return await e.scanWordPressPlugin(n)}finally{await e.close()}}Object.defineProperty(exports,"BrowserTotalScanner",{enumerable:!0,get:function(){return scanner_js_1.BrowserTotalScanner}});
@@ -0,0 +1,30 @@
1
+ import { ScannerOptions, UrlScanResult, ExtensionScanResult, PackageScanResult, ProgressCallback, BrowserStore, Platform } from './types.js';
2
+ export declare class BrowserTotalScanner {
3
+ private options;
4
+ private browser;
5
+ constructor(options?: ScannerOptions);
6
+ private buildHashParams;
7
+ private ensureBrowser;
8
+ private reportProgress;
9
+ scanUrl(url: string, onProgress?: ProgressCallback): Promise<UrlScanResult>;
10
+ scanExtension(extensionId: string, store?: BrowserStore, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
11
+ scanVSCodeExtension(extensionId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
12
+ scanOpenVSXExtension(extensionId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
13
+ scanJetBrainsPlugin(pluginId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
14
+ scanNpmPackage(packageName: string, onProgress?: ProgressCallback): Promise<PackageScanResult>;
15
+ scanPyPIPackage(packageName: string, onProgress?: ProgressCallback): Promise<PackageScanResult>;
16
+ scanWordPressPlugin(pluginSlug: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
17
+ scanHuggingFace(modelId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
18
+ scanAppSourceAddin(addinId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
19
+ scanPowerShellModule(moduleName: string, onProgress?: ProgressCallback): Promise<PackageScanResult>;
20
+ scanSalesforceApp(appId: string, onProgress?: ProgressCallback): Promise<ExtensionScanResult>;
21
+ scanByPlatform(identifier: string, platform: Platform | BrowserStore, onProgress?: ProgressCallback): Promise<ExtensionScanResult | PackageScanResult>;
22
+ private scanGenericExtension;
23
+ private scanGenericPackage;
24
+ private waitForScanResultEvent;
25
+ private mapUrlEventResult;
26
+ private mapExtensionEventResult;
27
+ private mapPackageEventResult;
28
+ private mapStatus;
29
+ close(): Promise<void>;
30
+ }
@@ -0,0 +1 @@
1
+ "use strict";var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.BrowserTotalScanner=void 0;const puppeteer_1=__importDefault(require("puppeteer")),BASE_URL=process.env.BROWSERTOTAL_URL||"https://browsertotal.com",DEFAULT_TIMEOUT=42e4,BROWSER_STORE_MAP={chrome:"google",firefox:"mozilla",edge:"microsoft",opera:"opera",safari:"safari",brave:"brave"},PLATFORM_PATH_MAP={vscode:"vscode",openvsx:"openvsx",jetbrains:"jetbrains",npmjs:"npmjs",pypi:"pypi",wordpress:"wordpress",huggingface:"huggingface",appsource:"appsource",powershellgallery:"powershellgallery",salesforce:"salesforce"};function toHex(e){return Array.from(e).map(e=>e.charCodeAt(0).toString(16).padStart(2,"0")).join("")}class BrowserTotalScanner{options;browser=null;constructor(e={}){this.options={headless:e.headless??!0,timeout:e.timeout??42e4,waitForResults:e.waitForResults??!0,disableAI:e.disableAI??!0,userDataDir:e.userDataDir}}buildHashParams(){const e=["automationEvent=true"];return this.options.disableAI&&e.push("disableAI=true"),"#"+e.join("&")}async ensureBrowser(){return this.browser||(this.browser=await puppeteer_1.default.launch({headless:this.options.headless,args:["--no-sandbox","--disable-setuid-sandbox"],userDataDir:this.options.userDataDir})),this.browser}reportProgress(e,s){e&&e(s)}async scanUrl(e,s){const t=await this.ensureBrowser(),n=await t.newPage();try{this.reportProgress(s,{phase:"initializing",message:"Starting URL scan..."});const t=toHex(e),a=`${BASE_URL}/analysis/urls/${t}${this.buildHashParams()}`;this.reportProgress(s,{phase:"navigating",message:`Navigating to ${a}`});const r=this.waitForScanResultEvent(n,"url");if(await n.goto(a,{waitUntil:"networkidle2",timeout:this.options.timeout}),this.reportProgress(s,{phase:"scanning",message:"Waiting for scan results..."}),this.options.waitForResults){const t=await r;if(t)return this.reportProgress(s,{phase:"complete",message:"Scan complete"}),this.mapUrlEventResult(t,e,a)}throw this.reportProgress(s,{phase:"complete",message:"Scan error"}),new Error("Scan error")}finally{await n.close()}}async scanExtension(e,s="chrome",t){const n=`${BASE_URL}/analysis/live/store/${BROWSER_STORE_MAP[s]||s}/${e}${this.buildHashParams()}`;return this.scanGenericExtension(e,n,`${s} extension`,t)}async scanVSCodeExtension(e,s){const t=`${BASE_URL}/analysis/live/store/vscode/${e}${this.buildHashParams()}`;return this.scanGenericExtension(e,t,"VS Code extension",s)}async scanOpenVSXExtension(e,s){const t=`${BASE_URL}/analysis/live/store/openvsx/${e}${this.buildHashParams()}`;return this.scanGenericExtension(e,t,"Open VSX extension",s)}async scanJetBrainsPlugin(e,s){const t=`${BASE_URL}/analysis/live/store/jetbrains/${e}${this.buildHashParams()}`;return this.scanGenericExtension(e,t,"JetBrains plugin",s)}async scanNpmPackage(e,s){const t=`${BASE_URL}/analysis/live/store/npmjs/${encodeURIComponent(e)}${this.buildHashParams()}`;return this.scanGenericPackage(e,"npmjs",t,"npm package",s)}async scanPyPIPackage(e,s){const t=`${BASE_URL}/analysis/live/store/pypi/${encodeURIComponent(e)}${this.buildHashParams()}`;return this.scanGenericPackage(e,"pypi",t,"PyPI package",s)}async scanWordPressPlugin(e,s){const t=`${BASE_URL}/analysis/live/store/wordpress/${encodeURIComponent(e)}${this.buildHashParams()}`;return this.scanGenericExtension(e,t,"WordPress plugin",s)}async scanHuggingFace(e,s){const t=`${BASE_URL}/analysis/live/store/huggingface/${encodeURIComponent(e)}${this.buildHashParams()}`;return this.scanGenericExtension(e,t,"Hugging Face model",s)}async scanAppSourceAddin(e,s){const t=`${BASE_URL}/analysis/live/store/appsource/${e}${this.buildHashParams()}`;return this.scanGenericExtension(e,t,"AppSource add-in",s)}async scanPowerShellModule(e,s){const t=`${BASE_URL}/analysis/live/store/powershellgallery/${encodeURIComponent(e)}${this.buildHashParams()}`;return this.scanGenericPackage(e,"powershellgallery",t,"PowerShell module",s)}async scanSalesforceApp(e,s){const t=`${BASE_URL}/analysis/live/store/salesforce/${e}${this.buildHashParams()}`;return this.scanGenericExtension(e,t,"Salesforce app",s)}async scanByPlatform(e,s,t){if(s in BROWSER_STORE_MAP)return this.scanExtension(e,s,t);switch(s){case"vscode":return this.scanVSCodeExtension(e,t);case"openvsx":return this.scanOpenVSXExtension(e,t);case"jetbrains":return this.scanJetBrainsPlugin(e,t);case"npmjs":return this.scanNpmPackage(e,t);case"pypi":return this.scanPyPIPackage(e,t);case"wordpress":return this.scanWordPressPlugin(e,t);case"huggingface":return this.scanHuggingFace(e,t);case"appsource":return this.scanAppSourceAddin(e,t);case"powershellgallery":return this.scanPowerShellModule(e,t);default:throw new Error(`Unsupported platform: ${s}`)}}async scanGenericExtension(e,s,t,n){const a=await this.ensureBrowser(),r=await a.newPage();try{this.reportProgress(n,{phase:"initializing",message:`Starting ${t} scan...`}),this.reportProgress(n,{phase:"navigating",message:`Navigating to ${s}`});const a=this.waitForScanResultEvent(r,"extension");if(await r.goto(s,{waitUntil:"networkidle2",timeout:this.options.timeout}),this.reportProgress(n,{phase:"scanning",message:`Waiting for ${t} analysis...`}),this.options.waitForResults){const t=await a;if(t)return this.reportProgress(n,{phase:"complete",message:"Scan complete"}),this.mapExtensionEventResult(t,e,s)}throw this.reportProgress(n,{phase:"complete",message:"Scan error"}),new Error("Scan error")}finally{await r.close()}}async scanGenericPackage(e,s,t,n,a){const r=await this.ensureBrowser(),i=await r.newPage();try{this.reportProgress(a,{phase:"initializing",message:`Starting ${n} scan...`}),this.reportProgress(a,{phase:"navigating",message:`Navigating to ${t}`});const r=this.waitForScanResultEvent(i,"extension");if(await i.goto(t,{waitUntil:"networkidle2",timeout:this.options.timeout}),this.reportProgress(a,{phase:"scanning",message:`Waiting for ${n} analysis...`}),this.options.waitForResults){const n=await r;if(n)return this.reportProgress(a,{phase:"complete",message:"Scan complete"}),this.mapPackageEventResult(n,e,s,t)}throw this.reportProgress(a,{phase:"complete",message:"Scan error"}),new Error("Scan error")}finally{await i.close()}}async waitForScanResultEvent(e,s){return new Promise(t=>{const n=setTimeout(()=>{console.log("[Scanner] Timeout waiting for scan_result event"),t(null)},this.options.timeout);e.exposeFunction("__browsertotalScanResult",e=>{clearTimeout(n),e?.type===s?(console.log("[Scanner] Received scan_result event:",e.type),t(e)):(console.log("[Scanner] Received wrong event type:",e?.type,"expected:",s),t(null))}).catch(()=>{}),e.evaluateOnNewDocument("\n window.addEventListener('scan_result', function(event) {\n console.log('[BrowserTotal] scan_result event fired');\n if (typeof window.__browsertotalScanResult === 'function') {\n window.__browsertotalScanResult(event.detail);\n }\n });\n ").catch(()=>{e.evaluate("\n window.addEventListener('scan_result', function(event) {\n console.log('[BrowserTotal] scan_result event fired (late binding)');\n if (typeof window.__browsertotalScanResult === 'function') {\n window.__browsertotalScanResult(event.detail);\n }\n });\n ").catch(()=>{})})})}mapUrlEventResult(e,s,t){const n=e.data||{};return{url:s,status:this.mapStatus(e.status,n.riskLevel),score:n.score,threats:n.threats?.map(e=>({type:"string"==typeof e?e:e.type||e.description,severity:e.severity||"medium",description:e.description}))||n.vulnerabilities?.map(e=>({type:e.type||e.vulnerability||e.description,severity:e.severity||"medium",description:e.description})),categories:n.categories,scanUrl:t.replace(/#.*$/,""),timestamp:new Date(e.timestamp||Date.now()),raw:e}}mapExtensionEventResult(e,s,t){const n=e.data||{};return{extensionId:s,name:n.name,status:this.mapStatus(e.status,n.riskLevel),score:n.score,permissions:n.permissions,threats:n.threats?.map(e=>({type:"string"==typeof e?e:e.type||e.description,severity:e.severity||"medium",description:e.description}))||n.vulnerabilities?.map(e=>({type:e.type||e.vulnerability||e.description,severity:e.severity||"medium",description:e.description})),scanUrl:t.replace(/#.*$/,""),timestamp:new Date(e.timestamp||Date.now()),raw:e}}mapPackageEventResult(e,s,t,n){const a=e.data||{};return{packageName:s,platform:t,name:a.name,version:a.version,status:this.mapStatus(e.status,a.riskLevel),score:a.score,dependencies:a.dependencies,threats:a.threats?.map(e=>({type:"string"==typeof e?e:e.type||e.description,severity:e.severity||"medium",description:e.description}))||a.vulnerabilities?.map(e=>({type:e.type||e.vulnerability||e.description,severity:e.severity||"medium",description:e.description})),scanUrl:n.replace(/#.*$/,""),timestamp:new Date(e.timestamp||Date.now()),raw:e}}mapStatus(e,s){if("error"===e)return"error";const t=(s||"").toLowerCase();return"critical"===t||"malicious"===t?"malicious":"high"===t||"suspicious"===t?"suspicious":"safe"===t||"low"===t||"clean"===t?"safe":"medium"===t?"suspicious":"unknown"}async close(){this.browser&&(await this.browser.close(),this.browser=null)}}exports.BrowserTotalScanner=BrowserTotalScanner;
@@ -0,0 +1,53 @@
1
+ export interface ScannerOptions {
2
+ headless?: boolean;
3
+ timeout?: number;
4
+ waitForResults?: boolean;
5
+ disableAI?: boolean;
6
+ userDataDir?: string;
7
+ }
8
+ export type BrowserStore = 'chrome' | 'firefox' | 'edge' | 'opera' | 'safari' | 'brave';
9
+ export type Platform = 'vscode' | 'openvsx' | 'jetbrains' | 'npmjs' | 'pypi' | 'wordpress' | 'huggingface' | 'appsource' | 'powershellgallery' | 'salesforce';
10
+ export interface ThreatInfo {
11
+ type: string;
12
+ severity: 'low' | 'medium' | 'high' | 'critical';
13
+ description?: string;
14
+ }
15
+ export interface UrlScanResult {
16
+ url: string;
17
+ status: 'safe' | 'suspicious' | 'malicious' | 'unknown' | 'error';
18
+ score?: number;
19
+ threats?: ThreatInfo[];
20
+ categories?: string[];
21
+ scanUrl: string;
22
+ timestamp: Date;
23
+ raw?: Record<string, unknown>;
24
+ }
25
+ export interface ExtensionScanResult {
26
+ extensionId: string;
27
+ name?: string;
28
+ status: 'safe' | 'suspicious' | 'malicious' | 'unknown' | 'error';
29
+ score?: number;
30
+ permissions?: string[];
31
+ threats?: ThreatInfo[];
32
+ scanUrl: string;
33
+ timestamp: Date;
34
+ raw?: Record<string, unknown>;
35
+ }
36
+ export interface PackageScanResult {
37
+ packageName: string;
38
+ platform: string;
39
+ name?: string;
40
+ version?: string;
41
+ status: 'safe' | 'suspicious' | 'malicious' | 'unknown' | 'error';
42
+ score?: number;
43
+ dependencies?: Record<string, string>;
44
+ threats?: ThreatInfo[];
45
+ scanUrl: string;
46
+ timestamp: Date;
47
+ raw?: Record<string, unknown>;
48
+ }
49
+ export interface ScanProgress {
50
+ phase: 'initializing' | 'navigating' | 'scanning' | 'extracting' | 'complete';
51
+ message: string;
52
+ }
53
+ export type ProgressCallback = (progress: ScanProgress) => void;
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@browsertotal/scanner",
3
+ "version": "2.0.0",
4
+ "description": "Scan URLs and extensions using BrowserTotal.com",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/SeraphicSecurity/BrowserTotal"
17
+ },
18
+ "homepage": "https://browsertotal.com",
19
+ "bugs": {
20
+ "url": "https://github.com/SeraphicSecurity/BrowserTotal/issues"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "build:prod": "tsc && npm run minify",
25
+ "minify": "terser dist/index.js -o dist/index.js -c -m && terser dist/scanner.js -o dist/scanner.js -c -m && terser dist/types.js -o dist/types.js -c -m",
26
+ "dev": "tsc --watch",
27
+ "test": "tsx test.ts",
28
+ "clean": "rm -rf dist",
29
+ "prepublishOnly": "npm run clean && npm run build:prod"
30
+ },
31
+ "keywords": [
32
+ "browsertotal",
33
+ "security",
34
+ "scanner",
35
+ "extension",
36
+ "url",
37
+ "malware",
38
+ "browser",
39
+ "vscode",
40
+ "npm",
41
+ "pypi",
42
+ "chrome",
43
+ "firefox"
44
+ ],
45
+ "author": "BrowserTotal <support@browsertotal.com>",
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "puppeteer": "^24.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.0.0",
52
+ "terser": "^5.37.0",
53
+ "tsx": "^4.21.0",
54
+ "typescript": "^5.7.0"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ }
59
+ }