From f9b07d0a9c5f7d3c7c9b7a58f038362bc3272e9e Mon Sep 17 00:00:00 2001 From: Syed Osama Ali Shah <86572800+Osamaali313@users.noreply.github.com> Date: Wed, 8 Jul 2026 23:54:33 +0300 Subject: [PATCH] fix(tools): avoid panic in StripToolPrefix on overlapping prefix+suffix (#1398) When a tool_call_prefix template has both a prefix and a suffix (e.g. "pre_{tool_name}_suf"), StripToolPrefix stripped the prefix and then sliced off the suffix length without checking the two ends don't overlap. A model-emitted name that satisfies both HasPrefix and HasSuffix but is shorter than prefix+suffix (e.g. "pre_suf") produced a negative slice length -> "slice bounds out of range [:-1]" panic. This runs in the agent turn loop on LLM-controlled names (resolveToolCallName -> StripToolPrefix), so a crafted/degenerate name crashes the turn. Guard with len(prefix)+len(suffix) <= len(name); overlapping names now fall through to returning the name unchanged, consistent with the other no-match fallbacks. Adds a regression row to the prefix test table. --- internal/tools/policy.go | 6 +++++- internal/tools/policy_prefix_test.go | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/tools/policy.go b/internal/tools/policy.go index 329525c9..a5629476 100644 --- a/internal/tools/policy.go +++ b/internal/tools/policy.go @@ -527,7 +527,11 @@ func StripToolPrefix(tmpl, name string) string { if strings.Contains(tmpl, placeholder) { parts := strings.SplitN(tmpl, placeholder, 2) prefix, suffix := parts[0], parts[1] - if strings.HasPrefix(name, prefix) && strings.HasSuffix(name, suffix) { + // Require the name to be at least as long as prefix+suffix so an + // overlapping match (e.g. tmpl "pre_{tool_name}_suf", name "pre_suf") + // doesn't slice with a negative length and panic. + if strings.HasPrefix(name, prefix) && strings.HasSuffix(name, suffix) && + len(prefix)+len(suffix) <= len(name) { result := name[len(prefix):] if suffix != "" { result = result[:len(result)-len(suffix)] diff --git a/internal/tools/policy_prefix_test.go b/internal/tools/policy_prefix_test.go index a07f7972..1698effc 100644 --- a/internal/tools/policy_prefix_test.go +++ b/internal/tools/policy_prefix_test.go @@ -16,6 +16,7 @@ func TestStripToolPrefix(t *testing.T) { {"template no match", "proxy_{tool_name}", "other_exec", "other_exec"}, {"template empty result", "proxy_{tool_name}", "proxy_", "proxy_"}, {"template exact placeholder", "{tool_name}", "exec", "exec"}, + {"template prefix+suffix overlap (regression)", "pre_{tool_name}_suf", "pre_suf", "pre_suf"}, // Literal prefix {"literal prefix with separator", "proxy_", "proxy_exec", "exec"},