payloadcms/payload · critical · Forbidden
You are not allowed to perform this action.
Error message
You are not allowed to perform this action.
What it means
During duplication or re-upload (`shouldReupload`), Payload reconstructs the file from the existing document's `filename`/`url`. If that `filename` contains a path-traversal sequence — either `../` (Unix) or `..\\` (Windows backslash) — Payload throws `Forbidden` (HTTP 403). This blocks an attacker from crafting a document whose stored filename escapes `staticDir` on the next write (`getFileByPath(`${staticPath}/${filename}`)` would otherwise resolve outside the upload folder).
Source
Thrown at packages/payload/src/uploads/generateFileData.ts:122
imageSizes,
resizeOptions,
staticDir,
trimOptions,
withMetadata,
} = collectionConfig.upload
const staticPath = staticDir
const incomingFileData: Document = isDuplicating ? originalDoc : data
let isLocalFile = false
if (
!file &&
(isDuplicating || shouldReupload(uploadEdits, incomingFileData as Record<string, unknown>))
) {
const { filename, url } = incomingFileData as unknown as FileData
if (filename && (filename.includes('../') || filename.includes('..\\'))) {
throw new Forbidden(req.t)
}
if ((serverURL && url?.startsWith(serverURL)) || url?.startsWith('/')) {
isLocalFile = true
}
try {
if (!disableLocalStorage && isLocalFile) {
// File is stored locally
const filePath = `${staticPath}/${filename}`
const response = await getFileByPath(filePath)
file = response
overwriteExistingFiles = true
} else if (filename && url) {
// File is remote
file = await getExternalFile({
data: incomingFileData as unknown as FileData,
req,View on GitHub (pinned to 00c58b35c0)
Solutions
- Audit and sanitize stored `filename` values in the database (remove `..` segments); the normal write path uses `sanitize-filename` so any non-conforming value was written out-of-band.
- Trace where the offending `filename` originated (import script, custom hook, direct DB write) and fix the writer to use `sanitize-filename` / `getSafeFileName`.
- If the document is meant to reference an external file, use the `url` path (validated by `isURLAllowed`) rather than embedding traversal in `filename`.
- Add a migration to rewrite existing bad rows before re-uploading.
- Reject such filenames at ingest with a `beforeChange` hook that validates against `/\.\.[\\/]/`.
- For local file refetch, prefer resolving via the collection's storage metadata instead of trusting the stored filename string.
Example fix
// before — migration wrote a bad filename
{ filename: '../../etc/passwd', url: '/media/../../etc/passwd' }
// after — sanitize on write
import sanitize from 'sanitize-filename'
await payload.update({
collection: 'media',
id,
data: { filename: sanitize(badFilename) },
}) Defensive patterns
Strategy: validation
Validate before calling
function isSafeFilename(name: string): boolean {
return !name.includes('../') && !name.includes('..\\')
}
for (const doc of await payload.db.find({ collection: 'media', where: {} })) {
if (doc.filename && !isSafeFilename(doc.filename)) {
// flag for repair — sanitize and rewrite
}
} Type guard
function hasNoTraversal(name: unknown): name is string {
return typeof name === 'string' && !name.includes('../') && !name.includes('..\\')
}
if (!hasNoTraversal(doc.filename)) {
throw new Error(`Stored filename contains a traversal sequence: ${doc.filename}`)
} Try / catch
// Guard at ingest with a beforeChange hook
beforeChange: [({ data }) => {
if (typeof data.filename === 'string' && (data.filename.includes('../') || data.filename.includes('..\\'))) {
throw new Error('Refusing to store a filename with a path-traversal sequence')
}
}] Prevention
- Always write filenames through `sanitize-filename` / `getSafeFileName` (Payload's default path).
- Audit imported/migrated `filename` values for `../` and `..\\`.
- Never store user-supplied relative paths in the `filename` field.
When it happens
Trigger: A create/update/duplicate operation on an upload collection where `req.file` is absent, `isDuplicating` is true OR `shouldReupload(uploadEdits, incomingFileData)` is true, and `incomingFileData.filename` (the persisted filename on the document) contains `../` or `..\\`. This typically means a bad/stored filename in the database.
Common situations: A migration imported documents with un-sanitized filenames. A custom hook or external writer stored a relative path in the `filename` field. A plugin-cloud-storage prefix accidentally landed in `filename`. Manual DB edits left traversal sequences in place.
Related errors
- Invalid filename.
- You are not allowed to perform this action.
- File type ${mimeTypeFromExtension} (from extension ${typeFro
- The provided URL is not allowed.
- There was a problem while uploading the file. ${message}
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/5867b8646d5d101b.
Report an issue: GitHub.