Static safety/lint runner for the BlockNinja codebase. ~25 invariant
checks across Go and frontend sources. Was at git.dev.alexdunmow.com:block/ninja
in backend/cmd/check-safety/ until the 2026-06-06 consolidation moved
the BlockNinja repos under a shared ~/src/blockninja/ parent.
This repo is the standalone extraction:
- Own go.mod (git.dev.alexdunmow.com/block/check-safety, go 1.26.4)
- Vendored internal/{helpers,theme} from CMS (Go's internal/ rule
blocks cross-module imports; vendoring is the workaround)
- CLI contract unchanged: `check-safety <target-dir> [--flags]`
- CMS Makefile shells into ../check-safety for safety-check /
install-safety-checker targets
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
114 lines
2.6 KiB
Go
114 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"git.dev.alexdunmow.com/block/check-safety/internal/helpers"
|
|
)
|
|
|
|
type htmlSanitizeViolation struct {
|
|
file string
|
|
line int
|
|
rule string
|
|
snippet string
|
|
}
|
|
|
|
var htmlSanitizePatterns = []struct {
|
|
pattern *regexp.Regexp
|
|
rule string
|
|
}{
|
|
{
|
|
pattern: regexp.MustCompile(`regexp\.\s*(?:MustCompile|Compile)\s*\(.+<(?:script|style)\b`),
|
|
rule: "regex-html-strip",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`regexp\.\s*(?:MustCompile|Compile)\s*\(.+<\[\^`),
|
|
rule: "regex-html-strip",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`(?:func\s+(?:strip|Strip)HTML|func\s+stripHTMLTags|func\s+sanitizeHTML|func\s+cleanHTML)\s*\(`),
|
|
rule: "hand-rolled-strip-func",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`if\s+r\s*==\s*'<'\s*\{`),
|
|
rule: "char-by-char-tag-strip",
|
|
},
|
|
{
|
|
pattern: regexp.MustCompile(`strings\.NewReplacer\(.*"<[^"]*>".*\)`),
|
|
rule: "replacer-html-strip",
|
|
},
|
|
}
|
|
|
|
var htmlSanitizeAllowedFiles = map[string]bool{
|
|
"cmd/check-safety/htmlsanitize.go": true,
|
|
"cmd/check-safety/check_htmlsanitize.go": true, // holds the fix-instruction table (example patterns)
|
|
"cmd/check-safety/main.go": true,
|
|
"internal/helpers/slug.go": true,
|
|
}
|
|
|
|
func isHTMLSanitizeAllowed(relPath string) bool {
|
|
if strings.HasSuffix(relPath, "_test.go") {
|
|
return true
|
|
}
|
|
for allowed := range htmlSanitizeAllowedFiles {
|
|
if strings.HasSuffix(relPath, allowed) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func checkHTMLSanitize(root string) []htmlSanitizeViolation {
|
|
var violations []htmlSanitizeViolation
|
|
|
|
_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") {
|
|
return nil
|
|
}
|
|
if strings.Contains(path, "/vendor/") {
|
|
return nil
|
|
}
|
|
|
|
relPath, _ := filepath.Rel(root, path)
|
|
if isHTMLSanitizeAllowed(relPath) {
|
|
return nil
|
|
}
|
|
|
|
f, ferr := os.Open(path)
|
|
if ferr != nil {
|
|
return nil
|
|
}
|
|
defer helpers.LogDeferredError(nil, "close html-sanitize scan file", f.Close, "path", path)
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
lineNum := 0
|
|
for scanner.Scan() {
|
|
lineNum++
|
|
line := scanner.Text()
|
|
trimmed := strings.TrimSpace(line)
|
|
|
|
if strings.HasPrefix(trimmed, "//") {
|
|
continue
|
|
}
|
|
|
|
for _, check := range htmlSanitizePatterns {
|
|
if check.pattern.MatchString(line) {
|
|
violations = append(violations, htmlSanitizeViolation{
|
|
file: relPath,
|
|
line: lineNum,
|
|
rule: check.rule,
|
|
snippet: strings.TrimSpace(line),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return violations
|
|
}
|