Conversation
…tained (#393) The getTypeDir() method in YamlVaultConfigRepository passed the user-supplied vaultType string directly to join() with no validation, allowing a crafted type like "../../.ssh" to escape .swamp/vault/. The fix pattern already existed in unified_data_repository.ts via assertPathContained(). This change applies the same approach here. Changes: - Added resolve to the @std/path import alongside the existing join - Ported assertPathContained() from unified_data_repository.ts (identical logic: resolves both paths and checks prefix containment) - Updated getTypeDir() to call assertPathContained() after constructing the candidate path, throwing before any I/O if the path escapes the vault base directory - Created yaml_vault_config_repository_test.ts with three tests: - Normal vault types ("aws", "local_encryption") resolve without error - "../../.ssh" throws Error: Path traversal detected - "../foo" throws Error: Path traversal detected getPath() requires no further change as it already depends on the validated output of getTypeDir(). Reported-by: umag <umag@users.noreply.github.com> Co-authored-by: umag <umag@users.noreply.github.com>
There was a problem hiding this comment.
✅ Approved
This PR correctly fixes the path traversal vulnerability reported in #393. The implementation is sound.
What I Verified
Security:
- The
assertPathContained()function usesresolve()to normalize paths before comparison, handling edge cases beyond literal..segments - All public methods (
findById,findAllByType,save,delete) flow throughgetTypeDir()which validates before any I/O - Absolute path injection (e.g.,
/etc/passwd) is also blocked sincejoin()replaces prior segments with absolute paths, which would then fail the containment check
Code Style (CLAUDE.md):
- ✅ No
anytypes - ✅ Named exports used
- ✅ AGPLv3 license header present
- ✅ Test file next to source file
- ✅ Uses
@std/assertfor assertions
DDD:
- ✅ Repository pattern correctly implemented
- ✅ Path validation is an appropriate infrastructure-level concern
Test Coverage:
- ✅ Tests cover valid vault types and path traversal detection
- ✅ Proper cleanup in finally blocks
Suggestions (non-blocking)
- Consider extracting
assertPathContainedto a shared utility — The function is now duplicated betweenunified_data_repository.tsandyaml_vault_config_repository.ts. A sharedpath_security.tsmodule could reduce duplication. However, the current approach is consistent with existing patterns, so this is fine to leave as-is.
🤖 Generated with Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the path traversal vulnerability reported in #393.
getTypeDir()inYamlVaultConfigRepositorypassed the user-suppliedvaultTypestring directly tojoin()with no validation, allowing a crafted type like"../../.ssh"to escape.swamp/vault/.The fix pattern already existed in
unified_data_repository.tsviaassertPathContained(). This change applies the same approach here.Plan vs Implementation
The plan (issue #393) identified two possible fix approaches:
..,/,\, and null bytesassertPathContained()fromunified_data_repository.tsImplementation chose option 2 — porting
assertPathContained()directly — because:resolve()to handle all path normalisation edge cases (not just literal..segments)Changes
resolveto the@std/pathimport alongside the existingjoinassertPathContained()fromunified_data_repository.ts(identical logic: resolves both paths and checks prefix containment)getTypeDir()to callassertPathContained()after constructing the candidate path, throwing before any I/O if the path escapes the vault base directoryyaml_vault_config_repository_test.tswith three tests:"aws","local_encryption") resolve without error"../../.ssh"throwsError: Path traversal detected"../foo"throwsError: Path traversal detectedgetPath()requires no further change — it already depends on the validated output ofgetTypeDir().Test Plan
deno check— type checking passesdeno lint— no lint errorsdeno fmt— code formatteddeno run test src/infrastructure/persistence/yaml_vault_config_repository_test.ts(3/3)deno run test(1792/1792)deno run compile— binary recompiles cleanlyReported-by: @umag