Skip to content

Comments

fix: path traversal in yaml_vault_config_repository via assertPathContained (#393)#406

Merged
stack72 merged 1 commit intomainfrom
fix/path-traversal-yaml-vault-config-repository
Feb 19, 2026
Merged

fix: path traversal in yaml_vault_config_repository via assertPathContained (#393)#406
stack72 merged 1 commit intomainfrom
fix/path-traversal-yaml-vault-config-repository

Conversation

@stack72
Copy link
Contributor

@stack72 stack72 commented Feb 19, 2026

Summary

Fixes the path traversal vulnerability reported in #393.

getTypeDir() 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.

Plan vs Implementation

The plan (issue #393) identified two possible fix approaches:

  1. Add component validation rejecting .., /, \, and null bytes
  2. Use assertPathContained() from unified_data_repository.ts

Implementation chose option 2 — porting assertPathContained() directly — because:

  • It is already proven and in use for the same class of problem
  • It uses resolve() to handle all path normalisation edge cases (not just literal .. segments)
  • It keeps the fix consistent with the existing codebase pattern

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 — it already depends on the validated output of getTypeDir().

Test Plan

  • deno check — type checking passes
  • deno lint — no lint errors
  • deno fmt — code formatted
  • New tests pass: deno run test src/infrastructure/persistence/yaml_vault_config_repository_test.ts (3/3)
  • Full test suite passes: deno run test (1792/1792)
  • deno run compile — binary recompiles cleanly

Reported-by: @umag

…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>
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

This PR correctly fixes the path traversal vulnerability reported in #393. The implementation is sound.

What I Verified

Security:

  • The assertPathContained() function uses resolve() to normalize paths before comparison, handling edge cases beyond literal .. segments
  • All public methods (findById, findAllByType, save, delete) flow through getTypeDir() which validates before any I/O
  • Absolute path injection (e.g., /etc/passwd) is also blocked since join() replaces prior segments with absolute paths, which would then fail the containment check

Code Style (CLAUDE.md):

  • ✅ No any types
  • ✅ Named exports used
  • ✅ AGPLv3 license header present
  • ✅ Test file next to source file
  • ✅ Uses @std/assert for 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)

  1. Consider extracting assertPathContained to a shared utility — The function is now duplicated between unified_data_repository.ts and yaml_vault_config_repository.ts. A shared path_security.ts module could reduce duplication. However, the current approach is consistent with existing patterns, so this is fine to leave as-is.

🤖 Generated with Claude Code

@stack72 stack72 merged commit 9e53938 into main Feb 19, 2026
4 checks passed
@stack72 stack72 deleted the fix/path-traversal-yaml-vault-config-repository branch February 19, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant