{"record":{"id":"32b491a41c50c809","repo":"can1357/oh-my-pi","slug":"refusing-to-download-onto-a-non-regular-file-ab","errorCode":null,"errorMessage":"Refusing to download onto a non-regular file: ${absolutePath}","messagePattern":"Refusing to download onto a non-regular file: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cursor.ts","lineNumber":193,"sourceCode":"async function writeWithoutFollowingLinks(absolutePath: string, payload: string | Buffer): Promise<void> {\n\tawait fs.promises.mkdir(path.dirname(absolutePath), { recursive: true });\n\tconst handle = await fs.promises\n\t\t.open(\n\t\t\tabsolutePath,\n\t\t\tfs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK,\n\t\t)\n\t\t.catch((error: NodeJS.ErrnoException) => {\n\t\t\t// A readerless FIFO. Reported as the refusal it is, rather than the\n\t\t\t// bare \"no such device or address\" the errno spells out.\n\t\t\tif (error.code === \"ENXIO\") {\n\t\t\t\tthrow new Error(`Refusing to download onto a special file: ${absolutePath}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t});\n\ttry {\n\t\tconst stat = await handle.stat();\n\t\tif (!stat.isFile()) {\n\t\t\tthrow new Error(`Refusing to download onto a non-regular file: ${absolutePath}`);\n\t\t}\n\t\tif (stat.nlink > 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`Refusing to download onto a file with ${stat.nlink} hard links, which would overwrite its other names: ${absolutePath}`,\n\t\t\t);\n\t\t}\n\t\tawait handle.truncate(0);\n\t\tawait handle.writeFile(payload);\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\nfunction createToolResultMessage(\n\ttoolCallId: string,\n\ttoolName: string,\n\tresult: AgentToolResult<unknown>,\n\tisError: boolean,","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cursor.ts#L175-L211","documentation":"After opening the destination without following symlinks, the writer stats the handle and refuses if the inode is not a regular file. Downloads may only land on plain files, never directories, devices, sockets, or FIFOs that slipped past the open flags. This is an explicit integrity guard around overwriting existing content.","triggerScenarios":"The downloadPath resolves to an existing directory, socket, block/character device, or another special inode (a case the ENXIO open guard doesn't cover, e.g. a FIFO with a reader or a directory opened with O_CREAT which fails differently).","commonSituations":"A resource name collides with a directory of the same name; pointing the download at /dev/null or a unix socket; a stale symlink-target directory.","solutions":["Choose a download path that is a regular file (or does not exist yet).","If a directory occupies the name, remove or rename the directory first (`rm -r <path>`).","Do not target device or socket nodes; pick a path under your workspace."],"exampleFix":"// before\n{ downloadPath: \"/tmp/out\" }  // /tmp/out is a directory\n// after\n{ downloadPath: \"/tmp/out/report.json\" }","handlingStrategy":"validation","validationCode":"const s = await stat(downloadPath).catch(() => null);\nif (s && !s.isFile()) throw new Error(`${downloadPath} exists and is not a regular file`);","typeGuard":"function isPlainFile(s: import(\"node:fs\").Stats | undefined): boolean {\n  return !!s && s.isFile();\n}","tryCatchPattern":"try {\n  await downloadResource(res, downloadPath);\n} catch (e) {\n  if (String(e.message).startsWith(\"Refusing to download onto a non-regular file\")) {\n    // choose a different destination path\n  } else throw e;\n}","preventionTips":["Ensure the destination name doesn't collide with an existing directory or socket.","Download into a dedicated directory that only contains regular files.","Stat the destination before writing when reusing paths."],"tags":["filesystem","security","mcp"],"backgroundTag":"special-file-write-refused","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}