{"record":{"id":"b921acd678945a8d","repo":"FlowiseAI/Flowise","slug":"invalid-or-unsafe-file-name-name","errorCode":null,"errorMessage":"Invalid or unsafe file name: ${name}","messagePattern":"Invalid or unsafe file name: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/components/src/validator.ts","lineNumber":430,"sourceCode":"    // Strip the FILE-STORAGE:: prefix if present\n    let stripped = name.replace(/^FILE-STORAGE::/, '')\n    // Decode percent-encoded traversal sequences before basename extraction\n    try {\n        stripped = decodeURIComponent(stripped)\n    } catch (_) {\n        // If decoding fails the raw string is fine — basename will still strip dirs\n    }\n    // Normalize backslashes to forward slashes so path.basename works on all\n    // platforms (on Linux, path.basename does not treat \\ as a separator)\n    stripped = stripped.replace(/\\\\/g, '/')\n    // Extract only the base filename — removes all directory components\n    let baseName = path.basename(stripped)\n    // Run through sanitize-filename to strip OS-reserved chars, control chars, etc.\n    baseName = sanitize(baseName)\n    // Remove leading dots to prevent hidden files or relative path references\n    baseName = baseName.replace(/^\\.+/, '')\n    if (!baseName || isUnsafeFilePath(baseName)) {\n        throw new Error(`Invalid or unsafe file name: ${name}`)\n    }\n    return baseName\n}\n\n/**\n * Safely resolve an untrusted relative key/filename to an absolute path inside a\n * trusted base directory, guaranteeing the result cannot escape that directory.\n *\n * @param {string} baseDir The trusted base directory (e.g. a freshly created temp dir)\n * @param {string} key The untrusted relative key or filename\n * @returns {string} A validated absolute path guaranteed to be within baseDir\n * @throws {Error} If key is missing/invalid or the resolved path escapes baseDir\n */\nexport const getSafeFilePath = (baseDir: string, key: string): string => {\n    if (!key || typeof key !== 'string') {\n        throw new Error('Invalid file path: key is required and must be a string')\n    }\n","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/FlowiseAI/Flowise/blob/abe4a8601a058047b350c260676826e21dd14101/packages/components/src/validator.ts#L412-L448","documentation":"Thrown by sanitizeFileName() after it has stripped the FILE-STORAGE:: prefix, percent-decoded, extracted the basename via path.basename, run it through the sanitize-filename package, and removed leading dots — and the result (`baseName`) is either empty or still flagged by isUnsafeFilePath(). isUnsafeFilePath rejects any remaining `..`, encoded traversal bytes, null/control chars, absolute Unix/Windows roots, or UNC/extended-length prefixes. Reaching this throw means the input survived every normalization step yet is still dangerous, so it is treated as a malicious or corrupt file name.","triggerScenarios":"A name composed entirely of reserved/control characters that sanitize-filename strips to an empty string (e.g. a name of all dots, all backslashes, or all control bytes); a name whose decoded form still contains `..` after basename extraction on a platform where separators behave unexpectedly; a deliberately crafted payload like `....//....//etc/passwd` that reduces to a traversal fragment; a name with embedded null or control characters that survive sanitization.","commonSituations":"Penetration testing / fuzzing of the upload API; a storage migration that reintroduces raw user-supplied names; PATH_TRAVERSAL_SAFETY left enabled (the default) while a client sends OS-reserved or encoded payloads; a filename that legitimately had only an extension with no base portion and got stripped to nothing.","solutions":["Log the offending `name` server-side to identify whether it is malicious traffic or a legitimate edge case.","If legitimate, generate a safe replacement name (UUID) instead of forwarding the raw value, then retry the operation.","Sanitize/normalize the input at the source (client or upstream service) so it contains only alphanumerics, dash, underscore, dot before reaching Flowise.","Verify PATH_TRAVERSAL_SAFETY is intentionally left at its safe default ('false' disables the isUnsafeFilePath check entirely and must never be set in production)."],"exampleFix":"// before\nconst safe = sanitizeFileName(userSuppliedName) // throws on residual unsafe content\n\n// after\nlet safe: string\ntry {\n    safe = sanitizeFileName(userSuppliedName)\n} catch {\n    safe = crypto.randomUUID() // fall back to a guaranteed-safe name\n}\nlogger.warn('Rejected unsafe filename; substituted generated name', { original: userSuppliedName })","handlingStrategy":"validation","validationCode":"// Pre-screen with the same predicate the sanitizer uses\nif (isUnsafeFilePath(name)) {\n    return res.status(400).json({ message: 'Unsafe file name' })\n}\nlet safe: string\ntry {\n    safe = sanitizeFileName(name)\n} catch {\n    safe = crypto.randomUUID() // or reject\n}","typeGuard":null,"tryCatchPattern":"try {\n    const safe = sanitizeFileName(name)\n} catch (e) {\n    logger.warn('sanitizeFileName rejected input', { name, err: (e as Error).message })\n    // do NOT strip chars and retry with the same name — treat as malicious/corrupt\n    return res.status(400).json({ message: 'Invalid file name' })\n}","preventionTips":["Treat residual-unsafe as an attack signal: log and reject, don't silently clean.","Keep PATH_TRAVERSAL_SAFETY at its safe default (unset); setting it to 'false' disables isUnsafeFilePath.","Pre-normalize names to basename at the client/upstream so only single segments arrive."],"tags":["security","path-traversal","sanitization","filename","input-validation"],"backgroundTag":null,"analyzedSha":"abe4a8601a058047b350c260676826e21dd14101","analyzedAt":"2026-08-12T16:04:40.823Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}