From 31e25617c5d8c4ad5670678c79eba35f2d143c53 Mon Sep 17 00:00:00 2001 From: Om Patel Date: Wed, 3 Jun 2026 16:10:32 -0400 Subject: [PATCH] fix(cli): use path.relative for robust path traversal checks --- packages/cli/src/utils/skillUtils.ts | 34 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/utils/skillUtils.ts b/packages/cli/src/utils/skillUtils.ts index f170171881..d3ab10596b 100644 --- a/packages/cli/src/utils/skillUtils.ts +++ b/packages/cli/src/utils/skillUtils.ts @@ -134,10 +134,8 @@ export async function installSkill( // Quick security check to prevent directory traversal out of temp dir when cloning if (tempDirToClean) { const resolvedTemp = path.resolve(tempDirToClean); - if ( - sourcePath !== resolvedTemp && - !sourcePath.startsWith(resolvedTemp + path.sep) - ) { + const relative = path.relative(resolvedTemp, sourcePath); + if (relative.startsWith('..') || path.isAbsolute(relative)) { throw new Error('Invalid path: Directory traversal not allowed.'); } } @@ -172,7 +170,12 @@ export async function installSkill( const skillDir = path.dirname(skill.location); const destPath = path.resolve(resolvedTarget, skillName); - if (!destPath.startsWith(resolvedTarget + path.sep)) { + const relative = path.relative(resolvedTarget, destPath); + if ( + relative.startsWith('..') || + path.isAbsolute(relative) || + relative === '' + ) { throw new Error('Invalid skill name: Path traversal detected.'); } @@ -249,7 +252,12 @@ export async function linkSkill( const skillSourceDir = path.dirname(skill.location); const destPath = path.resolve(resolvedTarget, skillName); - if (!destPath.startsWith(resolvedTarget + path.sep)) { + const relative = path.relative(resolvedTarget, destPath); + if ( + relative.startsWith('..') || + path.isAbsolute(relative) || + relative === '' + ) { throw new Error('Invalid skill name: Path traversal detected.'); } @@ -301,7 +309,12 @@ export async function uninstallSkill( const skillPath = path.resolve(resolvedTarget, name); // Security check: ensure the resolved path is within the target directory to prevent path traversal - if (!skillPath.startsWith(resolvedTarget + path.sep)) { + const relative = path.relative(resolvedTarget, skillPath); + if ( + relative.startsWith('..') || + path.isAbsolute(relative) || + relative === '' + ) { return null; } @@ -316,7 +329,12 @@ export async function uninstallSkill( } const skillDir = path.resolve(path.dirname(skillToUninstall.location)); - if (!skillDir.startsWith(resolvedTarget + path.sep)) { + const relative = path.relative(resolvedTarget, skillDir); + if ( + relative.startsWith('..') || + path.isAbsolute(relative) || + relative === '' + ) { return null; }