fix(#141): address security-review-bot REQUEST_CHANGES findings
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 25s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 41s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 48s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 54s

Finding #1 [MAJOR]: replace os.Stat with os.Lstat in checkStaleDocs to
prevent symlink traversal. Symlinks under repoRoot could probe arbitrary
host file existence; Lstat never follows them. Symlinked docs are now
treated as stale.

Finding #2 [MINOR]: resolve --repo-root with filepath.Abs +
filepath.EvalSymlinks before passing to checkStaleDocs, so a symlinked
repo-root cannot bypass the filepath.Rel escape guard.

Finding #3 [NIT]: reject backslashes in ValidateDocPath to prevent
Windows platform edge cases where a path separator may be normalised
differently by the host OS or VCS backend.

Tests added:
- TestCheckStaleDocs_SymlinkOutside: symlink inside repo → outside
- TestCheckStaleDocs_SymlinkInsideRepo: intra-repo symlink also rejected
- TestRunValidateDocmap_SymlinkRepoRoot: symlinked --repo-root resolves OK
- TestValidateDocPath_Backslash: backslash paths rejected
- Backslash cases added to TestValidateDocPath invalid slice

All go test ./... pass, go vet ./... clean.
This commit is contained in:
Rodin
2026-05-14 23:50:12 -07:00
parent 3f8da76b42
commit b22de19aa1
4 changed files with 161 additions and 11 deletions
+25
View File
@@ -395,6 +395,10 @@ func TestValidateDocPath(t *testing.T) {
"docs/../../../etc/passwd",
"../sibling-repo/file.md",
"a/b/../c",
// Backslashes must be rejected (Finding #3 — Windows platform edge cases).
`docs\foo.md`,
`docs\..\secret`,
`\absolute`,
}
for _, p := range invalid {
if err := ValidateDocPath(p); err == nil {
@@ -420,6 +424,27 @@ func TestLoadMatchingDocs_PathTraversalRejected(t *testing.T) {
}
}
// TestValidateDocPath_Backslash verifies that backslash-bearing paths are
// rejected to prevent Windows platform edge cases where a path separator
// could be normalised differently by the host OS or VCS backend.
func TestValidateDocPath_Backslash(t *testing.T) {
backslashPaths := []string{
`docs\foo.md`,
`docs\subdir\file.md`,
`\absolute`,
}
for _, p := range backslashPaths {
if err := ValidateDocPath(p); err == nil {
t.Errorf("expected backslash path %q to be rejected, but it was accepted", p)
}
}
// Sanity: forward-slash path must still be accepted.
if err := ValidateDocPath("docs/foo.md"); err != nil {
t.Errorf("expected forward-slash path to be accepted, got: %v", err)
}
}
// ============================================================
// Helpers
// ============================================================