T05 · Unauthorized Access and Privilege Escalation
- Location
SKILL.md:120- Finding
Workspace Manifest Patterns Can Escape the Authorized Repository Root
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md:120-132andSKILL.md:175-188
Vulnerability Type: Unvalidated path traversal through repository-controlled workspace glob patterns
Risk Level: MediumVulnerable Code
python workspace_patterns = root_pkg.get('workspaces', []) # pnpm-workspace.yaml format pnpm_cfg = f'{root}/pnpm-workspace.yaml' if os.path.exists(pnpm_cfg): import yaml # pyyaml cfg = yaml.safe_load(Path(pnpm_cfg).read_text()) workspace_patterns = cfg.get('packages', []) # Expand glob patterns to actual directories pkg_dirs = [] for pattern in workspace_patterns: matched = glob.glob(f'{root}/{pattern}', recursive=True)The same weakness is present in Cargo workspace discovery:
python workspace_toml = Path(f'{root}/Cargo.toml').read_text() # Extract workspace members member_match = re.search(r'members\s*=\s*\[(.*?)\]', workspace_toml, re.DOTALL) if not member_match: return packages members = re.findall(r'"([^"]+)"', member_match.group(1)) for pattern in members: for pkg_dir in glob.glob(f'{root}/{pattern}'): toml_path = f'{pkg_dir}/Cargo.toml' if not os.path.exists(toml_path): continue content = Path(toml_path).read_text()Technical Analysis
Workspace patterns are obtained from repository-controlled
package.json,pnpm-workspace.yaml, andCargo.tomlfiles. These patterns are concatenated with the selected repository root and passed directly toglob.glob()without canonicalization or containment checks.A workspace pattern can contain parent-directory components such as
../../other-project/*. Although the resulting string begins with the selected root, filesystem path resolution processes the..components and may locate directories outside that root. A symlinked workspace directory can produce a similar boundary escape.Once an external directory matches, the d ...[truncated 2241 chars]
- Remediation
View remediation
Remediation Suggestions
Enforce repository-root containment before accepting or reading any workspace match:
- Resolve the configured root once with
Path(root).resolve(). - Reject absolute workspace patterns.
- Reject patterns containing parent-directory traversal components.
- Resolve every matched directory before use.
- Verify that each resolved candidate remains beneath the resolved root.
- Apply the same validation to npm, pnpm, Yarn, and Cargo discovery.
- Define an explicit symlink policy. For strict isolation, reject workspace directories that resolve through symlinks outside the root.
- Skip invalid entries and report them as security warnings rather than processing them.
Example containment helper:
python from pathlib import Path def require_within_root(root: Path, candidate: Path) -> Path: resolved_root = root.resolve(strict=True) resolved_candidate = candidate.resolve(strict=True) if not resolved_candidate.is_relative_to(resolved_root): raise ValueError( f"Workspace path escapes repository root: {resolved_candidate}" ) return resolved_candidateValidate patterns before glob expansion and validate every result afterward:
python root_path = Path(root).resolve(strict=True) for pattern in workspace_patterns: pattern_path = Path(pattern) if pattern_path.is_absolute() or '..' in pattern_path.parts: raise ValueError(f"Unsafe workspace pattern: {pattern}") for match in root_path.glob(pattern): pkg_dir = require_within_root(root_path, match) manifest = require_within_root( root_path, pkg_dir / 'package.json' ) if manifest.is_file(): content = manifest.read_text()Because glob patterns and symlink resolution have separate security implications, both pre-expansion pattern validation and post-expansion canonical-path cont ...[truncated 34 chars]
- Resolve the configured root once with
