From ff76341413721fb9fc1ca01a31b8e80df253db22 Mon Sep 17 00:00:00 2001 From: Sanket Saurav Date: Sat, 18 Apr 2026 23:09:51 -0700 Subject: [PATCH] fix(checkers): load custom YAML checkers from disk, not embedded FS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `LoadCustomYamlCheckers(dir)` walks `os.DirFS(dir)` to find YAML files on local disk, but the closure it used read file content via `builtinCheckers.ReadFile(path)` — i.e. always the embedded FS. For every custom YAML file, `ReadFile` returned `fs.ErrNotExist`, the error was silently swallowed by the top-level error check in the walk callback, and the checker was never loaded. Net result: `globstar check --checkers=local` in a repo with a `.globstar/*.yml` found zero issues regardless of the YAML's content, while the same YAML loaded via `--checkers=builtin` worked fine — confirming the loader (not the YAML) was the problem. Fix: thread a per-backend `readFile func(string) ([]byte, error)` through `findYamlCheckers`. `LoadBuiltinYamlCheckers` passes `builtinCheckers.ReadFile`; `LoadCustomYamlCheckers` passes a closure that reads from disk via `os.ReadFile(filepath.Join(dir, p))`. No public API breakage. Add a regression test that writes a YAML checker to a temp dir and asserts `LoadCustomYamlCheckers` returns it. --- checkers/checker.go | 10 ++++---- checkers/checker_test.go | 52 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 checkers/checker_test.go diff --git a/checkers/checker.go b/checkers/checker.go index f05d28f..5f6de51 100644 --- a/checkers/checker.go +++ b/checkers/checker.go @@ -13,7 +13,7 @@ import ( //go:embed **/*.y*ml var builtinCheckers embed.FS -func findYamlCheckers(checkersMap map[analysis.Language][]analysis.Analyzer) func(path string, d fs.DirEntry, err error) error { +func findYamlCheckers(checkersMap map[analysis.Language][]analysis.Analyzer, readFile func(string) ([]byte, error)) func(path string, d fs.DirEntry, err error) error { return func(path string, d fs.DirEntry, err error) error { if err != nil { return nil @@ -29,7 +29,7 @@ func findYamlCheckers(checkersMap map[analysis.Language][]analysis.Analyzer) fun return nil } - fileContent, err := builtinCheckers.ReadFile(path) + fileContent, err := readFile(path) if err != nil { return nil } @@ -47,13 +47,15 @@ func findYamlCheckers(checkersMap map[analysis.Language][]analysis.Analyzer) fun func LoadBuiltinYamlCheckers() (map[analysis.Language][]analysis.Analyzer, error) { checkersMap := make(map[analysis.Language][]analysis.Analyzer) - err := fs.WalkDir(builtinCheckers, ".", findYamlCheckers(checkersMap)) + err := fs.WalkDir(builtinCheckers, ".", findYamlCheckers(checkersMap, builtinCheckers.ReadFile)) return checkersMap, err } func LoadCustomYamlCheckers(dir string) (map[analysis.Language][]analysis.Analyzer, error) { checkersMap := make(map[analysis.Language][]analysis.Analyzer) - err := fs.WalkDir(os.DirFS(dir), ".", findYamlCheckers(checkersMap)) + err := fs.WalkDir(os.DirFS(dir), ".", findYamlCheckers(checkersMap, func(p string) ([]byte, error) { + return os.ReadFile(filepath.Join(dir, p)) + })) return checkersMap, err } diff --git a/checkers/checker_test.go b/checkers/checker_test.go new file mode 100644 index 0000000..a15331a --- /dev/null +++ b/checkers/checker_test.go @@ -0,0 +1,52 @@ +package checkers + +import ( + "os" + "path/filepath" + "testing" + + "globstar.dev/analysis" +) + +// TestLoadCustomYamlCheckers_ReadsFromDisk is a regression test ensuring that +// custom YAML checkers are loaded from the local filesystem and not silently +// dropped because the loader was reading from the embedded FS. +func TestLoadCustomYamlCheckers_ReadsFromDisk(t *testing.T) { + dir := t.TempDir() + const yml = `language: go +name: go_custom_test +message: "custom checker fired" +category: security +severity: critical +pattern: > + [ + (import_spec + path: (interpreted_string_literal) @import + (#eq? @import "\"crypto/md5\"")) + ] @go_custom_test +` + if err := os.WriteFile(filepath.Join(dir, "custom.yml"), []byte(yml), 0o644); err != nil { + t.Fatalf("write yaml: %v", err) + } + + checkersMap, err := LoadCustomYamlCheckers(dir) + if err != nil { + t.Fatalf("LoadCustomYamlCheckers: %v", err) + } + + goCheckers, ok := checkersMap[analysis.LangGo] + if !ok || len(goCheckers) == 0 { + t.Fatalf("expected at least one Go checker loaded from disk, got map=%v", checkersMap) + } + + found := false + for _, c := range goCheckers { + if c.Name == "go_custom_test" { + found = true + break + } + } + if !found { + t.Fatalf("expected 'go_custom_test' to be loaded; got %+v", goCheckers) + } +}