{"record":{"id":"2cfec44fbb99d211","repo":"chatboxai/chatbox","slug":"invalid-skill-name-path-traversal-not-allowed","errorCode":null,"errorMessage":"Invalid skill name: path traversal not allowed","messagePattern":"Invalid skill name: path traversal not allowed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/skills/ipc-handlers.ts","lineNumber":178,"sourceCode":"      return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }\n    }\n  })\n\n  ipcMain.handle(\n    'skills:execute-script',\n    async (\n      _event,\n      params: { skillName: string; scriptName: string; args?: string[] }\n    ): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number | null }> => {\n      const { skillName, scriptName, args = [] } = params\n\n      try {\n        if (!skillName || !scriptName) {\n          throw new Error('Skill name and script name are required')\n        }\n\n        if (skillName.includes('..') || skillName.includes('/') || skillName.includes('\\\\')) {\n          throw new Error('Invalid skill name: path traversal not allowed')\n        }\n\n        if (scriptName.includes('..') || scriptName.includes('/') || scriptName.includes('\\\\')) {\n          throw new Error('Invalid script name: path traversal not allowed')\n        }\n\n        const skillsDir = getSkillsDir()\n        const scriptPath = path.join(skillsDir, skillName, 'scripts', scriptName)\n        if (!fs.existsSync(scriptPath)) {\n          throw new Error(`Script not found: ${scriptName}`)\n        }\n        const resolvedSkillsDir = fs.realpathSync(skillsDir)\n        const resolvedScriptPath = fs.realpathSync(scriptPath)\n        if (!resolvedScriptPath.startsWith(`${resolvedSkillsDir}${path.sep}`)) {\n          throw new Error('Script path escapes skills directory')\n        }\n\n        const scriptDir = path.dirname(resolvedScriptPath)","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/skills/ipc-handlers.ts#L160-L196","documentation":"Thrown by the skills:execute-script IPC handler when skillName contains '..', '/', or '\\\\'. This is a path-traversal guard preventing the constructed scriptPath (path.join(skillsDir, skillName, 'scripts', scriptName)) from escaping the skills directory. It is the first of two layers — a syntactic check followed by a realpath containment check (error 107).","triggerScenarios":"A renderer (or any IPC client) sends skillName like '../', '..\\\\', 'a/../../../etc', or 'foo/bar'. Because path.join resolves these segments, the resulting path would point outside the skills directory without this guard.","commonSituations":"Malicious or buggy IPC payload; a skill whose name legitimately contains a slash is rejected (by design); testing the handler with crafted inputs. The realpath check (107) is the backstop if this string check is bypassed via symlinks.","solutions":["Use a flat skill identifier (alphanumeric, dash, underscore only) — never allow slashes in skill names.","Validate skillName against a strict allowlist pattern in the renderer before invoking the IPC.","If a nested path is genuinely needed, redesign the skill layout to be flat under skills/{name}/scripts/."],"exampleFix":"// before\nif (skillName.includes('..') || skillName.includes('/') || skillName.includes('\\\\')) {\n  throw new Error('Invalid skill name: path traversal not allowed')\n}\n\n// after — single strict allowlist regex covers traversal, slashes, and hidden dirs\nconst SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/\nif (!SAFE_NAME.test(skillName)) {\n  throw new Error(`Invalid skill name: ${skillName}`)\n}","handlingStrategy":"validation","validationCode":"const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/\nfunction isSafeSkillName(name: string): boolean {\n  return SAFE_NAME.test(name)\n}\nif (!isSafeSkillName(skillName)) {\n  throw new Error(`Invalid skill name: ${skillName}`)\n}","typeGuard":"function isSafeName(name: unknown): name is string {\n  return typeof name === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)\n}","tryCatchPattern":"// The handler returns { success: false, stderr: message } for all thrown errors,\n// so callers check the result envelope rather than catching.\nconst result = await ipcRenderer.invoke('skills:execute-script', params)\nif (!result.success && /path traversal/i.test(result.stderr)) {\n  showToast('Skill name contains invalid characters')\n}","preventionTips":["Enforce a strict identifier allowlist (alphanumeric, dash, underscore, dot) at install time so bad names never reach disk.","Reject skill names with slashes, backslashes, or '..' in the renderer before sending IPC.","Treat the string check as the first layer; the realpath check (107) is the backstop."],"tags":["security","path-traversal","ipc","skills","validation"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}