@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 +366 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1 -0
- package/dist/scanner.d.ts +30 -0
- package/dist/scanner.js +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.js +1 -0
- package/package.json +59 -0
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)
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/scanner.js
ADDED
|
@@ -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;
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|