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/src/cli.js ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * MalShare CLI — command-line interface for the MalShare API.
4
+ *
5
+ * Install: npm install -g malshare-sdk (or bun install -g)
6
+ * Usage: malshare <command> [options]
7
+ *
8
+ * Auth: MALSHARE_KEY env var, or --key flag
9
+ *
10
+ * @module malshare-sdk/cli
11
+ */
12
+
13
+ import { MalShare } from './index.js';
14
+ import { parseArgs } from 'node:util';
15
+
16
+ const KEY = process.env.MALSHARE_KEY || '';
17
+
18
+ /**
19
+ * Get configured MalShare client.
20
+ */
21
+ function getClient(key) {
22
+ if (!key) {
23
+ console.error('⚠️ MALSHARE_KEY env var is not set.');
24
+ console.error(' Register at https://malshare.com/register.php');
25
+ console.error(' Then: export MALSHARE_KEY=your-key-here');
26
+ process.exit(1);
27
+ }
28
+ return new MalShare(key);
29
+ }
30
+
31
+ /**
32
+ * Format bytes to human-readable.
33
+ */
34
+ function formatBytes(bytes) {
35
+ if (bytes < 1024) return `${bytes} B`;
36
+ if (bytes < 1_048_576) return `${(bytes / 1024).toFixed(1)} KB`;
37
+ return `${(bytes / 1_048_576).toFixed(1)} MB`;
38
+ }
39
+
40
+ // ══════════════════════════════════════════════════════════════════
41
+ // MAIN
42
+ // ══════════════════════════════════════════════════════════════════
43
+
44
+ async function main() {
45
+ const args = process.argv.slice(2);
46
+ const cmd = args[0];
47
+
48
+ // Parse --key flag
49
+ let key = KEY;
50
+ const keyIdx = args.indexOf('--key');
51
+ if (keyIdx > -1 && args[keyIdx + 1]) {
52
+ key = args[keyIdx + 1];
53
+ args.splice(keyIdx, 2);
54
+ }
55
+
56
+ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
57
+ console.log(`
58
+ MalShare CLI — https://malshare.com
59
+
60
+ Usage: malshare <command> [options]
61
+
62
+ Commands:
63
+ list List SHA256 hashes from the past 24 hours
64
+ raw List SHA256 hashes (raw text, one per line)
65
+ sources List sample sources from the past 24 hours
66
+ filenames List file names from the past 24 hours
67
+ types List file types and counts
68
+ info <hash> Get sample details by hash
69
+ search <q> Search by hash, source, or file name
70
+ type <type> List hashes by file type (PE32, ELF, PDF...)
71
+ download <h> Download a sample (raw bytes to stdout)
72
+ save <h> <f> Download a sample and save to file
73
+ quota Check API quota
74
+ url <url> Submit URL for MalShare to download
75
+
76
+ Auth:
77
+ export MALSHARE_KEY=your-key-here
78
+ Or use --key flag: malshare list --key your-key
79
+
80
+ Examples:
81
+ malshare list # latest samples
82
+ malshare info 46faab8ab153... # sample details
83
+ malshare save 46faab8ab153... ./malware.bin
84
+ malshare types # file type distribution
85
+ malshare quota # API calls remaining
86
+ `);
87
+ process.exit(0);
88
+ }
89
+
90
+ const ms = getClient(key);
91
+
92
+ try {
93
+ switch (cmd) {
94
+ case 'list': {
95
+ const hashes = await ms.listSamples();
96
+ console.log(`${hashes.length} samples in the last 24 hours`);
97
+ hashes.slice(0, 20).forEach((h) => console.log(` ${h}`));
98
+ break;
99
+ }
100
+
101
+ case 'raw': {
102
+ const text = await ms.listSamplesRaw();
103
+ process.stdout.write(text);
104
+ break;
105
+ }
106
+
107
+ case 'sources': {
108
+ const sources = await ms.listSources();
109
+ console.log(`${sources.length} sources`);
110
+ for (const s of (Array.isArray(sources) ? sources : [])) {
111
+ console.log(` ${s.source || s.name || JSON.stringify(s)}`);
112
+ }
113
+ break;
114
+ }
115
+
116
+ case 'filenames': {
117
+ const names = await ms.listFileNames();
118
+ console.log(`${names.length} file names`);
119
+ for (const n of names.slice(0, 20)) console.log(` ${n}`);
120
+ break;
121
+ }
122
+
123
+ case 'types': {
124
+ const types = await ms.listTypes();
125
+ console.log(`${types.length} file types`);
126
+ for (const t of (Array.isArray(types) ? types : [])) {
127
+ console.log(` ${t.type || t.name || JSON.stringify(t)}`);
128
+ }
129
+ break;
130
+ }
131
+
132
+ case 'info': {
133
+ const hash = args[1];
134
+ if (!hash) { console.error('Usage: malshare info <hash>'); process.exit(1); }
135
+ const details = await ms.details(hash);
136
+ if (!details) { console.log('Sample not found'); process.exit(1); }
137
+ console.log('SHA256: ', details.SHA256);
138
+ console.log('SHA1: ', details.SHA1);
139
+ console.log('MD5: ', details.MD5);
140
+ console.log('Type: ', details.F_TYPE);
141
+ console.log('Size: ', formatBytes(details.F_SIZE));
142
+ console.log('Name: ', details.F_NAME || 'N/A');
143
+ console.log('Sources: ', (details.SOURCES || []).join(', '));
144
+ break;
145
+ }
146
+
147
+ case 'search': {
148
+ const query = args.slice(1).join(' ');
149
+ if (!query) { console.error('Usage: malshare search <query>'); process.exit(1); }
150
+ const results = await ms.search(query);
151
+ console.log(results);
152
+ break;
153
+ }
154
+
155
+ case 'type': {
156
+ const fileType = args.slice(1).join(' ');
157
+ if (!fileType) { console.error('Usage: malshare type <filetype>'); process.exit(1); }
158
+ const hashes = await ms.searchByType(fileType);
159
+ console.log(`${hashes.length} ${fileType} samples (24h)`);
160
+ hashes.slice(0, 20).forEach((h) => console.log(` ${h}`));
161
+ break;
162
+ }
163
+
164
+ case 'download': {
165
+ const hash = args[1];
166
+ if (!hash) { console.error('Usage: malshare download <hash>'); process.exit(1); }
167
+ const bytes = await ms.download(hash);
168
+ process.stdout.write(bytes);
169
+ break;
170
+ }
171
+
172
+ case 'save': {
173
+ const hash = args[1];
174
+ const filePath = args[2];
175
+ if (!hash || !filePath) { console.error('Usage: malshare save <hash> <filepath>'); process.exit(1); }
176
+ const result = await ms.downloadTo(hash, filePath);
177
+ console.log(`✅ Saved ${formatBytes(result.size)} → ${result.path}`);
178
+ break;
179
+ }
180
+
181
+ case 'quota': {
182
+ const quota = await ms.getQuota();
183
+ console.log(`Daily limit: ${quota.limit}`);
184
+ console.log(`Remaining: ${quota.remaining}`);
185
+ console.log(`Used: ${(quota.limit - quota.remaining)}`);
186
+ console.log(`Usage: ${((1 - quota.remaining / quota.limit) * 100).toFixed(1)}%`);
187
+ break;
188
+ }
189
+
190
+ case 'url': {
191
+ const url = args[1];
192
+ if (!url) { console.error('Usage: malshare url <url> [--recursive]'); process.exit(1); }
193
+ const recursive = args.includes('--recursive');
194
+ const result = await ms.downloadUrl(url, recursive);
195
+ console.log(JSON.stringify(result, null, 2));
196
+ break;
197
+ }
198
+
199
+ default:
200
+ console.error(`Unknown command: ${cmd}`);
201
+ console.error('Run: malshare help');
202
+ process.exit(1);
203
+ }
204
+ } catch (err) {
205
+ console.error(`❌ Error: ${err.message}`);
206
+ process.exit(1);
207
+ }
208
+ }
209
+
210
+ main();
package/src/index.d.ts ADDED
@@ -0,0 +1,309 @@
1
+ /**
2
+ * MalShare SDK — JavaScript client for the MalShare API.
3
+ *
4
+ * MalShare is a free malware sample repository with 1M+ samples.
5
+ * API: https://malshare.com/doc.php
6
+ * Registration: https://malshare.com/register.php
7
+ *
8
+ * Works on: Bun, Node.js, Deno, Cloudflare Workers.
9
+ * Zero dependencies. Uses native fetch() or Bun/node:http.
10
+ *
11
+ * @module malshare-sdk
12
+ * @license MIT
13
+ *
14
+ * @example
15
+ * import { MalShare } from 'malshare-sdk';
16
+ * const ms = new MalShare('your-api-key');
17
+ * const hashes = await ms.listSamples();
18
+ * const sample = await ms.download('abc123...');
19
+ */
20
+ /**
21
+ * @typedef {Object} MalShareConfig
22
+ * @property {string} apiKey — MalShare API key
23
+ * @property {string} [baseUrl='https://malshare.com/api.php'] — API base URL
24
+ * @property {number} [timeoutMs=60000] — request timeout in ms
25
+ * @property {Function} [fetch] — custom fetch implementation (for CF Workers etc.)
26
+ */
27
+ /**
28
+ * @typedef {Object} FileDetails
29
+ * @property {string} MD5 — MD5 hash
30
+ * @property {string} SHA1 — SHA1 hash
31
+ * @property {string} SHA256 — SHA256 hash
32
+ * @property {string} F_TYPE — file type (e.g. 'PE32 executable')
33
+ * @property {number} F_SIZE — file size in bytes
34
+ * @property {string} [F_NAME] — original file name
35
+ * @property {string[]} [SOURCES] — sample sources
36
+ * @property {string} [FIRST_SEEN] — first seen timestamp
37
+ * @property {string} [LAST_SEEN] — last seen timestamp
38
+ */
39
+ /**
40
+ * @typedef {Object} UploadResult
41
+ * @property {string} status — 'OK' or 'ERROR'
42
+ * @property {string} [guid] — upload GUID for status tracking
43
+ * @property {string} [error] — error message if status is ERROR
44
+ */
45
+ /**
46
+ * @typedef {Object} DownloadUrlResult
47
+ * @property {string} status — 'OK' or 'ERROR'
48
+ * @property {string} [guid] — task GUID for checking status
49
+ * @property {string} [error] — error message
50
+ */
51
+ /**
52
+ * @typedef {Object} DownloadUrlStatus
53
+ * @property {string} status — 'missing' | 'pending' | 'processing' | 'finished'
54
+ * @property {string} [guid] — task GUID
55
+ */
56
+ /**
57
+ * @typedef {Object} QuotaInfo
58
+ * @property {number} limit — allocated API calls per day
59
+ * @property {number} remaining — remaining API calls
60
+ */
61
+ export class MalShare {
62
+ /**
63
+ * @param {string} apiKey — MalShare API key (free at https://malshare.com/register.php)
64
+ * @param {Partial<MalShareConfig>} [config]
65
+ */
66
+ constructor(apiKey: string, config?: Partial<MalShareConfig>);
67
+ /** @type {MalShareConfig} */
68
+ config: MalShareConfig;
69
+ /**
70
+ * Make an API request.
71
+ * @param {string} action — API action
72
+ * @param {Object} [params={}] — query parameters
73
+ * @param {Object} [opts={}] — request options
74
+ * @returns {Promise<any>}
75
+ */
76
+ _request(action: string, params?: Object, opts?: Object): Promise<any>;
77
+ /**
78
+ * List SHA256 hashes from the past 24 hours.
79
+ * @returns {Promise<string[]>}
80
+ */
81
+ listSamples(): Promise<string[]>;
82
+ /**
83
+ * List SHA256 hashes from the past 24 hours (raw text, one per line).
84
+ * @returns {Promise<string>}
85
+ */
86
+ listSamplesRaw(): Promise<string>;
87
+ /**
88
+ * List sample sources from the past 24 hours.
89
+ * @returns {Promise<{source: string, count: number}[]>}
90
+ */
91
+ listSources(): Promise<{
92
+ source: string;
93
+ count: number;
94
+ }[]>;
95
+ /**
96
+ * List sample sources from the past 24 hours (raw text).
97
+ * @returns {Promise<string>}
98
+ */
99
+ listSourcesRaw(): Promise<string>;
100
+ /**
101
+ * List file names from uploads in the past 24 hours.
102
+ * @returns {Promise<string[]>}
103
+ */
104
+ listFileNames(): Promise<string[]>;
105
+ /**
106
+ * Get file types and counts from the past 24 hours.
107
+ * @returns {Promise<{type: string, count: number}[]>}
108
+ */
109
+ listTypes(): Promise<{
110
+ type: string;
111
+ count: number;
112
+ }[]>;
113
+ /**
114
+ * Get detailed information about a sample.
115
+ *
116
+ * @param {string} hash — MD5, SHA1, or SHA256 hash
117
+ * @returns {Promise<FileDetails|null>}
118
+ *
119
+ * @example
120
+ * const details = await ms.details('46faab8ab153fae...');
121
+ * console.log(details.F_TYPE, details.MD5);
122
+ */
123
+ details(hash: string): Promise<FileDetails | null>;
124
+ /**
125
+ * Bulk hash lookup — supply an array of hashes.
126
+ *
127
+ * @param {string[]} hashes — array of hex-encoded hashes
128
+ * @returns {Promise<FileDetails[]>}
129
+ *
130
+ * @example
131
+ * const results = await ms.hashLookup(['abc123...', 'def456...']);
132
+ */
133
+ hashLookup(hashes: string[]): Promise<FileDetails[]>;
134
+ /**
135
+ * Search by file type (samples from past 24h).
136
+ *
137
+ * @param {string} fileType — e.g. 'PE32', 'ELF', 'PDF', 'Zip', 'JavaScript'
138
+ * @returns {Promise<string[]>} — array of MD5/SHA1/SHA256 hashes
139
+ *
140
+ * @example
141
+ * const hashes = await ms.searchByType('PE32 executable');
142
+ */
143
+ searchByType(fileType: string): Promise<string[]>;
144
+ /**
145
+ * Search samples by query (hash, source, or file name).
146
+ * Returns raw text results.
147
+ *
148
+ * @param {string} query — search term
149
+ * @returns {Promise<string>} — raw search results
150
+ */
151
+ search(query: string): Promise<string>;
152
+ /**
153
+ * Download a malware sample by hash.
154
+ * Returns raw bytes. **Handle with care — this is live malware.**
155
+ *
156
+ * @param {string} hash — MD5, SHA1, or SHA256 hash
157
+ * @returns {Promise<Uint8Array>} — raw file bytes
158
+ *
159
+ * @example
160
+ * const bytes = await ms.download('46faab8ab153fae...');
161
+ * await Bun.write('/tmp/malware.bin', bytes);
162
+ */
163
+ download(hash: string): Promise<Uint8Array>;
164
+ /**
165
+ * Download a sample and save to disk (Bun/Node only).
166
+ *
167
+ * @param {string} hash — file hash
168
+ * @param {string} filePath — where to save
169
+ * @returns {Promise<{path: string, size: number}>}
170
+ */
171
+ downloadTo(hash: string, filePath: string): Promise<{
172
+ path: string;
173
+ size: number;
174
+ }>;
175
+ /**
176
+ * Upload a malware sample.
177
+ * Uploading files temporarily increases your API quota.
178
+ *
179
+ * @param {Uint8Array|Buffer|Blob} file — the sample to upload
180
+ * @param {string} [fileName='sample.bin'] — file name
181
+ * @returns {Promise<UploadResult>}
182
+ */
183
+ upload(file: Uint8Array | Buffer | Blob, fileName?: string): Promise<UploadResult>;
184
+ /**
185
+ * Submit a URL for MalShare to download and add to its collection.
186
+ *
187
+ * @param {string} url — URL to download
188
+ * @param {boolean} [recursive=false] — enable crawling
189
+ * @returns {Promise<DownloadUrlResult>}
190
+ */
191
+ downloadUrl(url: string, recursive?: boolean): Promise<DownloadUrlResult>;
192
+ /**
193
+ * Check the status of a URL download task.
194
+ *
195
+ * @param {string} guid — task GUID from downloadUrl()
196
+ * @returns {Promise<DownloadUrlStatus>}
197
+ */
198
+ downloadUrlStatus(guid: string): Promise<DownloadUrlStatus>;
199
+ /**
200
+ * Get API quota information.
201
+ * @returns {Promise<QuotaInfo>}
202
+ */
203
+ getQuota(): Promise<QuotaInfo>;
204
+ }
205
+ export default MalShare;
206
+ export type MalShareConfig = {
207
+ /**
208
+ * — MalShare API key
209
+ */
210
+ apiKey: string;
211
+ /**
212
+ * — API base URL
213
+ */
214
+ baseUrl?: string | undefined;
215
+ /**
216
+ * — request timeout in ms
217
+ */
218
+ timeoutMs?: number | undefined;
219
+ /**
220
+ * — custom fetch implementation (for CF Workers etc.)
221
+ */
222
+ fetch?: Function | undefined;
223
+ };
224
+ export type FileDetails = {
225
+ /**
226
+ * — MD5 hash
227
+ */
228
+ MD5: string;
229
+ /**
230
+ * — SHA1 hash
231
+ */
232
+ SHA1: string;
233
+ /**
234
+ * — SHA256 hash
235
+ */
236
+ SHA256: string;
237
+ /**
238
+ * — file type (e.g. 'PE32 executable')
239
+ */
240
+ F_TYPE: string;
241
+ /**
242
+ * — file size in bytes
243
+ */
244
+ F_SIZE: number;
245
+ /**
246
+ * — original file name
247
+ */
248
+ F_NAME?: string | undefined;
249
+ /**
250
+ * — sample sources
251
+ */
252
+ SOURCES?: string[] | undefined;
253
+ /**
254
+ * — first seen timestamp
255
+ */
256
+ FIRST_SEEN?: string | undefined;
257
+ /**
258
+ * — last seen timestamp
259
+ */
260
+ LAST_SEEN?: string | undefined;
261
+ };
262
+ export type UploadResult = {
263
+ /**
264
+ * — 'OK' or 'ERROR'
265
+ */
266
+ status: string;
267
+ /**
268
+ * — upload GUID for status tracking
269
+ */
270
+ guid?: string | undefined;
271
+ /**
272
+ * — error message if status is ERROR
273
+ */
274
+ error?: string | undefined;
275
+ };
276
+ export type DownloadUrlResult = {
277
+ /**
278
+ * — 'OK' or 'ERROR'
279
+ */
280
+ status: string;
281
+ /**
282
+ * — task GUID for checking status
283
+ */
284
+ guid?: string | undefined;
285
+ /**
286
+ * — error message
287
+ */
288
+ error?: string | undefined;
289
+ };
290
+ export type DownloadUrlStatus = {
291
+ /**
292
+ * — 'missing' | 'pending' | 'processing' | 'finished'
293
+ */
294
+ status: string;
295
+ /**
296
+ * — task GUID
297
+ */
298
+ guid?: string | undefined;
299
+ };
300
+ export type QuotaInfo = {
301
+ /**
302
+ * — allocated API calls per day
303
+ */
304
+ limit: number;
305
+ /**
306
+ * — remaining API calls
307
+ */
308
+ remaining: number;
309
+ };