Skip to content

feat(core): enable runaway tool-call rate limiting and daily cost cap by default#6528

Merged
bug-ops merged 1 commit into
mainfrom
feat/issue-6469/default-runaway-guardrails
Jul 19, 2026
Merged

feat(core): enable runaway tool-call rate limiting and daily cost cap by default#6528
bug-ops merged 1 commit into
mainfrom
feat/issue-6469/default-runaway-guardrails

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • A fresh Zeph install shipped with the tool-call rate limiter disabled and no daily cost cap, leaving no built-in defense against a stuck tool-call loop or unbounded LLM spend. Both enforcement mechanisms (ToolRateLimiter, CostTracker::check_budget()) already existed and were fully wired into the tool-dispatch and LLM-dispatch hot paths — this is a default-value + wizard-prompt + metrics change, not new enforcement logic.
  • Fixes a serde split-brain bug found during architecture review: RateLimitConfig.enabled used a bare #[serde(default)] on the field (resolving to false) while impl Default set true, so an absent [security.rate_limit] TOML section was protected but a present-but-partial section (e.g. only one sub-limit configured) silently stayed disabled. Both paths now agree via a named default fn, verified with regression tests at both the leaf RateLimitConfig and nested SecurityConfig level.
  • Rate limiter now enabled by default (unchanged per-category thresholds: shell 30/min, web 20/min, memory 60/min, mcp 40/min, other 60/min, 30s cooldown).
  • Daily cost cap now defaults to $25.00/day (2500 cents), not unlimited (0).
  • Explicit user-set values (enabled = false, max_daily_cents = 0, or any other explicit non-zero value) are preserved unchanged — this only affects sparse/absent config sections.
  • Adds a zeph_cost_budget_exhausted_total Prometheus counter mirroring the existing zeph_security_rate_limit_trips metric.
  • Adds remediation hints to the budget-exhausted and rate-limit-exceeded user-facing messages (how to raise/disable via config.toml).
  • Updates the --init wizard: rate-limit prompt now defaults to enabled; new prompt for the daily cost cap in dollars.
  • Adds an optional advisory-only migration step (migrate_rate_limit_advisory) that documents the new defaults for existing configs via commented-out TOML — purely documentary, cannot activate a live value.
  • Syncs config/default.toml and book/src/ docs with the new defaults.

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 14791/14791 passed
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • New regression tests: absent/partial [security.rate_limit] section resolves enabled = true (the split-brain guard), explicit enabled = false preserved, explicit max_daily_cents = 0/non-zero preserved, CostConfig absent-section default, wizard dollars-to-cents parsing (extracted to a pure fn with unit tests)
  • gitleaks protect --staged
  • Live-testing playbook (.local/testing/playbooks/security.md, Scenarios 1-6) — not yet run; tracked in coverage-status.md as Untested, to be exercised in a future CI/live-testing cycle

Closes #6469

… by default

A fresh install previously shipped with the tool-call rate limiter disabled
and no daily cost cap, leaving no built-in defense against a stuck loop or
unbounded LLM spend. Both mechanisms already existed and were fully wired
into the tool-dispatch and LLM-dispatch hot paths; only the defaults and
wizard prompts needed to change.

- Fix a serde split-brain in RateLimitConfig: enabled used a bare
  #[serde(default)] on the field (resolving to false) while impl Default
  set true, so an absent [security.rate_limit] section was protected but a
  present-but-partial section silently stayed disabled. Both paths now
  agree via a named default fn.
- Flip RateLimitConfig::default().enabled to true and
  CostConfig::default_max_daily_cents() to 2500 ($25.00/day). Explicit
  user-set values (enabled = false, max_daily_cents = 0) are preserved.
- Add zeph_cost_budget_exhausted_total metric mirroring the existing
  rate-limit-trips counter.
- Add remediation hints to the budget-exhausted and rate-limit-exceeded
  user-facing messages.
- Update the --init wizard defaults and add a daily-cost-cap prompt.
- Add an advisory (non-activating) migration step documenting the new
  [security.rate_limit] / [cost] defaults for existing configs.
- Sync config/default.toml and book docs with the new defaults.

Closes #6469
@github-actions github-actions Bot added enhancement New feature or request documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate config Configuration file changes size/L Large PR (201-500 lines) and removed enhancement New feature or request labels Jul 19, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 19, 2026 23:50
@bug-ops
bug-ops merged commit b17730f into main Jul 19, 2026
47 checks passed
@bug-ops
bug-ops deleted the feat/issue-6469/default-runaway-guardrails branch July 19, 2026 23:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Configuration file changes core zeph-core crate documentation Improvements or additions to documentation rust Rust code changes size/L Large PR (201-500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: no default-on runaway tool-call / cost protection — both safety nets ship disabled

1 participant