fix(wing,server): client names are matched as whole Unicode words; unsafe lists are a startup error (review of #214)
- 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:
@@ -234,3 +234,59 @@ func TestRouter_ClientMarkerChunkingDoesNotSplitAMultiByteRune(t *testing.T) {
|
||||
clean := strings.Repeat("\u00e9", 300)
|
||||
assert.Equal(t, wing.Default, r.Resolve(context.Background(), wing.Note{Text: clean}).Source, "no panic or false match on a text of 2-byte runes")
|
||||
}
|
||||
|
||||
// From the review of #214: the shared secret is a bare list of client names.
|
||||
func TestWrapNames(t *testing.T) {
|
||||
park := func(t *testing.T, names, text string) bool {
|
||||
t.Helper()
|
||||
pat, err := wing.WrapNames(names)
|
||||
assert.NoError(t, err)
|
||||
r, err := wing.NewRouter(routerBrain(t), "web-clip", pat)
|
||||
assert.NoError(t, err)
|
||||
return r.Resolve(context.Background(), wing.Note{Text: text}).Source == wing.Exception
|
||||
}
|
||||
t.Run("a short name does not match inside ordinary words", func(t *testing.T) {
|
||||
assert.False(t, park(t, "dif|trucker", "a different approach to modifying prompts"))
|
||||
assert.True(t, park(t, "dif|trucker", "kickoff for the DIF project"))
|
||||
})
|
||||
t.Run("names with a non-ASCII letter at the edge still match (Go's \\b would never)", func(t *testing.T) {
|
||||
assert.True(t, park(t, "Åke|Société|Ørsted", "Åke called"))
|
||||
assert.True(t, park(t, "Åke|Société|Ørsted", "la Société wrote"))
|
||||
assert.True(t, park(t, "Åke|Société|Ørsted", "Ørsted wind"))
|
||||
})
|
||||
t.Run("identifiers with underscores and punctuation count as boundaries", func(t *testing.T) {
|
||||
assert.True(t, park(t, "trucker", "repo trucker_ecommerce was cloned"))
|
||||
assert.True(t, park(t, "trucker", "(Trucker)"))
|
||||
})
|
||||
t.Run("a name inside a longer word or with digits glued on is not a boundary", func(t *testing.T) {
|
||||
assert.False(t, park(t, "acme", "acmecorp"))
|
||||
assert.False(t, park(t, "acme", "acme2"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestWrapNames_RefusesWhatWouldBreakOrBlockEverything(t *testing.T) {
|
||||
for _, bad := range []string{
|
||||
"x)|.*(", // breaks out of the group
|
||||
"acme)|(b", // changes the structure
|
||||
"a||b", // empty alternative matches everywhere
|
||||
"acme|", // trailing empty alternative
|
||||
"|acme", // leading empty alternative
|
||||
"", // nothing
|
||||
"c++ corp", // not a valid regexp
|
||||
"([unclosed", // not a valid regexp
|
||||
} {
|
||||
t.Run(bad, func(t *testing.T) {
|
||||
_, err := wing.WrapNames(bad)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRouter_RefusesAPatternThatMatchesTheEmptyString(t *testing.T) {
|
||||
for _, p := range []string{`.*`, `x*`, `^`, `\b`, `(a|)`} {
|
||||
t.Run(p, func(t *testing.T) {
|
||||
_, err := wing.NewRouter(routerBrain(t), "web-clip", p)
|
||||
assert.Error(t, err, "it would park every note")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user