mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 04:16:06 +00:00
fix(http/agents): validate agent_key slug on update path
handleUpdate accepted any string in the agent_key allowlist field without running it through isValidSlug. A client could rename an agent to "weird:key", which would break router cache exact-segment invalidation (the cache splits on the last colon for invalidation matching). Add the slug check inline after the allowlist filter and return MsgInvalidSlug on failure. The slug regex already rejects colons, slashes, whitespace, and other characters that would confuse path rendering or cache key parsing — add a dedicated predicate test covering the full trap surface.
This commit is contained in:
1 parent
c7a93703ba
commit
93099cc682
2 files changed
+53
No files matched your search
@@ -368,6 +368,18 @@ func (h *AgentsHandler) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
allowed := filterAllowedKeys(updates, agentAllowedFields)
|
||||
allowed["restrict_to_workspace"] = true
|
||||
|
||||
// If agent_key is being changed, enforce the slug format. The router
|
||||
// cache uses `tenantID:agentKey` as its canonical key and splits on the
|
||||
// last colon for exact-segment invalidation — a colon inside agent_key
|
||||
// would silently break invalidation. Slug regex already rejects colons
|
||||
// and any other shell/path-unfriendly characters.
|
||||
if newKey, ok := allowed["agent_key"].(string); ok && newKey != "" {
|
||||
if !isValidSlug(newKey) {
|
||||
writeError(w, http.StatusBadRequest, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidSlug, "agent_key"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate v3 flag values in other_config (must be boolean).
|
||||
if oc, ok := allowed["other_config"]; ok && oc != nil {
|
||||
switch v := oc.(type) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package http
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestIsValidSlug covers the slug predicate used by agent_key, skill slug,
|
||||
// provider name, and MCP server name validation. The slug format is the
|
||||
// router cache's canonical anchor — the cache splits on the last colon for
|
||||
// exact-segment invalidation, so the predicate MUST reject any character
|
||||
// that would collide with that split (notably `:`).
|
||||
func TestIsValidSlug(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{"simple lowercase", "agent", true},
|
||||
{"with hyphen", "goctech-leader", true},
|
||||
{"with digits", "agent-42", true},
|
||||
{"single char", "a", true},
|
||||
{"starts with digit", "1-agent", true},
|
||||
|
||||
{"empty", "", false},
|
||||
{"uppercase rejected", "Agent", false},
|
||||
{"starts with hyphen", "-agent", false},
|
||||
{"ends with hyphen", "agent-", false},
|
||||
{"colon rejected", "weird:key", false},
|
||||
{"slash rejected", "weird/key", false},
|
||||
{"whitespace rejected", "weird key", false},
|
||||
{"dot rejected", "weird.key", false},
|
||||
{"tab rejected", "weird\tkey", false},
|
||||
{"underscore rejected", "weird_key", false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := isValidSlug(tc.input); got != tc.want {
|
||||
t.Errorf("isValidSlug(%q) = %v, want %v", tc.input, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user