@msal95/fileguard 0.1.1
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 +530 -0
- package/index.js +2 -0
- package/package.json +122 -0
- package/src/adapters/express.js +86 -0
- package/src/adapters/fastify.js +105 -0
- package/src/adapters/index.js +3 -0
- package/src/adapters/nextjs.js +74 -0
- package/src/audit/logger.js +32 -0
- package/src/core/rateLimiter.js +55 -0
- package/src/core/sanitizer.js +62 -0
- package/src/core/validator.js +163 -0
- package/src/errors/UploadError.js +43 -0
- package/src/index.js +65 -0
- package/src/react/DropZone.jsx +146 -0
- package/src/react/FilePreview.jsx +152 -0
- package/src/react/ProgressBar.jsx +82 -0
- package/src/react/UploadButton.jsx +123 -0
- package/src/react/index.js +4 -0
- package/src/scanners/clamav.js +50 -0
- package/src/scanners/magicBytes.js +62 -0
- package/src/scanners/polyglot.js +58 -0
- package/src/scanners/virustotal.js +85 -0
- package/src/scanners/zipBomb.js +85 -0
- package/src/storage/cloudinary.js +56 -0
- package/src/storage/index.js +27 -0
- package/src/storage/local.js +39 -0
- package/src/storage/s3.js +62 -0
- package/types/index.d.ts +380 -0
package/README.md
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# fileguard
|
|
2
|
+
|
|
3
|
+
Production-grade secure file upload middleware for Node.js.
|
|
4
|
+
|
|
5
|
+
Not just a file picker โ real security: magic byte detection, ZIP bomb protection, polyglot file blocking, optional ClamAV + VirusTotal scanning, and unified adapters for Express, Next.js, and Fastify.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@msal95/fileguard)
|
|
8
|
+
[](https://www.npmjs.com/package/@msal95/fileguard)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://www.npmjs.com/package/@msal95/fileguard)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- ๐ **Magic bytes validation** โ reads the actual file signature, not just the extension or declared MIME type
|
|
17
|
+
- ๐ฃ **ZIP bomb detection** โ rejects archives with compression ratio > 100ร or > 1000 files
|
|
18
|
+
- ๐งฌ **Polyglot detection** โ blocks files that embed MZ/EXE, `<script>`, PHP, nested ZIP, or shell shebangs
|
|
19
|
+
- ๐งน **Filename sanitization** โ strips path traversal, null bytes, reserved names, and unsafe characters
|
|
20
|
+
- ๐ฆ **Rate limiting** โ per-user/IP in-memory limiter, no Redis required
|
|
21
|
+
- ๐ฆ **ClamAV scanning** โ opt-in, skips gracefully if daemon is unavailable
|
|
22
|
+
- ๐ **VirusTotal scanning** โ opt-in, skips gracefully on missing API key or network failure
|
|
23
|
+
- ๐๏ธ **Storage adapters** โ local disk, AWS S3, Cloudinary
|
|
24
|
+
- โก **Framework adapters** โ Express middleware, Next.js App Router handler, Fastify plugin
|
|
25
|
+
- โ๏ธ **React UI components** โ DropZone, UploadButton, ProgressBar, FilePreview with CSS variable theming
|
|
26
|
+
- ๐ **Audit logging** โ append-only JSON log of every upload attempt
|
|
27
|
+
- ๐ฆ **TypeScript** โ full type definitions included, no `@types/fileguard` needed
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @msal95/fileguard
|
|
35
|
+
yarn add @msal95/fileguard
|
|
36
|
+
pnpm add @msal95/fileguard
|
|
37
|
+
bun add @msal95/fileguard
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Optional peer dependencies โ install only what you need:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# S3 storage
|
|
44
|
+
npm install @aws-sdk/client-s3
|
|
45
|
+
yarn add @aws-sdk/client-s3
|
|
46
|
+
|
|
47
|
+
# Cloudinary storage
|
|
48
|
+
npm install cloudinary
|
|
49
|
+
yarn add cloudinary
|
|
50
|
+
|
|
51
|
+
# ClamAV scanning
|
|
52
|
+
npm install clamscan
|
|
53
|
+
yarn add clamscan
|
|
54
|
+
|
|
55
|
+
# React UI components
|
|
56
|
+
npm install react
|
|
57
|
+
yarn add react
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Validation Pipeline
|
|
63
|
+
|
|
64
|
+
Every upload passes through this fixed sequence. No step can be skipped.
|
|
65
|
+
|
|
66
|
+
| Step | Check |
|
|
67
|
+
|------|-------|
|
|
68
|
+
| 1 | File size |
|
|
69
|
+
| 2 | Extension allowlist |
|
|
70
|
+
| 3 | MIME type allowlist |
|
|
71
|
+
| 4 | Magic bytes (reads first 8 KB of buffer) |
|
|
72
|
+
| 5 | ZIP bomb detection (archive types only) |
|
|
73
|
+
| 6 | Polyglot detection |
|
|
74
|
+
| 7 | Filename sanitization |
|
|
75
|
+
| 8 | ClamAV scan *(opt-in)* |
|
|
76
|
+
| 9 | VirusTotal scan *(opt-in)* |
|
|
77
|
+
| 10 | Rate limit check |
|
|
78
|
+
| 11 | Store to adapter |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
import { createGuard } from '@msal95/fileguard'
|
|
86
|
+
|
|
87
|
+
const guard = createGuard({
|
|
88
|
+
allowedExtensions: ['jpg', 'png', 'pdf'],
|
|
89
|
+
allowedMimeTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
|
90
|
+
maxFileSize: 5 * 1024 * 1024, // 5 MB
|
|
91
|
+
storage: 'local',
|
|
92
|
+
localPath: './uploads',
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const result = await guard.process({
|
|
96
|
+
buffer,
|
|
97
|
+
filename: 'photo.jpg',
|
|
98
|
+
mimeType: 'image/jpeg',
|
|
99
|
+
size: buffer.length,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
if (result.success) {
|
|
103
|
+
console.log(result.data.url)
|
|
104
|
+
} else {
|
|
105
|
+
console.error(result.error, result.message)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Express
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import express from 'express'
|
|
115
|
+
import { createExpressMiddleware } from '@msal95/fileguard/express'
|
|
116
|
+
|
|
117
|
+
const app = express()
|
|
118
|
+
|
|
119
|
+
app.post(
|
|
120
|
+
'/upload',
|
|
121
|
+
createExpressMiddleware({
|
|
122
|
+
allowedExtensions: ['jpg', 'png', 'pdf'],
|
|
123
|
+
allowedMimeTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
|
124
|
+
maxFileSize: 5 * 1024 * 1024,
|
|
125
|
+
storage: 'local',
|
|
126
|
+
localPath: './uploads',
|
|
127
|
+
}),
|
|
128
|
+
(req, res) => {
|
|
129
|
+
if (!req.uploadResult.success) return res.status(422).json(req.uploadResult)
|
|
130
|
+
res.json(req.uploadResult)
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The middleware always calls `next()`. Validation errors appear in `req.uploadResult` โ nothing is ever thrown.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Next.js App Router
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
// app/api/upload/route.js
|
|
143
|
+
import { createNextHandler } from '@msal95/fileguard/nextjs'
|
|
144
|
+
|
|
145
|
+
export const POST = createNextHandler({
|
|
146
|
+
allowedExtensions: ['jpg', 'png', 'pdf'],
|
|
147
|
+
allowedMimeTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
|
148
|
+
maxFileSize: 5 * 1024 * 1024,
|
|
149
|
+
storage: 'local',
|
|
150
|
+
localPath: './public/uploads',
|
|
151
|
+
fieldName: 'file', // default
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Returns a `Response` with JSON. Status `200` on success, `422` on validation failure, `400` when no file found.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Fastify
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
import Fastify from 'fastify'
|
|
163
|
+
import { createFastifyPlugin } from '@msal95/fileguard/fastify'
|
|
164
|
+
|
|
165
|
+
const fastify = Fastify()
|
|
166
|
+
|
|
167
|
+
await fastify.register(createFastifyPlugin({
|
|
168
|
+
storage: 'local',
|
|
169
|
+
localPath: './uploads',
|
|
170
|
+
}))
|
|
171
|
+
|
|
172
|
+
fastify.post('/upload', { preHandler: fastify.uploadGuard() }, async (req, reply) => {
|
|
173
|
+
return req.uploadResult
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Storage Adapters
|
|
180
|
+
|
|
181
|
+
### Local
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
{ storage: 'local', localPath: './uploads' }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### S3
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npm install @aws-sdk/client-s3
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
{
|
|
195
|
+
storage: 's3',
|
|
196
|
+
bucket: 'my-bucket',
|
|
197
|
+
region: 'us-east-1',
|
|
198
|
+
prefix: 'uploads', // optional key prefix
|
|
199
|
+
endpoint: '...', // optional โ for S3-compatible services (MinIO, R2, etc.)
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Cloudinary
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm install cloudinary
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
{
|
|
211
|
+
storage: 'cloudinary',
|
|
212
|
+
cloudName: 'my-cloud',
|
|
213
|
+
apiKey: 'key',
|
|
214
|
+
apiSecret: 'secret',
|
|
215
|
+
resourceType: 'auto', // 'image' | 'video' | 'raw' | 'auto'
|
|
216
|
+
folder: 'uploads', // optional
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Optional Scanners
|
|
223
|
+
|
|
224
|
+
### ClamAV
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm install clamscan
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
{
|
|
232
|
+
scan: { clamav: true },
|
|
233
|
+
clamavOptions: {
|
|
234
|
+
clamdscan: { host: '127.0.0.1', port: 3310 },
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
If `clamscan` is not installed or the daemon is unreachable, the scan is **skipped with a console warning** โ the upload is never blocked by a missing scanner.
|
|
240
|
+
|
|
241
|
+
### VirusTotal
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
{
|
|
245
|
+
scan: { virustotal: true },
|
|
246
|
+
virustotalOptions: {
|
|
247
|
+
apiKey: process.env.VT_API_KEY,
|
|
248
|
+
pollIntervalMs: 5000, // default
|
|
249
|
+
maxPolls: 3, // default
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
If the API key is missing or the request fails, the scan is **skipped** โ the upload proceeds.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## React UI Components
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
npm install react
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```jsx
|
|
265
|
+
import { DropZone, UploadButton, ProgressBar, FilePreview } from '@msal95/fileguard/react'
|
|
266
|
+
|
|
267
|
+
function Uploader() {
|
|
268
|
+
const [file, setFile] = useState(null)
|
|
269
|
+
const [progress, setProgress] = useState(0)
|
|
270
|
+
|
|
271
|
+
const handleUpload = async (file) => {
|
|
272
|
+
setFile(file)
|
|
273
|
+
const form = new FormData()
|
|
274
|
+
form.append('file', file)
|
|
275
|
+
|
|
276
|
+
const xhr = new XMLHttpRequest()
|
|
277
|
+
xhr.upload.onprogress = (e) => setProgress(Math.round(e.loaded / e.total * 100))
|
|
278
|
+
xhr.open('POST', '/api/upload')
|
|
279
|
+
xhr.send(form)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<>
|
|
284
|
+
<DropZone
|
|
285
|
+
onUpload={handleUpload}
|
|
286
|
+
onError={(err) => console.error(err.message)}
|
|
287
|
+
accept={['jpg', 'png', 'pdf']}
|
|
288
|
+
maxSize={5 * 1024 * 1024}
|
|
289
|
+
/>
|
|
290
|
+
{file && <FilePreview file={file} onRemove={() => setFile(null)} />}
|
|
291
|
+
{progress > 0 && <ProgressBar progress={progress} label="Uploadingโฆ" />}
|
|
292
|
+
<UploadButton onUpload={handleUpload}>Pick a file</UploadButton>
|
|
293
|
+
</>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
All four components accept a `headless` prop โ set it to `true` to strip all built-in styles and apply your own CSS.
|
|
299
|
+
|
|
300
|
+
### CSS Variables
|
|
301
|
+
|
|
302
|
+
Theme any component by setting these on a parent element:
|
|
303
|
+
|
|
304
|
+
```css
|
|
305
|
+
:root {
|
|
306
|
+
--fg-primary: #2563eb;
|
|
307
|
+
--fg-border: #d1d5db;
|
|
308
|
+
--fg-bg: #fafafa;
|
|
309
|
+
--fg-bg-active: #eff6ff;
|
|
310
|
+
--fg-text: #111827;
|
|
311
|
+
--fg-text-muted: #9ca3af;
|
|
312
|
+
--fg-radius: 8px;
|
|
313
|
+
--fg-padding: 40px 24px;
|
|
314
|
+
--fg-font-size: 14px;
|
|
315
|
+
--fg-bar-height: 8px;
|
|
316
|
+
--fg-bar-bg: #e5e7eb;
|
|
317
|
+
--fg-btn-padding: 8px 18px;
|
|
318
|
+
--fg-btn-text: #ffffff;
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Rate Limiting
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
import { createGuard } from '@msal95/fileguard'
|
|
328
|
+
|
|
329
|
+
const guard = createGuard({
|
|
330
|
+
storage: 'local',
|
|
331
|
+
localPath: './uploads',
|
|
332
|
+
rateLimit: {
|
|
333
|
+
enabled: true,
|
|
334
|
+
maxUploads: 10, // per key per window
|
|
335
|
+
windowMs: 60_000, // 1 minute
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Pass a key (user ID or IP) as the second argument
|
|
340
|
+
const result = await guard.process(file, { key: req.ip })
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Audit Logging
|
|
346
|
+
|
|
347
|
+
```js
|
|
348
|
+
{
|
|
349
|
+
audit: {
|
|
350
|
+
enabled: true,
|
|
351
|
+
logPath: './logs/uploads.log', // appends JSON-newline entries
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Each log entry contains: `event`, `filename`, `size`, `storage`, `url` or `error`, and a UTC timestamp.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Low-Level API
|
|
361
|
+
|
|
362
|
+
Use the building blocks directly without a framework adapter:
|
|
363
|
+
|
|
364
|
+
```js
|
|
365
|
+
import { validateFile } from '@msal95/fileguard'
|
|
366
|
+
import { localStore } from '@msal95/fileguard/storage/local'
|
|
367
|
+
|
|
368
|
+
const validation = await validateFile(
|
|
369
|
+
{ buffer, filename: 'photo.png', mimeType: 'image/png', size: buffer.length },
|
|
370
|
+
{ allowedExtensions: ['png'], allowedMimeTypes: ['image/png'] }
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if (!validation.success) {
|
|
374
|
+
console.error(validation.error) // 'INVALID_EXTENSION' | 'INVALID_MAGIC_BYTES' | โฆ
|
|
375
|
+
} else {
|
|
376
|
+
const result = await localStore(
|
|
377
|
+
{ ...file, sanitizedFilename: validation.sanitizedFilename },
|
|
378
|
+
{ localPath: './uploads' }
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Result Shape
|
|
386
|
+
|
|
387
|
+
Every function returns a plain object โ nothing is ever thrown to the caller.
|
|
388
|
+
|
|
389
|
+
```js
|
|
390
|
+
// Success
|
|
391
|
+
{ success: true, data: { url, filename, size, mimeType, storage } }
|
|
392
|
+
|
|
393
|
+
// Failure
|
|
394
|
+
{ success: false, error: 'ERROR_CODE', message: 'Human readable message' }
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Error Codes
|
|
398
|
+
|
|
399
|
+
| Code | Trigger |
|
|
400
|
+
|------|---------|
|
|
401
|
+
| `FILE_TOO_LARGE` | File exceeds `maxFileSize` |
|
|
402
|
+
| `INVALID_EXTENSION` | Extension not in `allowedExtensions` |
|
|
403
|
+
| `INVALID_MIME_TYPE` | Declared MIME not in `allowedMimeTypes` |
|
|
404
|
+
| `INVALID_MAGIC_BYTES` | File content doesn't match declared type |
|
|
405
|
+
| `ZIP_BOMB_DETECTED` | Compression ratio > 100ร or > 1000 files |
|
|
406
|
+
| `POLYGLOT_DETECTED` | File embeds MZ, `<script>`, nested ZIP, or PHP |
|
|
407
|
+
| `UNSAFE_FILENAME` | Reserved for future use |
|
|
408
|
+
| `VIRUS_DETECTED` | ClamAV or VirusTotal flagged the file |
|
|
409
|
+
| `RATE_LIMIT_EXCEEDED` | Upload rate limit exceeded |
|
|
410
|
+
| `STORAGE_ERROR` | Write to storage adapter failed |
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Default Configuration
|
|
415
|
+
|
|
416
|
+
```js
|
|
417
|
+
{
|
|
418
|
+
maxFileSize: 10 * 1024 * 1024, // 10 MB
|
|
419
|
+
allowedExtensions: [
|
|
420
|
+
'jpg', 'jpeg', 'png', 'gif', 'webp',
|
|
421
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx',
|
|
422
|
+
],
|
|
423
|
+
allowedMimeTypes: [
|
|
424
|
+
'image/jpeg', 'image/png', 'image/gif',
|
|
425
|
+
'image/webp', 'application/pdf',
|
|
426
|
+
],
|
|
427
|
+
storage: 'local',
|
|
428
|
+
localPath: './uploads',
|
|
429
|
+
scan: {
|
|
430
|
+
magicBytes: true, // always on โ cannot be disabled
|
|
431
|
+
zipBomb: true, // runs for archive types
|
|
432
|
+
polyglot: true, // always on
|
|
433
|
+
clamav: false, // opt-in
|
|
434
|
+
virustotal: false, // opt-in
|
|
435
|
+
},
|
|
436
|
+
rateLimit: {
|
|
437
|
+
enabled: false,
|
|
438
|
+
maxUploads: 10,
|
|
439
|
+
windowMs: 60_000,
|
|
440
|
+
},
|
|
441
|
+
audit: {
|
|
442
|
+
enabled: false,
|
|
443
|
+
logPath: './logs/uploads.log',
|
|
444
|
+
},
|
|
445
|
+
sanitizeFilename: true,
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## TypeScript
|
|
452
|
+
|
|
453
|
+
Full type definitions are included โ no `@types/fileguard` needed.
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
import { createGuard } from '@msal95/fileguard'
|
|
457
|
+
import type { FileguardConfig, Result } from '@msal95/fileguard'
|
|
458
|
+
|
|
459
|
+
const guard = createGuard({ storage: 'local', localPath: './uploads' })
|
|
460
|
+
|
|
461
|
+
const result: Result = await guard.process({
|
|
462
|
+
buffer,
|
|
463
|
+
filename: 'photo.jpg',
|
|
464
|
+
mimeType: 'image/jpeg',
|
|
465
|
+
size: buffer.length,
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
if (result.success) {
|
|
469
|
+
console.log(result.data.url)
|
|
470
|
+
} else {
|
|
471
|
+
console.error(result.error, result.message)
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Sub-path Exports
|
|
478
|
+
|
|
479
|
+
```js
|
|
480
|
+
import { createGuard, validateFile } from '@msal95/fileguard'
|
|
481
|
+
import { createExpressMiddleware } from '@msal95/fileguard/express'
|
|
482
|
+
import { createNextHandler } from '@msal95/fileguard/nextjs'
|
|
483
|
+
import { createFastifyPlugin } from '@msal95/fileguard/fastify'
|
|
484
|
+
import { localStore } from '@msal95/fileguard/storage/local'
|
|
485
|
+
import { s3Store } from '@msal95/fileguard/storage/s3'
|
|
486
|
+
import { cloudinaryStore } from '@msal95/fileguard/storage/cloudinary'
|
|
487
|
+
import { DropZone, UploadButton } from '@msal95/fileguard/react'
|
|
488
|
+
import { scanWithClamAV } from '@msal95/fileguard/scanners/clamav'
|
|
489
|
+
import { scanWithVirusTotal } from '@msal95/fileguard/scanners/virustotal'
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## What's Built
|
|
495
|
+
|
|
496
|
+
| Module | Status |
|
|
497
|
+
|--------|--------|
|
|
498
|
+
| Core validation (size, extension, MIME, magic bytes) | โ
|
|
|
499
|
+
| ZIP bomb detection | โ
|
|
|
500
|
+
| Polyglot file detection | โ
|
|
|
501
|
+
| Filename sanitization | โ
|
|
|
502
|
+
| In-memory rate limiter | โ
|
|
|
503
|
+
| Audit logger | โ
|
|
|
504
|
+
| Local storage adapter | โ
|
|
|
505
|
+
| S3 storage adapter | โ
|
|
|
506
|
+
| Cloudinary storage adapter | โ
|
|
|
507
|
+
| Express middleware | โ
|
|
|
508
|
+
| Next.js App Router handler | โ
|
|
|
509
|
+
| Fastify plugin | โ
|
|
|
510
|
+
| ClamAV scanner (opt-in) | โ
|
|
|
511
|
+
| VirusTotal scanner (opt-in) | โ
|
|
|
512
|
+
| React UI components (4 components) | โ
|
|
|
513
|
+
| TypeScript definitions | โ
|
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Requirements
|
|
518
|
+
|
|
519
|
+
- Node.js >= 18.0.0
|
|
520
|
+
- `file-type`, `busboy`, `uuid` (included as dependencies)
|
|
521
|
+
- `@aws-sdk/client-s3` (optional โ S3 storage)
|
|
522
|
+
- `cloudinary` (optional โ Cloudinary storage)
|
|
523
|
+
- `clamscan` (optional โ ClamAV scanning)
|
|
524
|
+
- `react >= 18` (optional โ UI components)
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## License
|
|
529
|
+
|
|
530
|
+
MIT ยฉ [Muhammad Shahid](https://github.com/msal95)
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@msal95/fileguard",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Production-grade secure file upload middleware for Node.js โ magic bytes, ZIP bomb & polyglot detection, virus scanning, multi-storage, and React UI components",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"types": "./types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.js",
|
|
11
|
+
"types": "./types/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./express": {
|
|
14
|
+
"import": "./src/adapters/express.js",
|
|
15
|
+
"types": "./types/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./nextjs": {
|
|
18
|
+
"import": "./src/adapters/nextjs.js",
|
|
19
|
+
"types": "./types/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./fastify": {
|
|
22
|
+
"import": "./src/adapters/fastify.js",
|
|
23
|
+
"types": "./types/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./storage/local": {
|
|
26
|
+
"import": "./src/storage/local.js",
|
|
27
|
+
"types": "./types/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./storage/s3": {
|
|
30
|
+
"import": "./src/storage/s3.js",
|
|
31
|
+
"types": "./types/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./storage/cloudinary": {
|
|
34
|
+
"import": "./src/storage/cloudinary.js",
|
|
35
|
+
"types": "./types/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./react": {
|
|
38
|
+
"import": "./src/react/index.js",
|
|
39
|
+
"types": "./types/index.d.ts"
|
|
40
|
+
},
|
|
41
|
+
"./scanners/clamav": {
|
|
42
|
+
"import": "./src/scanners/clamav.js",
|
|
43
|
+
"types": "./types/index.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./scanners/virustotal": {
|
|
46
|
+
"import": "./src/scanners/virustotal.js",
|
|
47
|
+
"types": "./types/index.d.ts"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"index.js",
|
|
52
|
+
"src/",
|
|
53
|
+
"types/"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"typecheck": "tsc --noEmit"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"file-upload",
|
|
65
|
+
"security",
|
|
66
|
+
"middleware",
|
|
67
|
+
"express",
|
|
68
|
+
"nextjs",
|
|
69
|
+
"fastify",
|
|
70
|
+
"clamav",
|
|
71
|
+
"virustotal",
|
|
72
|
+
"magic-bytes",
|
|
73
|
+
"zip-bomb",
|
|
74
|
+
"polyglot",
|
|
75
|
+
"s3",
|
|
76
|
+
"cloudinary",
|
|
77
|
+
"react",
|
|
78
|
+
"typescript",
|
|
79
|
+
"multipart",
|
|
80
|
+
"upload-validation"
|
|
81
|
+
],
|
|
82
|
+
"author": "Muhammad Shahid",
|
|
83
|
+
"license": "MIT",
|
|
84
|
+
"repository": {
|
|
85
|
+
"type": "git",
|
|
86
|
+
"url": "git+https://github.com/msal95/fileguard.git"
|
|
87
|
+
},
|
|
88
|
+
"homepage": "https://github.com/msal95/fileguard#readme",
|
|
89
|
+
"bugs": {
|
|
90
|
+
"url": "https://github.com/msal95/fileguard/issues"
|
|
91
|
+
},
|
|
92
|
+
"dependencies": {
|
|
93
|
+
"busboy": "^1.6.0",
|
|
94
|
+
"file-type": "^19.0.0",
|
|
95
|
+
"uuid": "^9.0.0"
|
|
96
|
+
},
|
|
97
|
+
"devDependencies": {
|
|
98
|
+
"@types/node": "^25.8.0",
|
|
99
|
+
"typescript": "^5.0.0",
|
|
100
|
+
"vitest": "^1.6.0"
|
|
101
|
+
},
|
|
102
|
+
"peerDependencies": {
|
|
103
|
+
"@aws-sdk/client-s3": "^3.0.0",
|
|
104
|
+
"clamscan": "^2.0.0",
|
|
105
|
+
"cloudinary": "^2.0.0",
|
|
106
|
+
"react": ">=18.0.0"
|
|
107
|
+
},
|
|
108
|
+
"peerDependenciesMeta": {
|
|
109
|
+
"@aws-sdk/client-s3": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"cloudinary": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"clamscan": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"react": {
|
|
119
|
+
"optional": true
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|