fix(#141): address review feedback — tighten escape check, improve error messages, add comments
This commit is contained in:
@@ -68,8 +68,15 @@ func runValidateDocmap(args []string) int {
|
|||||||
failed := false
|
failed := false
|
||||||
|
|
||||||
// --- Check 1: Coverage ---
|
// --- Check 1: Coverage ---
|
||||||
|
// Note: an empty docmap (no mappings) means every changed file is
|
||||||
|
// uncovered — there are no patterns to match against. This is intentional:
|
||||||
|
// if you declare a doc-map, every changed file must be accounted for.
|
||||||
|
// On empty stdin the check is vacuously true (no files to cover).
|
||||||
var uncovered []string
|
var uncovered []string
|
||||||
for _, f := range changedFiles {
|
for _, f := range changedFiles {
|
||||||
|
// Normalize Windows-style backslashes to forward slashes so that
|
||||||
|
// changed-file paths from git on Windows match doc-map globs.
|
||||||
|
f = strings.ReplaceAll(f, "\\", "/")
|
||||||
if !review.FileCoveredByDocMap(cfg, f) {
|
if !review.FileCoveredByDocMap(cfg, f) {
|
||||||
uncovered = append(uncovered, f)
|
uncovered = append(uncovered, f)
|
||||||
}
|
}
|
||||||
@@ -93,7 +100,11 @@ func runValidateDocmap(args []string) int {
|
|||||||
}
|
}
|
||||||
resolvedRoot, err := filepath.EvalSymlinks(absRoot)
|
resolvedRoot, err := filepath.EvalSymlinks(absRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
fmt.Fprintf(errWriter, "Error: --repo-root %q does not exist\n", *repoRootFlag)
|
||||||
|
} else {
|
||||||
fmt.Fprintf(errWriter, "Error: failed to resolve --repo-root %q: %v\n", *repoRootFlag, err)
|
fmt.Fprintf(errWriter, "Error: failed to resolve --repo-root %q: %v\n", *repoRootFlag, err)
|
||||||
|
}
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
// checkStaleDocs validates each path before touching the filesystem; see
|
// checkStaleDocs validates each path before touching the filesystem; see
|
||||||
@@ -151,7 +162,7 @@ func checkStaleDocs(cfg *review.DocMapConfig, repoRoot string) []string {
|
|||||||
// filepath.Rel check confirms the path is still under repoRoot.
|
// filepath.Rel check confirms the path is still under repoRoot.
|
||||||
fullPath := filepath.Clean(filepath.Join(repoRoot, filepath.FromSlash(docPath)))
|
fullPath := filepath.Clean(filepath.Join(repoRoot, filepath.FromSlash(docPath)))
|
||||||
rel, err := filepath.Rel(repoRoot, fullPath)
|
rel, err := filepath.Rel(repoRoot, fullPath)
|
||||||
if err != nil || strings.HasPrefix(rel, "..") {
|
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||||
stale = append(stale, docPath)
|
stale = append(stale, docPath)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,12 @@ mappings:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stdinValidateDocmap runs runValidateDocmap with a synthetic stdin.
|
// stdinValidateDocmap runs runValidateDocmap with a synthetic stdin.
|
||||||
|
//
|
||||||
|
// Implementation note: we write stdinContent to a temp file and point
|
||||||
|
// os.Stdin at it. The defer f.Close() fires after stdinValidateDocmap
|
||||||
|
// returns, which is after runValidateDocmap has finished reading stdin
|
||||||
|
// synchronously — so the file is not closed while still in use.
|
||||||
|
// Tests must not call t.Parallel() while sharing the global os.Stdin.
|
||||||
func stdinValidateDocmap(t *testing.T, stdinContent string, args []string) (code int, stdout, stderr string) {
|
func stdinValidateDocmap(t *testing.T, stdinContent string, args []string) (code int, stdout, stderr string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
// Write stdin content to a temp file and redirect os.Stdin.
|
// Write stdin content to a temp file and redirect os.Stdin.
|
||||||
|
|||||||
+5
-5
@@ -310,11 +310,11 @@ func readFileBytes(path string) ([]byte, error) {
|
|||||||
return os.ReadFile(path)
|
return os.ReadFile(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateDocPath rejects doc paths that could cause path traversal via the
|
// ValidateDocPath rejects doc paths that could cause path traversal
|
||||||
// VCS API (absolute paths, any ".." segment, backslashes). Defense-in-depth:
|
// (absolute paths, any ".." segment, backslashes). Defense-in-depth: callers
|
||||||
// the VCS API should already scope paths to the repo, but we validate locally
|
// must also confine the joined path to the repo root via filepath.Rel before
|
||||||
// to avoid any quirk in backend path handling. Backslashes are rejected
|
// any filesystem access. Backslashes are rejected explicitly to prevent
|
||||||
// explicitly to prevent Windows platform edge cases.
|
// Windows platform edge cases.
|
||||||
func ValidateDocPath(p string) error {
|
func ValidateDocPath(p string) error {
|
||||||
if strings.Contains(p, "\\") {
|
if strings.Contains(p, "\\") {
|
||||||
return fmt.Errorf("backslashes not allowed in doc paths")
|
return fmt.Errorf("backslashes not allowed in doc paths")
|
||||||
|
|||||||
Reference in New Issue
Block a user