Skip to content

Bug symlink path travesal#59

Merged
wesm merged 2 commits intowesm:mainfrom
hughdbrown:bug-symlink-path-travesal
Feb 5, 2026
Merged

Bug symlink path travesal#59
wesm merged 2 commits intowesm:mainfrom
hughdbrown:bug-symlink-path-travesal

Conversation

@hughdbrown
Copy link
Contributor

Fix for a security attack

The tokenPath() function in internal/oauth/oauth.go:301-317 used strings.HasPrefix to verify paths stayed within tokensDir, but didn't detect symlinks. An attacker who could create a symlink inside tokensDir (e.g., tokensDir/evil.json -> /etc/passwd) could cause token data to be written outside the tokens directory.

hughdbrown and others added 2 commits February 4, 2026 14:06
  The Bug

  The tokenPath() function in internal/oauth/oauth.go:301-317 used strings.HasPrefix to verify paths stayed within tokensDir, but
  didn't detect symlinks. An attacker who could create a symlink inside tokensDir (e.g., tokensDir/evil.json -> /etc/passwd) could
  cause token data to be written outside the tokens directory.

  The Fix

  Added symlink detection using os.Lstat() and filepath.EvalSymlinks():

  // Check if path is a symlink that could escape tokensDir
  if info, err := os.Lstat(cleanPath); err == nil && info.Mode()&os.ModeSymlink != 0 {
      // Path exists and is a symlink - resolve it and verify it stays within tokensDir
      resolved, err := filepath.EvalSymlinks(cleanPath)
      if err != nil || !isPathWithinDir(resolved, cleanTokensDir) {
          // Symlink resolution failed or escapes tokensDir - use hash-based fallback
          return filepath.Join(m.tokensDir, fmt.Sprintf("%x.json", sha256.Sum256([]byte(email))))
      }
  }

  Also added a helper function isPathWithinDir() that properly resolves symlinks in the base directory before comparing paths.

  Changes Made

  - internal/oauth/oauth.go: Added symlink detection in tokenPath() and new isPathWithinDir() helper (13 lines added)
  - internal/oauth/oauth_test.go: Added TestTokenPath_SymlinkEscape test case (47 lines added)
- Fix incomplete path prefix check: Replace strings.HasPrefix with
  hasPathPrefix() that properly handles directory separators, preventing
  attacks where tokensDir-evil would pass the check for tokensDir

- Add hasPathPrefix() helper for safe directory prefix checking without
  symlink resolution, keeping isPathWithinDir() for resolved symlink checks

- Document TOCTOU limitation in symlink check with explanation of the
  low practical risk due to required attacker capabilities

- Improve test to verify exact hash-based fallback path format

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@wesm wesm merged commit 2c4bc5c into wesm:main Feb 5, 2026
1 check passed
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.

2 participants