feat(examples): add secure-uploads example with Pompelmi security scanning#15571
feat(examples): add secure-uploads example with Pompelmi security scanning#15571SonoTommy wants to merge 1 commit intopayloadcms:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new examples/secure-uploads example project demonstrating a “secure uploads” pipeline for Payload by scanning uploaded file buffers with the Pompelmi library before accepting them.
Changes:
- Introduces a new Next.js + Payload example app scaffolded under
examples/secure-uploads - Adds a
MediacollectionbeforeOperationhook that scansreq.file.datawithscanBytesand rejects non-clean verdicts viaAPIError - Adds accompanying docs and project config (README, env example, tsconfig, Next config, gitignore)
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/secure-uploads/tsconfig.json | TypeScript configuration for the example project |
| examples/secure-uploads/src/payload.config.ts | Payload config using SQLite adapter + admin setup |
| examples/secure-uploads/src/payload-types.ts | Generated Payload types for the example |
| examples/secure-uploads/src/collections/Users.ts | Minimal auth-enabled Users collection |
| examples/secure-uploads/src/collections/Media.ts | Media upload collection with Pompelmi scanning hook |
| examples/secure-uploads/src/app/(payload)/layout.tsx | Generated Payload Next layout wiring server functions |
| examples/secure-uploads/src/app/(payload)/custom.scss | Placeholder for admin UI styling overrides |
| examples/secure-uploads/src/app/(payload)/api/graphql/route.ts | Generated GraphQL route handler |
| examples/secure-uploads/src/app/(payload)/api/[...slug]/route.ts | Generated REST route handlers |
| examples/secure-uploads/src/app/(payload)/admin/importMap.js | Generated admin import map |
| examples/secure-uploads/src/app/(payload)/admin/[[...segments]]/page.tsx | Generated admin catch-all page |
| examples/secure-uploads/src/app/(payload)/admin/[[...segments]]/not-found.tsx | Generated admin not-found page |
| examples/secure-uploads/src/app/(app)/page.tsx | Simple landing page describing the example |
| examples/secure-uploads/src/app/(app)/layout.tsx | Root app layout |
| examples/secure-uploads/package.json | Example dependencies/scripts (adds pompelmi, sqlite adapter, etc.) |
| examples/secure-uploads/next.config.mjs | Next config wrapped with withPayload |
| examples/secure-uploads/next-env.d.ts | Next TypeScript env declarations |
| examples/secure-uploads/README.md | Example documentation and usage instructions |
| examples/secure-uploads/INSTALL.md | Extra installation workaround note |
| examples/secure-uploads/.gitignore | Ignores build artifacts, sqlite db files, and uploaded media |
| examples/secure-uploads/.env.example | Example environment variables |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Update the following variables: | ||
|
|
||
| - \`DATABASE_URL\`: Your MongoDB connection string | ||
| - \`PAYLOAD_SECRET\`: A secure random string for JWT encryption | ||
|
|
There was a problem hiding this comment.
README setup instructions mention setting DATABASE_URL to a MongoDB connection string, but this example’s config uses the SQLite adapter and never reads process.env.DATABASE_URL. Update the README to describe SQLite setup (or switch the config to read DATABASE_URL) so the instructions match the actual runtime behavior.
| }, | ||
| db: sqliteAdapter({ | ||
| client: { | ||
| url: 'file:./payload.db', |
There was a problem hiding this comment.
The SQLite adapter is configured with a hard-coded file:./payload.db URL, while the example includes a DATABASE_URL entry in .env.example and the README references it. Consider reading the DB URL from process.env.DATABASE_URL (with a SQLite default) to keep config/docs consistent and make the example easier to reconfigure.
| url: 'file:./payload.db', | |
| url: process.env.DATABASE_URL || 'file:./payload.db', |
| throw new APIError('File security scan failed. Please try again.', 500, { | ||
| originalError: errorMessage, | ||
| }) |
There was a problem hiding this comment.
new APIError('File security scan failed. Please try again.', 500, ...) will default isPublic to false (because status is 500), so the client will typically receive a generic "Something went wrong." response instead of this message. If the intent is to show this safe, generic message in the Admin UI, pass isPublic: true (4th ctor arg) and avoid returning originalError details in the public response.
| throw new APIError('File security scan failed. Please try again.', 500, { | |
| originalError: errorMessage, | |
| }) | |
| // Expose a generic, safe message to the admin UI and avoid leaking internal error details | |
| throw new APIError('File security scan failed. Please try again.', 500, undefined, true) |
| There is currently a Corepack signature verification issue preventing `pnpm` from working properly. | ||
| This is a known issue with Corepack in some environments. | ||
|
|
||
| ## Workaround | ||
|
|
||
| You can install dependencies using npm instead: | ||
|
|
||
| ```bash | ||
| cd examples/secure-uploads | ||
| npm install --legacy-peer-deps | ||
| ``` |
There was a problem hiding this comment.
This INSTALL note claims pnpm is currently broken due to a Corepack signature issue and recommends npm install --legacy-peer-deps. This is likely to become outdated quickly and it also conflicts with the README which instructs pnpm install. Recommend removing this file or replacing it with a short, evergreen troubleshooting note that links to a specific upstream issue.
| # Used to encrypt JWT tokens | ||
| PAYLOAD_SECRET=YOUR_SECRET_HERE | ||
|
|
||
| # Used to configure CORS, format links and more. No trailing slash |
There was a problem hiding this comment.
.env.example documents NEXT_PUBLIC_SERVER_URL, but the example payload.config.ts does not use it for cors/csrf (unlike other Next-based examples). Either wire this env var into the config or remove/adjust the comment to avoid suggesting configuration that has no effect.
| # Used to configure CORS, format links and more. No trailing slash | |
| # Used by the app (e.g. to format links). No trailing slash |
What?
Added a new official example in
examples/secure-uploadsthat demonstrates how to implement a secure file upload pipeline using Pompelmi. This toolkit performs deep file inspection directly within the Node.js process.Why?
File uploads are one of the most common attack vectors in web applications. Standard validation (like file extension or size checks) often fails to detect:
This example provides Payload users with a privacy-first (no external cloud APIs) and high-performance pattern to harden their Media collections.
How?
beforeOperationhook in theMediacollection.APIErrorto provide feedback to the UI.Social Proof: Pompelmi has recently been featured in Help Net Security and the Bytes.dev newsletter as a robust security toolkit for the Node.js ecosystem.
Fixes # (None)