Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions content/recipes/pompelmi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
### Scan uploaded files with Pompelmi
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recipe starts with an H3 heading (###). If recipes are expected to have a single top-level title for correct page structure/ToC (commonly #), consider promoting this to the appropriate top-level heading to keep rendering consistent across recipes.

Suggested change
### Scan uploaded files with Pompelmi
# Scan uploaded files with Pompelmi

Copilot uses AI. Check for mistakes.

Nest's built-in file upload support makes it straightforward to accept files with `FileInterceptor()` and validate basic constraints such as file size or MIME type.

For applications that accept untrusted uploads from users, you may also want to inspect the uploaded file contents before storing or further processing them. This can help catch cases such as spoofed file metadata or suspicious file structures.

One way to do that is with [Pompelmi](https://github.com/pompelmi/pompelmi), an open-source file upload scanning library for Node.js.

#### Installation

```bash
npm install pompelmi @pompelmi/nestjs-integration multer
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instructions tell readers to install @pompelmi/nestjs-integration, but the provided example doesn’t use it. Either remove it from the install command or update the example to actually demonstrate the NestJS integration package; otherwise readers may add an unnecessary dependency.

Suggested change
npm install pompelmi @pompelmi/nestjs-integration multer
npm install pompelmi multer

Copilot uses AI. Check for mistakes.
npm install -D @types/multer
```

#### Basic example

```typescript
import {
BadRequestException,
Controller,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import { scanBytes, STRICT_PUBLIC_UPLOAD } from 'pompelmi';
import { Express } from 'express';
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Express is typically used as a TypeScript namespace for Express.Multer.File and generally shouldn’t be imported as a named export from express (it may not exist as a runtime/named export depending on TS/module settings). Prefer removing this import and using the global Express namespace, or make it a type-only import that is valid for the project’s TS configuration.

Suggested change
import { Express } from 'express';

Copilot uses AI. Check for mistakes.

@Controller('files')
export class FilesController {
@Post('upload')
@UseInterceptors(
FileInterceptor('file', {
storage: memoryStorage(),
}),
Comment on lines +35 to +37
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using memoryStorage() without demonstrating/mentioning Multer limits (especially fileSize) can enable memory exhaustion (DoS) if a large upload reaches this endpoint. Consider adding a limits: { fileSize: ... } example here (or an explicit note near this snippet) to ensure scanning-in-memory is paired with a strict size cap.

Copilot uses AI. Check for mistakes.
)
async uploadFile(@UploadedFile() file: Express.Multer.File) {
if (!file) {
throw new BadRequestException('file is required');
}

const report = await scanBytes(file.buffer, {
filename: file.originalname,
mimeType: file.mimetype,
policy: STRICT_PUBLIC_UPLOAD,
failClosed: true,
});

if (report.verdict !== 'clean') {
throw new BadRequestException({
message: 'Upload blocked',
verdict: report.verdict,
reasons: report.reasons,
});
}

return {
ok: true,
filename: file.originalname,
verdict: report.verdict,
};
}
}
```

In this example:

- `FileInterceptor()` parses the incoming multipart upload
- `memoryStorage()` keeps the uploaded file in memory so `file.buffer` is available
- `scanBytes()` inspects the uploaded file buffer
- uploads that are not considered `clean` are rejected before storage

#### Why scan before storage?

Basic validators such as maximum size and expected MIME type are still useful, but they do not fully inspect the uploaded content.

A scan step before storage can be helpful when:

- users can upload files from untrusted sources
- files are later parsed, transformed, served, or shared with other users
- you want an extra inspection layer before data reaches downstream systems

#### Notes

- Prefer scanning **before** persisting files to disk or object storage
- Keep standard Nest validation in place for file size and upload shape
- Use content scanning as an additional layer, not a replacement for validation
Loading