From d6bab7a9cf6ce3d084d8f40a91a51c1a8fc084e7 Mon Sep 17 00:00:00 2001 From: Rodin Date: Fri, 15 May 2026 16:11:15 -0700 Subject: [PATCH] fix(#150): close residual TOCTOU with LimitedReader at docmap open --- cmd/review-bot/validatedocmap.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/cmd/review-bot/validatedocmap.go b/cmd/review-bot/validatedocmap.go index 5db91ff..2c1f364 100644 --- a/cmd/review-bot/validatedocmap.go +++ b/cmd/review-bot/validatedocmap.go @@ -152,9 +152,26 @@ func runValidateDocmap(args []string) int { return 2 } - // Parse docmap YAML using the resolved path — eliminates any TOCTOU race - // between validation and use. - cfg, err := review.ParseDocMapConfig(resolvedDocmap) + // Open and read the docmap with a LimitedReader — closes the residual TOCTOU + // window between the Lstat size check in validateDocmapPath and the file open + // here. The limit is maxDocmapBytes+1 so we can detect a file that grew past + // the cap after the stat without reading unbounded bytes. + f, err := os.Open(resolvedDocmap) + if err != nil { + fmt.Fprintf(errWriter, "Error: failed to open docmap %q: %v\n", *docmapFlag, err) + return 2 + } + defer f.Close() // nolint:errcheck + docmapData, err := io.ReadAll(io.LimitReader(f, maxDocmapBytes+1)) + if err != nil { + fmt.Fprintf(errWriter, "Error: failed to read docmap %q: %v\n", *docmapFlag, err) + return 2 + } + if int64(len(docmapData)) > maxDocmapBytes { + fmt.Fprintf(errWriter, "Error: --docmap %q exceeded %d-byte limit after open\n", *docmapFlag, maxDocmapBytes) + return 2 + } + cfg, err := review.ParseDocMapConfigContent(string(docmapData), *docmapFlag) if err != nil { fmt.Fprintf(errWriter, "Error: failed to parse docmap %q: %v\n", *docmapFlag, err) return 2