malshare-sdk 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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +366 -0
- package/dist/index.js +215 -0
- package/package.json +59 -0
- package/src/cli.js +210 -0
- package/src/index.d.ts +309 -0
- package/src/index.js +404 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 (2026-06-05)
|
|
4
|
+
|
|
5
|
+
- Initial release
|
|
6
|
+
- `MalShare` class with full API coverage:
|
|
7
|
+
- `listSamples()`, `listSamplesRaw()`, `listSources()`, `listFileNames()`, `listTypes()`
|
|
8
|
+
- `details(hash)`, `hashLookup(hashes)`, `search(query)`, `searchByType(fileType)`
|
|
9
|
+
- `download(hash)`, `downloadTo(hash, path)`
|
|
10
|
+
- `upload(file)`, `downloadUrl(url)`, `downloadUrlStatus(guid)`
|
|
11
|
+
- `getQuota()`
|
|
12
|
+
- CLI tool (`malshare` command)
|
|
13
|
+
- Bun, Node.js, Deno, Cloudflare Workers support
|
|
14
|
+
- Zero dependencies
|
|
15
|
+
- 19 unit tests
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 {{ORG}}
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# MalShare SDK
|
|
2
|
+
|
|
3
|
+
JavaScript/TypeScript client for the [MalShare](https://malshare.com) API — a free malware sample repository with **1M+ samples**. Works on **Bun**, **Node.js**, **Deno**, and **Cloudflare Workers**. Zero dependencies.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/malshare-sdk)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://bun.sh)
|
|
8
|
+
[]()
|
|
9
|
+
[]()
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add malshare-sdk # or: npm install malshare-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Get a free API key at [malshare.com/register.php](https://malshare.com/register.php). Free accounts get **2,000 API calls/day**.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export MALSHARE_KEY=your-key-here
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { MalShare } from 'malshare-sdk';
|
|
27
|
+
|
|
28
|
+
const ms = new MalShare(process.env.MALSHARE_KEY);
|
|
29
|
+
|
|
30
|
+
// Latest sample hashes (24h)
|
|
31
|
+
const hashes = await ms.listSamples();
|
|
32
|
+
console.log(`${hashes.length} samples`);
|
|
33
|
+
|
|
34
|
+
// File metadata
|
|
35
|
+
const info = await ms.details('46faab8ab153...');
|
|
36
|
+
// → { MD5, SHA1, SHA256, F_TYPE: 'PE32 executable', F_SIZE: 123456, ... }
|
|
37
|
+
|
|
38
|
+
// Download a sample (live malware! handle with care)
|
|
39
|
+
const bytes = await ms.download('46faab8ab153...');
|
|
40
|
+
await Bun.write('/tmp/malware.bin', bytes);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## CLI Usage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Install globally
|
|
47
|
+
bun install -g malshare-sdk
|
|
48
|
+
|
|
49
|
+
# List today's samples
|
|
50
|
+
malshare list
|
|
51
|
+
|
|
52
|
+
# Sample details
|
|
53
|
+
malshare info 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09
|
|
54
|
+
|
|
55
|
+
# Download to file
|
|
56
|
+
malshare save 46faab8ab153... ./malware.zip
|
|
57
|
+
|
|
58
|
+
# Quota check
|
|
59
|
+
malshare quota
|
|
60
|
+
# → Daily limit: 2000
|
|
61
|
+
# → Remaining: 1523
|
|
62
|
+
|
|
63
|
+
# Search by file type
|
|
64
|
+
malshare type "PE32 executable"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## API Reference
|
|
70
|
+
|
|
71
|
+
### Constructor
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
new MalShare(apiKey, config?)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Param | Type | Default | Description |
|
|
78
|
+
|-------|------|---------|-------------|
|
|
79
|
+
| `apiKey` | `string` | *required* | API key from malshare.com/register.php |
|
|
80
|
+
| `config.baseUrl` | `string` | `https://malshare.com/api.php` | API endpoint (mirror support) |
|
|
81
|
+
| `config.timeoutMs` | `number` | `60000` | Request timeout (AbortController) |
|
|
82
|
+
| `config.fetch` | `function` | `globalThis.fetch` | Custom fetch (CF Workers, proxy, etc.) |
|
|
83
|
+
|
|
84
|
+
### Sample Listing (5 methods)
|
|
85
|
+
|
|
86
|
+
| Method | Returns | Description |
|
|
87
|
+
|--------|---------|-------------|
|
|
88
|
+
| `listSamples()` | `string[]` | SHA256 hashes from past 24h |
|
|
89
|
+
| `listSamplesRaw()` | `string` | Same, raw text (one per line) |
|
|
90
|
+
| `listSources()` | `object[]` | Sample sources with counts `{source, count}` |
|
|
91
|
+
| `listFileNames()` | `string[]` | Original file names |
|
|
92
|
+
| `listTypes()` | `object[]` | File types + counts `{type, count}` |
|
|
93
|
+
|
|
94
|
+
### Sample Info & Search (4 methods)
|
|
95
|
+
|
|
96
|
+
| Method | Returns | Description |
|
|
97
|
+
|--------|---------|-------------|
|
|
98
|
+
| `details(hash)` | `FileDetails \| null` | Full metadata: MD5, SHA1, SHA256, type, size, name, sources, first/last seen |
|
|
99
|
+
| `hashLookup(hashes)` | `FileDetails[]` | Bulk lookup via POST (max ~100 hashes) |
|
|
100
|
+
| `search(query)` | `string \| object` | Search by hash, source, or file name |
|
|
101
|
+
| `searchByType(type)` | `string[]` | Hashes by file type from past 24h |
|
|
102
|
+
|
|
103
|
+
### Download & Upload (5 methods)
|
|
104
|
+
|
|
105
|
+
| Method | Returns | Description |
|
|
106
|
+
|--------|---------|-------------|
|
|
107
|
+
| `download(hash)` | `Uint8Array` | Raw sample bytes ⚠️ live malware |
|
|
108
|
+
| `downloadTo(hash, path)` | `{path, size}` | Download + save to disk |
|
|
109
|
+
| `upload(file, name?)` | `UploadResult` | Upload a sample (increases quota temporarily) |
|
|
110
|
+
| `downloadUrl(url, recursive?)` | `DownloadUrlResult` | Submit URL for MalShare to crawl + add |
|
|
111
|
+
| `downloadUrlStatus(guid)` | `DownloadUrlStatus` | Check URL download task progress |
|
|
112
|
+
|
|
113
|
+
### Quota (1 method)
|
|
114
|
+
|
|
115
|
+
| Method | Returns | Description |
|
|
116
|
+
|--------|---------|-------------|
|
|
117
|
+
| `getQuota()` | `QuotaInfo` | `{limit, remaining}` |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### Type Definitions
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
interface FileDetails {
|
|
125
|
+
MD5: string; // 32-char hex
|
|
126
|
+
SHA1: string; // 40-char hex
|
|
127
|
+
SHA256: string; // 64-char hex
|
|
128
|
+
F_TYPE: string; // 'PE32 executable', 'ELF 64-bit', 'PDF document'...
|
|
129
|
+
F_SIZE: number; // bytes
|
|
130
|
+
F_NAME?: string; // original filename
|
|
131
|
+
SOURCES: string[]; // ['US', 'DE', 'JP', ...]
|
|
132
|
+
FIRST_SEEN?: string; // ISO timestamp
|
|
133
|
+
LAST_SEEN?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface QuotaInfo {
|
|
137
|
+
limit: number; // allocated per day (default: 2000)
|
|
138
|
+
remaining: number; // left today
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface UploadResult {
|
|
142
|
+
status: 'OK' | 'ERROR';
|
|
143
|
+
guid?: string; // upload tracking GUID
|
|
144
|
+
error?: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface DownloadUrlResult {
|
|
148
|
+
status: 'OK' | 'ERROR';
|
|
149
|
+
guid?: string; // download task GUID
|
|
150
|
+
error?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface DownloadUrlStatus {
|
|
154
|
+
status: 'missing' | 'pending' | 'processing' | 'finished';
|
|
155
|
+
guid?: string;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Usage Patterns
|
|
162
|
+
|
|
163
|
+
### Batch Download by Type
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
const ms = new MalShare(process.env.MALSHARE_KEY);
|
|
167
|
+
|
|
168
|
+
// Get all PE32 samples from today
|
|
169
|
+
const peHashes = await ms.searchByType('PE32 executable');
|
|
170
|
+
console.log(`${peHashes.length} PE32 samples today`);
|
|
171
|
+
|
|
172
|
+
// Download first 10 (be careful!)
|
|
173
|
+
for (const hash of peHashes.slice(0, 10)) {
|
|
174
|
+
const details = await ms.details(hash);
|
|
175
|
+
const bytes = await ms.download(hash);
|
|
176
|
+
await Bun.write(`./samples/${hash}.bin`, bytes);
|
|
177
|
+
console.log(`Downloaded: ${details.F_NAME || hash} (${details.F_SIZE} bytes)`);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Daily Quota Monitor
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
const ms = new MalShare(process.env.MALSHARE_KEY);
|
|
185
|
+
|
|
186
|
+
// Check before heavy operations
|
|
187
|
+
const { limit, remaining } = await ms.getQuota();
|
|
188
|
+
if (remaining < 100) {
|
|
189
|
+
console.warn(`Low quota: ${remaining}/${limit} — reset at midnight UTC`);
|
|
190
|
+
}
|
|
191
|
+
const used = (1 - remaining / limit) * 100;
|
|
192
|
+
console.log(`Quota: ${remaining}/${limit} (${used.toFixed(1)}% used)`);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Bulk Hash Lookup
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
const ms = new MalShare(process.env.MALSHARE_KEY);
|
|
199
|
+
|
|
200
|
+
// Look up up to 100 hashes at once
|
|
201
|
+
const hashes = ['abc123...', 'def456...', /* ... up to ~100 */];
|
|
202
|
+
const results = await ms.hashLookup(hashes);
|
|
203
|
+
|
|
204
|
+
for (const r of results) {
|
|
205
|
+
console.log(`${r.SHA256?.substring(0, 12)} | ${r.F_TYPE} | ${r.F_NAME}`);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### URL Submission for Crawling
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
const ms = new MalShare(process.env.MALSHARE_KEY);
|
|
213
|
+
|
|
214
|
+
// Submit a URL for MalShare to download
|
|
215
|
+
const { guid } = await ms.downloadUrl('http://evil.com/malware.exe');
|
|
216
|
+
|
|
217
|
+
// Poll until finished
|
|
218
|
+
let status = await ms.downloadUrlStatus(guid);
|
|
219
|
+
while (status.status === 'pending' || status.status === 'processing') {
|
|
220
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
221
|
+
status = await ms.downloadUrlStatus(guid);
|
|
222
|
+
}
|
|
223
|
+
console.log('Task finished:', status.status);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Cloudflare Workers
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
import { MalShare } from 'malshare-sdk';
|
|
230
|
+
|
|
231
|
+
export default {
|
|
232
|
+
async scheduled(event, env) {
|
|
233
|
+
const ms = new MalShare(env.MALSHARE_KEY, {
|
|
234
|
+
fetch: globalThis.fetch, // Workers-native fetch
|
|
235
|
+
});
|
|
236
|
+
const hashes = await ms.listSamples();
|
|
237
|
+
console.log(`${hashes.length} new samples today`);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Error Handling
|
|
245
|
+
|
|
246
|
+
All methods throw on HTTP errors (4xx/5xx), network failures, and timeouts.
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
try {
|
|
250
|
+
const info = await ms.details(hash);
|
|
251
|
+
} catch (err) {
|
|
252
|
+
if (err.message.includes('404')) {
|
|
253
|
+
// Sample not found
|
|
254
|
+
} else if (err.message.includes('429')) {
|
|
255
|
+
// Rate limited — back off
|
|
256
|
+
await new Promise(r => setTimeout(r, 60000));
|
|
257
|
+
} else if (err.name === 'AbortError') {
|
|
258
|
+
// Timeout — retry with longer timeout
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`details()` returns `null` for unknown hashes (doesn't throw).
|
|
264
|
+
`listSamples()` returns `[]` for empty responses.
|
|
265
|
+
`searchByType()` returns `[]` for unknown types.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Rate Limits & Fair Use
|
|
270
|
+
|
|
271
|
+
- Free accounts: **2,000 API calls/day** (resets at midnight UTC)
|
|
272
|
+
- Uploading samples **temporarily increases** your quota
|
|
273
|
+
- MalShare is an open-source project — be respectful
|
|
274
|
+
- Honored: `Retry-After` headers on 429 responses
|
|
275
|
+
- Recommended: 1-2 requests/second
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Platform Compatibility
|
|
280
|
+
|
|
281
|
+
| Platform | Support | Notes |
|
|
282
|
+
|----------|---------|-------|
|
|
283
|
+
| **Bun** | ✅ Full | `bun add malshare-sdk` |
|
|
284
|
+
| **Node.js** | ✅ Full | `>=18.0.0` (global fetch) |
|
|
285
|
+
| **Deno** | ✅ Full | `import { MalShare } from 'npm:malshare-sdk'` |
|
|
286
|
+
| **Cloudflare Workers** | ✅ | Custom `fetch`, no `downloadTo()` |
|
|
287
|
+
| **Browser** | ✅ | `window.fetch`, no `downloadTo()` |
|
|
288
|
+
| **Bun compile** | ✅ | Single binary with `bun build --compile` |
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Security
|
|
293
|
+
|
|
294
|
+
> ⚠️ **WARNING**: Samples from MalShare are **live, unmodified malware**.
|
|
295
|
+
> Always work in sandboxed VMs. Never execute on your host.
|
|
296
|
+
|
|
297
|
+
- Samples are stored as raw bytes — you choose the container
|
|
298
|
+
- Password-protected zip convention: `infected` (MalwareBazaar standard)
|
|
299
|
+
- No network requests during download (the binary is never executed by this SDK)
|
|
300
|
+
- API key: store in env vars only, never commit
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Architecture
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
┌──────────────────────────────────────────────┐
|
|
308
|
+
│ MalShare API │
|
|
309
|
+
│ malshare.com/api.php │
|
|
310
|
+
└──────────────────────────────────────────────┘
|
|
311
|
+
▲
|
|
312
|
+
│ fetch() + AbortController
|
|
313
|
+
┌────────────────────┴─────────────────────────┐
|
|
314
|
+
│ MalShare class │
|
|
315
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
|
316
|
+
│ │ Listing │ │ Info │ │ Download │ │
|
|
317
|
+
│ │ list*() │ │ details()│ │ download() │ │
|
|
318
|
+
│ │ │ │ hashLook │ │ downloadTo() │ │
|
|
319
|
+
│ │ │ │ search() │ │ upload() │ │
|
|
320
|
+
│ └──────────┘ └──────────┘ └──────────────┘ │
|
|
321
|
+
│ ┌──────────────────────────────────────┐ │
|
|
322
|
+
│ │ _request() — core │ │
|
|
323
|
+
│ │ URL params → fetch → parse → return │ │
|
|
324
|
+
│ └──────────────────────────────────────┘ │
|
|
325
|
+
└──────────────────────────────────────────────┘
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
| Component | Responsibility |
|
|
329
|
+
|-----------|---------------|
|
|
330
|
+
| `_request()` | Build URL, fetch with AbortController, parse response |
|
|
331
|
+
| `MalShare` class | Public API surface — all 14 methods delegate to `_request()` |
|
|
332
|
+
| CLI (`cli.js`) | Thin wrapper: parse args → call SDK → format output |
|
|
333
|
+
|
|
334
|
+
**Design decisions**: See [ADR-001](docs/adr/001-architecture.md) (TBD).
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Tests
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
# Unit tests (mocked fetch, no API key needed)
|
|
342
|
+
bun test
|
|
343
|
+
|
|
344
|
+
# Integration tests (real API calls)
|
|
345
|
+
MALSHARE_KEY=your-key bun test tests/malshare.int.js
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
| Suite | Tests | Coverage |
|
|
349
|
+
|-------|-------|----------|
|
|
350
|
+
| Unit | 48 | All 14 public methods, error boundaries, regression |
|
|
351
|
+
| Integration | 14 | Real API: list, details, search, download, quota |
|
|
352
|
+
| URL construction | 10 | All action + parameter combinations |
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Related Projects
|
|
357
|
+
|
|
358
|
+
- [MalShare.com](https://malshare.com) — the service itself
|
|
359
|
+
- [MalwareBazaar](https://bazaar.abuse.ch) — abuse.ch's malware repo (separate SDK: `@gutem/crawler/connectors/global/abuse-ch.js`)
|
|
360
|
+
- [VirusTotal](https://virustotal.com) — multi-engine malware scanner
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT © [gutem](https://github.com/gutem)
|
|
365
|
+
|
|
366
|
+
MalShare is a free community service by [@mal_share](https://twitter.com/mal_share).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__moduleCache.set(from, entry);
|
|
22
|
+
return entry;
|
|
23
|
+
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
29
|
+
var __export = (target, all) => {
|
|
30
|
+
for (var name in all)
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/index.js
|
|
40
|
+
var exports_src = {};
|
|
41
|
+
__export(exports_src, {
|
|
42
|
+
default: () => src_default,
|
|
43
|
+
MalShare: () => MalShare
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(exports_src);
|
|
46
|
+
|
|
47
|
+
class MalShare {
|
|
48
|
+
constructor(apiKey, config = {}) {
|
|
49
|
+
if (!apiKey)
|
|
50
|
+
throw new Error("MalShare requires an API key. Register at https://malshare.com/register.php");
|
|
51
|
+
this.config = {
|
|
52
|
+
apiKey,
|
|
53
|
+
baseUrl: config.baseUrl || "https://malshare.com/api.php",
|
|
54
|
+
timeoutMs: config.timeoutMs || 60000,
|
|
55
|
+
fetch: config.fetch || globalThis.fetch
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async _request(action, params = {}, opts = {}) {
|
|
59
|
+
const { raw = false, method = "GET" } = opts;
|
|
60
|
+
const qs = new URLSearchParams({ api_key: this.config.apiKey, action, ...params });
|
|
61
|
+
const url = `${this.config.baseUrl}?${qs}`;
|
|
62
|
+
const controller = new AbortController;
|
|
63
|
+
const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
64
|
+
try {
|
|
65
|
+
const res = await this.config.fetch(url, {
|
|
66
|
+
method,
|
|
67
|
+
signal: controller.signal,
|
|
68
|
+
headers: { Accept: "application/json, text/plain, */*" }
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
throw new Error(`MalShare API error: ${res.status} ${res.statusText}`);
|
|
72
|
+
}
|
|
73
|
+
if (raw) {
|
|
74
|
+
const buf = await res.arrayBuffer();
|
|
75
|
+
return new Uint8Array(buf);
|
|
76
|
+
}
|
|
77
|
+
const text = await res.text();
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(text);
|
|
80
|
+
} catch {
|
|
81
|
+
return text;
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async listSamples() {
|
|
88
|
+
const data = await this._request("getlist");
|
|
89
|
+
return Array.isArray(data) ? data : [];
|
|
90
|
+
}
|
|
91
|
+
async listSamplesRaw() {
|
|
92
|
+
return this._request("getlistraw");
|
|
93
|
+
}
|
|
94
|
+
async listSources() {
|
|
95
|
+
return this._request("getsources");
|
|
96
|
+
}
|
|
97
|
+
async listSourcesRaw() {
|
|
98
|
+
return this._request("getsourcesraw");
|
|
99
|
+
}
|
|
100
|
+
async listFileNames() {
|
|
101
|
+
return this._request("getfilenames");
|
|
102
|
+
}
|
|
103
|
+
async listTypes() {
|
|
104
|
+
return this._request("gettypes");
|
|
105
|
+
}
|
|
106
|
+
async details(hash) {
|
|
107
|
+
const data = await this._request("details", { hash });
|
|
108
|
+
if (!data || data.status === "ERROR" || !data.SHA256)
|
|
109
|
+
return null;
|
|
110
|
+
return {
|
|
111
|
+
MD5: data.MD5 || "",
|
|
112
|
+
SHA1: data.SHA1 || "",
|
|
113
|
+
SHA256: data.SHA256 || "",
|
|
114
|
+
F_TYPE: data.F_TYPE || "",
|
|
115
|
+
F_SIZE: data.F_SIZE || 0,
|
|
116
|
+
F_NAME: data.F_NAME || "",
|
|
117
|
+
SOURCES: data.SOURCES || [],
|
|
118
|
+
FIRST_SEEN: data.FIRST_SEEN || "",
|
|
119
|
+
LAST_SEEN: data.LAST_SEEN || "",
|
|
120
|
+
...data
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
async hashLookup(hashes) {
|
|
124
|
+
if (!Array.isArray(hashes) || hashes.length === 0)
|
|
125
|
+
return [];
|
|
126
|
+
const formData = new URLSearchParams;
|
|
127
|
+
formData.append("hashes", JSON.stringify(hashes));
|
|
128
|
+
const qs = new URLSearchParams({ api_key: this.config.apiKey, action: "hashlookup" });
|
|
129
|
+
const url = `${this.config.baseUrl}?${qs}`;
|
|
130
|
+
const controller = new AbortController;
|
|
131
|
+
const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
132
|
+
try {
|
|
133
|
+
const res = await this.config.fetch(url, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
body: formData,
|
|
136
|
+
signal: controller.signal,
|
|
137
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok)
|
|
140
|
+
throw new Error(`MalShare API error: ${res.status}`);
|
|
141
|
+
return res.json();
|
|
142
|
+
} finally {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async searchByType(fileType) {
|
|
147
|
+
const data = await this._request("type", { type: fileType });
|
|
148
|
+
return Array.isArray(data) ? data : [];
|
|
149
|
+
}
|
|
150
|
+
async search(query) {
|
|
151
|
+
return this._request("search", { query });
|
|
152
|
+
}
|
|
153
|
+
async download(hash) {
|
|
154
|
+
return this._request("getfile", { hash }, { raw: true });
|
|
155
|
+
}
|
|
156
|
+
async downloadTo(hash, filePath) {
|
|
157
|
+
const bytes = await this.download(hash);
|
|
158
|
+
if (typeof Bun !== "undefined") {
|
|
159
|
+
await Bun.write(filePath, bytes);
|
|
160
|
+
} else {
|
|
161
|
+
const { writeFileSync } = await import("node:fs");
|
|
162
|
+
writeFileSync(filePath, bytes);
|
|
163
|
+
}
|
|
164
|
+
return { path: filePath, size: bytes.length };
|
|
165
|
+
}
|
|
166
|
+
async upload(file, fileName = "sample.bin") {
|
|
167
|
+
const formData = new FormData;
|
|
168
|
+
formData.append("upload", new Blob([file]), fileName);
|
|
169
|
+
const qs = new URLSearchParams({ api_key: this.config.apiKey, action: "upload" });
|
|
170
|
+
const url = `${this.config.baseUrl}?${qs}`;
|
|
171
|
+
const controller = new AbortController;
|
|
172
|
+
const timer = setTimeout(() => controller.abort(), this.config.timeoutMs * 2);
|
|
173
|
+
try {
|
|
174
|
+
const res = await this.config.fetch(url, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
body: formData,
|
|
177
|
+
signal: controller.signal
|
|
178
|
+
});
|
|
179
|
+
return res.json();
|
|
180
|
+
} finally {
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async downloadUrl(url, recursive = false) {
|
|
185
|
+
const formData = new URLSearchParams;
|
|
186
|
+
formData.append("url", url);
|
|
187
|
+
if (recursive)
|
|
188
|
+
formData.append("recursive", "1");
|
|
189
|
+
const qs = new URLSearchParams({ api_key: this.config.apiKey, action: "download_url" });
|
|
190
|
+
const reqUrl = `${this.config.baseUrl}?${qs}`;
|
|
191
|
+
const controller = new AbortController;
|
|
192
|
+
const timer = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
193
|
+
try {
|
|
194
|
+
const res = await this.config.fetch(reqUrl, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
body: formData,
|
|
197
|
+
signal: controller.signal,
|
|
198
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" }
|
|
199
|
+
});
|
|
200
|
+
return res.json();
|
|
201
|
+
} finally {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async downloadUrlStatus(guid) {
|
|
206
|
+
return this._request("download_url_check", { guid });
|
|
207
|
+
}
|
|
208
|
+
async getQuota() {
|
|
209
|
+
const text = await this._request("getlimit");
|
|
210
|
+
const limit = parseInt(text.match(/LIMIT:\s*(\d+)/i)?.[1] || "0");
|
|
211
|
+
const remaining = parseInt(text.match(/REMAINING:\s*(\d+)/i)?.[1] || "0");
|
|
212
|
+
return { limit, remaining };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
var src_default = MalShare;
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "malshare-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "JavaScript/TypeScript SDK for the MalShare API — free malware sample repository. Works on Bun, Node.js, Deno, Cloudflare Workers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./src/index.js",
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"malshare": "./src/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./src/index.js",
|
|
15
|
+
"require": "./dist/index.cjs",
|
|
16
|
+
"types": "./src/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./cli": "./src/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"files": [
|
|
22
|
+
"src/",
|
|
23
|
+
"dist/",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0",
|
|
30
|
+
"bun": ">=1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "bun test",
|
|
34
|
+
"build": "bun test",
|
|
35
|
+
"prepublishOnly": "bun test"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/gutem/malshare-sdk"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"keywords": [
|
|
43
|
+
"malshare",
|
|
44
|
+
"malware",
|
|
45
|
+
"threat-intelligence",
|
|
46
|
+
"malware-analysis",
|
|
47
|
+
"malware-samples",
|
|
48
|
+
"infosec",
|
|
49
|
+
"cybersecurity",
|
|
50
|
+
"bun",
|
|
51
|
+
"deno",
|
|
52
|
+
"npm",
|
|
53
|
+
"sdk"
|
|
54
|
+
],
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"typescript": "^5.7.0",
|
|
57
|
+
"@playwright/test": "^1.48.0"
|
|
58
|
+
}
|
|
59
|
+
}
|