[SECURITY] Path sanitization hardening for plugin traversal protection - #1
Conversation
Prevent directory traversal and absolute path abuse in plugin component operations and hook script destinations. Changes: - internal/claude/plugin.go: add safeComponentName, safeBaseName, and safeJoinWithin helpers; validate all plugin component names and hook script destinations before use in filepath operations - internal/config/validate.go: add validateComponentName helper; enforce absolute-path and traversal rejection for hook_scripts.dest and all plugin_component names at profile-validation time - internal/claude/plugin_extra_test.go: add 11 tests covering all new safety functions (accept, reject empty, reject dot/dotdot, reject absolute paths, reject path separators, correct path computation) - internal/config/config_test.go: add 11 tests covering validateComponentName and Validate-layer checks for hook script dest and plugin component names Vulnerabilities addressed: - Arbitrary file write via unvalidated hook_scripts.dest (jdforsythe#1) - Arbitrary symlink creation via unsanitized plugin component names (jdforsythe#2) - Validation gap allowing path traversal to reach profile load time (jdforsythe#3) Zero usability impact: legitimate paths (e.g. 'my-agent', 'hooks/script.sh') pass cleanly; only traversal and absolute path attacks are rejected.
Deferred Hardening ItemsTwo additional security improvements were identified during the audit but not included in this PR because they would change existing user-facing behaviour. Documenting here for tracking: Decision 1: Project config trust gateIssue: When a user runs Risk: Opening an untrusted repo and running jig could silently execute repo-injected prompts or flags. Potential mitigations (ranked by usability impact):
Decision 2: MCP server override approvalIssue: Project-local Risk: Supply-chain style attack where a trusted server name is hijacked by a project config. Potential mitigations (ranked by usability impact):
Happy to implement any of the above if the maintainer wants to explore further hardening. |
Summary
This PR addresses three security vulnerabilities related to path traversal and arbitrary file write attacks in jig's plugin and hook script handling.
Vulnerabilities Fixed
1. Arbitrary file write via unvalidated
hook_scripts.destFile:
internal/claude/plugin.goSeverity: High
Attack vector: A malicious project
.jig.yamlor profile setshook_scripts[*].destto a path like../../../etc/cron.d/backdoor. Sincefilepath.Joinwas called without containment checks, this would write the hook script to an arbitrary location on disk with the user's privileges.Fix: Introduced
safeJoinWithin(baseDir, rel string)which usesfilepath.Clean+filepath.Relto verify the resolved destination is strictly within the plugin temp directory before performing the join.2. Arbitrary symlink creation via unsanitized plugin component names
File:
internal/claude/plugin.goSeverity: High
Attack vector: A malicious plugin definition exposes components with names like
../../../home/user/.ssh/authorized_keys. These names were used directly infilepath.Joinfor symlink destinations, allowing symlinks to be created at arbitrary paths.Fix: Introduced
safeComponentName(name string)which rejects empty strings,.,.., absolute paths, and any name containing path separators.safeBaseNamewraps this for symlink source path handling.3. Validation gap at profile load time
File:
internal/config/validate.goSeverity: Medium
Attack vector: Malicious path values could pass
Validate()and only fail (or not fail) at runtime during plugin directory generation, with no user-visible error.Fix: Added
validateComponentNamehelper to validation layer;Validate()now enforces absolute-path and traversal rejection forhook_scripts[*].destand allplugin_components[*].agents/skills/commandsentries at profile load time.Changes
internal/claude/plugin.gosafeComponentName,safeBaseName,safeJoinWithinhelpers; integrated into 6 symlink loops + 1 hook script copyinternal/config/validate.govalidateComponentNamehelper; added validation checks for hook script dest and plugin component namesinternal/claude/plugin_extra_test.gointernal/config/config_test.goTesting
All existing and new tests pass:
Usability Impact
None. Legitimate paths pass all checks:
my-agent,code-analyzer,validate✓hooks/script.sh,validate.sh✓Deferred (Behavior-Changing)
Two additional hardening opportunities were identified but not implemented as they would change existing behavior and require user discussion:
system_promptandextra_flagsfrom project-local profiles are currently applied without approval. An interactive prompt on first use of a project profile could be added..mcp.jsondefinitions currently override global~/.claude/.mcp.jsonwithout confirmation. A read-only append policy (project can add new servers but not override existing globals) could be considered.