fix(#141): harden checkStaleDocs against path traversal
PR Ready Gate / clear-labels (pull_request) Successful in 1s
CI / test (pull_request) Successful in 17s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 32s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m12s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m13s

Export review.ValidateDocPath and use it in checkStaleDocs before
calling os.Stat. Add filepath.Clean + filepath.Rel confinement check
as defense-in-depth to ensure doc paths from PR-controlled YAML
cannot probe filesystem locations outside repoRoot.

Also add tests covering: ../../etc/passwd, /etc/passwd, ../outside,
a valid present path, and a valid missing path.

Addresses security finding from security-review-bot on PR #142.
This commit is contained in:
Rodin
2026-05-14 23:43:24 -07:00
parent 2ecbd86e24
commit 3f8da76b42
4 changed files with 82 additions and 8 deletions
+3 -3
View File
@@ -11,7 +11,7 @@ import (
// fakeDocFetcher is a mock DocFetcher for tests.
type fakeDocFetcher struct {
files map[string]string // path -> content
files map[string]string // path -> content
dirs map[string]map[string]string // dir path -> (file path -> content)
}
@@ -384,7 +384,7 @@ func TestValidateDocPath(t *testing.T) {
"a/b/c",
}
for _, p := range valid {
if err := validateDocPath(p); err != nil {
if err := ValidateDocPath(p); err != nil {
t.Errorf("expected valid path %q to pass, got error: %v", p, err)
}
}
@@ -397,7 +397,7 @@ func TestValidateDocPath(t *testing.T) {
"a/b/../c",
}
for _, p := range invalid {
if err := validateDocPath(p); err == nil {
if err := ValidateDocPath(p); err == nil {
t.Errorf("expected path %q to be rejected, but it was accepted", p)
}
}