fix(wing,server): client names are matched as whole Unicode words; unsafe lists are a startup error (review of #214)
CI / Lint / Test / Vet (pull_request) Successful in 1m3s
CI / Mirror to GitHub (pull_request) Skipped

- WrapNames bounds the bare name list with Unicode-aware edges instead of \b
  (Go's \b is ASCII-only, so a name with a non-ASCII edge letter such as Åke,
  Société, Ørsted would never have matched), and validates the list on its own:
  it must compile, must not match the empty string, and cannot break out of its
  group ('x)|.*(' compiles alone only if balanced)
- NewRouter refuses any pattern that matches text naming no client
- docs state the whole-word semantics and what is not matched
Mutation-checked. Open decision for Mathias: whole-word versus substring recall
for a confidentiality filter (plurals, CamelCase and digits glued on do not match).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 21:44:23 +02:00
co-authored by Claude Sonnet 5.5
parent 0582dfdf6b
commit 80f3fd3c5e
5 changed files with 120 additions and 43 deletions
+13 -8
View File
@@ -152,17 +152,18 @@ func envInt(key string, fallback int) int {
// ingestClientPattern picks the client-content pattern for raw/ routing.
// INGEST_CLIENT_BLOCK is a complete regexp and is used as is. Otherwise the
// shared CLAUDE_INGEST_CLIENT_BLOCK is used, which the deployment injects as a
// bare alternation of client names that claudewatcher wraps in \b(...)\b: it is
// wrapped the same way here, or a short name would match inside ordinary words.
func ingestClientPattern(getenv func(string) string) string {
// shared CLAUDE_INGEST_CLIENT_BLOCK is used: the deployment injects it as a bare
// list of client names (regexp alternation), which wing.WrapNames bounds so a
// short name does not match inside ordinary words, and which it refuses if it
// would break out of its group or match the empty string.
func ingestClientPattern(getenv func(string) string) (string, error) {
if p := getenv("INGEST_CLIENT_BLOCK"); p != "" {
return p
return p, nil
}
if names := getenv("CLAUDE_INGEST_CLIENT_BLOCK"); names != "" {
return `\b(` + names + `)\b`
return wing.WrapNames(names)
}
return ""
return "", nil
}
// envFlag reads a boolean switch. Unset is off; a value strconv.ParseBool does
@@ -441,7 +442,11 @@ func main() {
os.Exit(1)
}
if routing {
pattern := ingestClientPattern(os.Getenv)
pattern, perr := ingestClientPattern(os.Getenv)
if perr != nil {
logger.Error("client-content pattern", "err", perr)
os.Exit(1)
}
router, rerr := wing.NewRouter(brainDir, envOr("INGEST_DEFAULT_WING", "web-clip"), pattern)
if rerr != nil {
logger.Error("build wing router", "err", rerr)