T09 · Insecure Skill Coding Practices
Error
- Location
- handler.js:52
- Finding
- Unsanitized Project Names Allow Filesystem Path Traversal and Recursive Deletion<![CDATA[ ## Vulnerability Details **File Location**: `handler.js:52-96`, `handler.js:175-204`, `handler.js:208-240` **Vulnerability Type**: Path traversal leading to arbitrary directory creation, relocation, and recursive deletion **Risk Level**: High ### Vulnerable Code ```js function projectCreate(name, description = '') { if (!name || name.trim() === '') { return { success: false, error: '项目名称不能为空' }; } const index = readIndex(); if (index.projects[name]) { return { success: false, error: `项目 "${name}" 已存在` }; } const projectDir = path.join(PROJECTS_DIR, name); ensureDir(projectDir); ensureDir(path.join(projectDir, 'memory')); ensureDir(path.join(projectDir, 'context')); const now = new Date().toISOString(); const projectConfig = { name, description, createdAt: now, updatedAt: now, agents: [], tags: [], status: 'active' }; fs.writeFileSync( path.join(projectDir, 'project.json'), JSON.stringify(projectConfig, null, 2) ); fs.writeFileSync( path.join(projectDir, 'memory', 'entries.json'), JSON.stringify({ entries: [] }, null, 2) ); index.projects[name] = { description, createdAt: now, updatedAt: now, status: 'active' }; index.currentProject = name; writeIndex(index); } ``` ```js function projectArchive(name) { const index = readIndex(); if (!index.projects[name]) { return { success: false, error: `项目 "${name}" 不存在` }; } index.projects[name].status = 'archived'; index.projects[name].archivedAt = new Date().toISOString(); if (index.currentProject === name) { index.currentProject = null; } writeIndex(index); ensureDir(ARCHIVED_DIR); const srcDir = path.join(PROJECTS_DIR, name); const destDir = path.join(ARCHIVED_DIR, name); if (fs.existsSync(srcDir)) { fs.renameSync(srcDir, destDir); } } ``` ```js function projectDelete(name, confirm) { if (!confirm) { return { success: false, error: '删 ...[truncated 3013 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Restrict project names to a conservative allowlist, such as letters, digits, spaces, underscores, and hyphens. 2. Explicitly reject: - Absolute paths. - `.` and `..`. - `/` and `\`. - Null bytes and control characters. - Names that normalize to reserved project directories. 3. Resolve and verify every filesystem target before use: ```js function resolveContainedPath(root, name) { if ( typeof name !== 'string' || !/^[\p{L}\p{N} _-]{1,100}$/u.test(name) || name === '.' || name === '..' ) { throw new Error('Invalid project name'); } const resolvedRoot = path.resolve(root); const resolvedTarget = path.resolve(resolvedRoot, name); if ( resolvedTarget === resolvedRoot || !resolvedTarget.startsWith(resolvedRoot + path.sep) ) { throw new Error('Project path escapes the allowed root'); } return resolvedTarget; } ``` 4. Apply containment checks independently to project and archive paths immediately before every create, read, write, rename, and delete operation. 5. Refuse to recursively delete the storage root, its ancestors, or any symbolic-link target. 6. Prefer generated immutable directory identifiers while storing the user-provided project name only as metadata. 7. Add regression tests for absolute paths, nested traversal, mixed separators, repeated traversal, symlinks, and root-directory deletion attempts. ]]>
