feat: rate limiting de tools por room en registry

Añade rate limiting de tool calls por room usando sliding window:

- tools/ratelimit.go: RateLimiter con sliding window per key (room),
  Allow() para verificar/registrar llamadas, Cleanup() para limpiar
  entries expiradas
- tools/registry.go: SetRateLimiter() y ExecuteForRoom() que verifica
  el rate limit antes de ejecutar, logueando tool_rate_limited si excede
- internal/config/schema.go: ToolRateLimitCfg en SecurityCfg con
  enabled, max_calls_per_min y cleanup_interval_s
- agents/runtime.go: inicializa rate limiter desde config y arranca
  goroutine de cleanup periodico
- agents/commands.go: usa ExecuteForRoom en !tool command

Config YAML:
  security:
    tool_rate_limit:
      enabled: true
      max_calls_per_min: 10

Parte de issue 0019c (prompt injection hardening — rate limiting).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 19:45:41 +00:00
parent 01a734cd9b
commit 69efb6ab95
5 changed files with 128 additions and 8 deletions
+24 -1
View File
@@ -230,6 +230,29 @@ func New(cfg *config.AgentConfig, rules []decision.Rule, logger *slog.Logger) (*
// Tool registry — register tools enabled in config
toolReg := buildToolRegistry(cfg, sshExec, matrixClient, memStore, kStore, roomCtx, logger)
// Rate limiting for tools
if cfg.Security.ToolRateLimit.Enabled {
maxCalls := cfg.Security.ToolRateLimit.MaxCallsPerMin
if maxCalls <= 0 {
maxCalls = 10
}
rl := tools.NewRateLimiter(maxCalls, time.Minute)
toolReg.SetRateLimiter(rl)
cleanupInterval := cfg.Security.ToolRateLimit.CleanupIntervalS
if cleanupInterval <= 0 {
cleanupInterval = 60
}
go func() {
ticker := time.NewTicker(time.Duration(cleanupInterval) * time.Second)
defer ticker.Stop()
for range ticker.C {
rl.Cleanup()
}
}()
logger.Info("tool rate limiting enabled", "max_calls_per_min", maxCalls)
}
a := &Agent{
cfg: cfg,
acl: agentACL,
@@ -753,7 +776,7 @@ func (a *Agent) runLLM(ctx context.Context, msgCtx decision.MessageContext) (str
a.logger.Warn("failed to send tool call notice", "tool", tc.Name, "err", err)
}
result := a.toolReg.Execute(ctx, tc.Name, tc.Arguments)
result := a.toolReg.ExecuteForRoom(ctx, tc.Name, tc.Arguments, msgCtx.RoomID)
output := result.Output
if result.Err != nil {