check-safety/htmlsanitize.go
Alex Dunmow cd88c808b0 initial: standalone check-safety module hoisted from CMS
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>
2026-06-06 13:04:02 +08:00

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
}