usebruno/bruno · error · Error
is not a valid filename
Error message
${request.filename} is not a valid filename What it means
Thrown by the 'renderer:save-new-request' IPC handler when the filename portion of a new request fails the strict filesystem-name validator (validateName). validateName rejects names containing <>:"/\|?* or control chars, leading space/hyphen, trailing dot/space, Windows reserved device names (CON, PRN, AUX, NUL, COM[0-9], LPT[0-9]), and names longer than 255 chars. The check runs on the base name with the collection format extension (e.g. .bru) stripped, so the extension itself is never the problem.
Solutions
- Sanitize request.filename (e.g. strip/replace /[<>:"/\\|?*\x00-\x1F]/ and trim leading spaces/hyphens and trailing dots/spaces) before invoking the IPC.
- Verify the basename is not a Windows reserved name (CON, PRN, AUX, NUL, COM1-9, LPT1-9) before sending.
- Pass only the filename (not a path) as request.filename; let the caller build the full pathname.
- Surface the validator's rules in the UI name input so users cannot submit invalid names.
Example fix
// before
await window.ipcRenderer.invoke('renderer:save-new-request', pathname, { filename: 'GET /users', ... });
// after
const clean = raw.replace(/[<>:"/\\|?*\x00-\x1F]/g, '').trim().replace(/^[\s-]+/, '').replace(/[.\s]+$/, '');
if (/^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i.test(clean)) throw new Error('reserved name');
await window.ipcRenderer.invoke('renderer:save-new-request', pathname, { filename: `${clean}.${format}`, ... }); Defensive patterns
Strategy: validation
Validate before calling
// Replicates bruno-electron validateName (filesystem.js:291)
function isValidRequestFilename(name) {
if (typeof name !== 'string' || name.length === 0 || name.length > 255) return false;
if (/^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i.test(name)) return false;
return /^[^\s\-<>:"/\\|?*\x00-\x1F]/.test(name)
&& /^[^<>:"/\\|?*\x00-\x1F]*$/.test(name)
&& /[^.\s<>:"/\\|?*\x00-\x1F]$/.test(name);
}
// strip the format extension the same way the handler does before validating
const base = request.filename.replace(/\.(bru|yml)$/, '');
if (!isValidRequestFilename(base)) throw new Error('invalid filename'); Type guard
function isCleanFilename(name) {
return typeof name === 'string'
&& name.length > 0 && name.length <= 255
&& !/^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i.test(name)
&& /^[^\s\-<>:"/\\|?*\x00-\x1F]/.test(name)
&& /^[^<>:"/\\|?*\x00-\x1F]*$/.test(name)
&& /[^.\s<>:"/\\|?*\x00-\x1F]$/.test(name);
} Try / catch
try {
await window.ipcRenderer.invoke('renderer:save-new-request', pathname, request);
} catch (e) {
if (/is not a valid filename/.test(e.message)) {
// sanitize and retry with a cleaned name, or surface to the user
} else throw e;
} Prevention
- Sanitize user-typed request names through the same regex the main process uses before the IPC call.
- Never put path separators in request.filename; build the full pathname on the caller side.
- Block Windows reserved device names (CON, PRN, AUX, NUL, COM1-9, LPT1-9) in the UI input.
When it happens
Trigger: Calling ipcMain 'renderer:save-new-request' with request.filename such as 'a/b', 'foo:bar', ' leading', 'trailing.', 'CON', or a name >255 chars. Any path separator or forbidden char in the filename triggers it because validateName is applied after stripping the format extension.
Common situations: A user pastes a request name containing a slash or colon (e.g. copied from a URL like 'GET /users'). Names ending in a dot on Windows. Names that match a DOS device name. A frontend bug that passes the full pathname instead of just the filename into request.filename.
Related errors
- A file with the name
- environment: already exists
- file already exists
- path: does not exist
- Source path: does not exist
AI-assisted analysis of usebruno/bruno@9bdd81c7bd (2026-08-13).
Data as JSON: /api/errors/35b669a5eb7d7ecf.
Report an issue: GitHub.
Appendix: source
Thrown at packages/bruno-electron/src/ipc/collection.js:554
});
// new request
ipcMain.handle('renderer:new-request', async (event, pathname, request) => {
try {
if (fs.existsSync(pathname)) {
throw new Error(`path: ${pathname} already exists`);
}
const collectionPath = findCollectionPathByItemPath(pathname);
if (!collectionPath) {
throw new Error('Collection not found for the given pathname');
}
const format = getCollectionFormat(collectionPath);
// For the actual filename part, we want to be strict
const baseFilename = request?.filename?.replace(`.${format}`, '');
if (!validateName(baseFilename)) {
throw new Error(`${request.filename} is not a valid filename`);
}
validatePathIsInsideCollection(pathname);
const content = await stringifyRequestViaWorker(request, { format });
await writeFile(pathname, content);
} catch (error) {
return Promise.reject(error);
}
});
// save request
ipcMain.handle('renderer:save-request', async (event, pathname, request, format) => {
try {
if (!fs.existsSync(pathname)) {
throw new Error(`path: ${pathname} does not exist`);
}
validatePathIsInsideCollection(pathname);View on GitHub (pinned to 9bdd81c7bd)