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