actualbudget/actual · error
Invalid upload filename
Error message
Invalid upload filename
What it means
uploadFileWeb sanitizes an uploaded filename before writing it into the browser virtual /uploads directory. If after stripping path components and NUL bytes the name is empty, '.', or '..', the write is refused with 'Invalid upload filename' to prevent path traversal or writing to the directory itself.
Source
Thrown at packages/loot-core/src/server/budgetfiles/app.ts:685
app.events.emit('load-budget', { id });
return {};
}
async function uploadFileWeb({
filename,
contents,
}: {
filename: string;
contents: ArrayBuffer;
}) {
if (!Platform.isBrowser) {
return null;
}
const safeName = filename.split(/[/\\]/).pop()?.replaceAll('\0', '');
if (!safeName || safeName === '.' || safeName === '..') {
throw new Error('Invalid upload filename');
}
await fs.writeFile(fs.join('/uploads', safeName), contents);
return {};
}
async function getBackups({ id }) {
return getAvailableBackups(id);
}
async function loadBackup({ id, backupId }) {
await _loadBackup(id, backupId);
}
async function makeBackup({ id }) {
await _makeBackup(id);
}
async function getLastOpenedBackup() {View on GitHub (pinned to d4334cb6e6)
Solutions
- Check the uploaded file's name in the browser before calling the handler and ensure it is a non-empty basename like 'report.xlsx'.
- Strip directory components yourself with filename.split(/[/\\]/).pop() and validate the result is not '.', '..', or empty.
- If a drag-and-drop dropped a directory, use a file input (accepting files only) or enumerate entry.files instead of passing the directory name.
- Sanitize with NUL-byte removal and reject reserved names before invoking uploadFileWeb.
Example fix
// before
await send('upload-file', { filename: dirEntry.name, contents });
// after
const safeName = dirEntry.name.split(/[/\\]/).pop()?.replaceAll('\0', '');
if (safeName && safeName !== '.' && safeName !== '..') {
await send('upload-file', { filename: safeName, contents });
} Defensive patterns
Strategy: validation
Validate before calling
function isValidUploadName(name) {
const safe = String(name).split(/[/\\]/).pop()?.replaceAll('\0', '');
return Boolean(safe) && safe !== '.' && safe !== '..';
}
if (!isValidUploadName(file.name)) throw new Error('Pick a real file, not a folder'); Type guard
function hasSafeName(f: unknown): f is string {
return typeof f === 'string' && f.length > 0 && !['.', '..'].includes(f.replaceAll('\0', ''));
} Try / catch
try {
await send('upload-file', { filename: file.name, contents });
} catch (e) {
if (e.message === 'Invalid upload filename') {
alert('The selected file has an invalid name. Choose a regular file.');
} else throw e;
} Prevention
- Use <input type="file"> which always yields real file basenames.
- Reject directories in drag-and-drop handlers via dataTransfer.items kind checks.
- Sanitize names (strip path and NULs) in the caller before sending.
- Add unit tests covering '', '.', '..', and separator-only names.
When it happens
Trigger: Calling uploadFileWeb (via the upload IPC handler from a browser file picker) with a filename that is empty, just '.', or just '..' — e.g. passing a raw directory path whose basename collapses, or a name consisting only of separators like '/' or '\\'.
Common situations: Users drag-and-drop a folder instead of a file; an integration passes an absolute path and takes the wrong basename; a buggy client sends a zero-length name field; Windows/Unix separator mixups leave only slashes after split.
Related errors
- Invalid --name: must be a non-empty string.
- No update fields provided. Use --name or --offbudget.
- Invalid cutoff date: expected a valid date (e.g. YYYY-MM-DD)
- No update fields provided. Use --name or --hidden.
- No update fields provided. Use --name or --hidden.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/0982b06ed069e61c.
Report an issue: GitHub.