{"record":{"id":"238cd83054b3d546","repo":"mihomo-party-org/clash-party","slug":"plugin-file-too-large","errorCode":null,"errorMessage":"Plugin file too large","messagePattern":"Plugin file too large","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/resolve/plugin/file.ts","lineNumber":19,"sourceCode":"import { open } from 'fs/promises'\nimport { basename, extname, resolve } from 'path'\nimport { MAX_PLUGIN_FILE_BYTES } from './constants'\n\nexport function findPluginFile(args: string[]): string | undefined {\n  return args.find(\n    (arg) => !arg.startsWith('-') && !arg.includes('://') && extname(arg).toLowerCase() === '.cpx'\n  )\n}\n\nexport async function readPluginFile(filePath: string): Promise<IPluginFilePayload> {\n  if (extname(filePath).toLowerCase() !== '.cpx') throw new Error('Unsupported plugin file type')\n\n  const resolvedPath = resolve(filePath)\n  const handle = await open(resolvedPath, 'r')\n  try {\n    const stat = await handle.stat()\n    if (!stat.isFile()) throw new Error('Plugin path is not a file')\n    if (stat.size > MAX_PLUGIN_FILE_BYTES) throw new Error('Plugin file too large')\n\n    // Read at most one byte beyond the limit so a file growing after stat cannot bypass the cap.\n    const bytes = Buffer.alloc(MAX_PLUGIN_FILE_BYTES + 1)\n    let length = 0\n    while (length < bytes.length) {\n      const { bytesRead } = await handle.read(bytes, length, bytes.length - length, null)\n      if (bytesRead === 0) break\n      length += bytesRead\n    }\n    if (length > MAX_PLUGIN_FILE_BYTES) throw new Error('Plugin file too large')\n\n    return {\n      name: basename(resolvedPath),\n      fileBytesB64: bytes.subarray(0, length).toString('base64')\n    }\n  } finally {\n    await handle.close()\n  }","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/main/resolve/plugin/file.ts#L1-L37","documentation":"readPluginFile enforces MAX_PLUGIN_FILE_BYTES: if stat().size exceeds the cap the file is rejected before any bytes are read. This prevents loading unreasonably large (possibly malicious or corrupted) plugin payloads into memory.","triggerScenarios":"Calling readPluginFile on a .cpx file whose on-disk size is greater than MAX_PLUGIN_FILE_BYTES at the time of stat().","commonSituations":"A corrupted/inflated download, someone replacing the plugin with a huge archive, or a disk issue producing a bogus file size.","solutions":["Obtain a fresh copy of the plugin under the size limit from a trusted source.","Check the file size yourself (ls -l / stat) to confirm it exceeds the cap before retrying.","Verify disk health if sizes look implausible (sparse/growing files)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { statSync } from 'node:fs'\nimport { MAX_PLUGIN_FILE_BYTES } from './plugin/file'\nconst st = statSync(pluginPath)\nif (st.size > MAX_PLUGIN_FILE_BYTES) {\n  throw new Error(`Plugin file exceeds ${MAX_PLUGIN_FILE_BYTES} bytes`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  const payload = await readPluginFile(pluginPath)\n} catch (e) {\n  if ((e as Error).message === 'Plugin file too large') {\n    // re-download from a trusted source / report oversized file\n  } else throw e\n}","preventionTips":["Check file size before loading plugins from untrusted sources.","Re-download plugins if the size looks abnormal (corruption/inflation).","Keep plugin artifacts versioned so a known-good copy is available."],"tags":["plugin","filesystem","size-limit"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}