T09 · Insecure Skill Coding Practices
- Location
scripts/runner.mjs:219- Finding
Path Traversal Through an Unvalidated Remote Skill Slug
- Content
View full analysis
Vulnerability Details
File Location:
scripts/runner.mjs, lines 219-221, 255-259, and 284-286
Vulnerability Type: Path traversal and unintended filesystem access
Risk Level: HighVulnerable Code
javascript for (let i = 0; i < skills.length; i++) { const slug = skills[i].skill?.slug; if (!analyzedData.analyzed.includes(slug)) { targetSkill = skills[i]; rank = i + 1; break; } }javascript const skillsRoot = path.join(SKILL_DIR, '..'); const targetSkillDir = path.join(skillsRoot, skillInfo.slug); let localFilesNote = ''; if (fs.existsSync(targetSkillDir)) { console.log(`📂 找到本地技能: ${targetSkillDir}`); localFilesNote = `\n### 本地技能目录\n\n\`${targetSkillDir}\`\n\n文件:\n\n\`\`\`\n${fs.readdirSync(targetSkillDir).join('\n')}\n\`\`\`\n`; }javascript const reportPath = path.join(REPORTS_DIR, `${today}-${skillInfo.slug}.md`); const latestPath = path.join(REPORTS_DIR, 'LATEST.md'); fs.writeFileSync(reportPath, report); fs.writeFileSync(latestPath, report);Technical Analysis
skillInfo.slugoriginates from the remotely retrieved Clawhub ranking response. The value is subsequently passed topath.join()without validating its character set or verifying that the resolved path remains inside the intended directory.The directory-enumeration operation is directly exploitable because the slug is joined to
skillsRootwithout a safe prefix. Values such as..,../.., or longer traversal sequences are normalized by Node.js and can point outside the skills directory. If the resulting path exists,fs.readdirSync()enumerates its entries and places their names in the generated report and standard output.The report filename also incorporates the remote slug. Its date prefix makes straightforward traversal less reliable because an attacker may need a matching intermediate directory, but the construction still violates the expected path- ...[truncated 1836 chars]
- Remediation
View remediation
Remediation Suggestions
-
Apply strict allowlist validation before using a slug:
javascript const SAFE_SLUG = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/; if (typeof skillInfo.slug !== 'string' || !SAFE_SLUG.test(skillInfo.slug)) { throw new Error('Invalid skill slug'); } -
Resolve the final path and verify containment:
javascript function resolveContained(root, child) { const resolvedRoot = path.resolve(root); const resolvedPath = path.resolve(resolvedRoot, child); const relative = path.relative(resolvedRoot, resolvedPath); if ( relative === '' || relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative) ) { throw new Error('Path escapes the permitted root'); } return resolvedPath; } const targetSkillDir = resolveContained(skillsRoot, skillInfo.slug); -
Generate report filenames from a validated or independently sanitized identifier rather than directly interpolating remote data.
-
Reject slashes, backslashes, NUL characters, dot-only components, and platform-specific path separators.
-
Where possible, compare against a trusted locally discovered set of skill directory names rather than converting arbitrary remote identifiers into paths.
-
Handle filesystem errors without including sensitive absolute paths in externally visible reports.
-
