feat: add sliding-window rate limiting to DefaultPolicyEngine#64
Merged
feat: add sliding-window rate limiting to DefaultPolicyEngine#64
Conversation
Add RateLimiter class and integrate per-(principal_id, capability_id) rate limiting into DefaultPolicyEngine.evaluate(). Defaults by safety class: 60 READ / 10 WRITE / 2 DESTRUCTIVE per 60s window. Service-role principals get 10x limits. Clock is injectable for deterministic testing. - policy.py: RateLimiter + DefaultPolicyEngine.__init__ with rate_limits/clock - test_policy.py: 8 new tests covering all acceptance criteria (mock clock) - docs/security.md: replace 'no rate limiting' with feature documentation - CHANGELOG.md: add entry under [Unreleased] Closes #39
There was a problem hiding this comment.
Pull request overview
Adds sliding-window rate limiting to DefaultPolicyEngine to mitigate runaway/compromised principals invoking capabilities at unbounded rates, aligning policy enforcement with the security threat model documented in docs/security.md.
Changes:
- Introduces
RateLimiter(injectable clock) and integrates per-(principal_id, capability_id)enforcement intoDefaultPolicyEngine.evaluate(). - Adds rate-limiting tests using a controllable clock (no
time.sleep()). - Updates security documentation and changelog to reflect the new control.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| src/agent_kernel/policy.py | Adds RateLimiter, default rate limit config, and enforcement in DefaultPolicyEngine. |
| tests/test_policy.py | Adds unit tests for rate limiter behavior and engine enforcement scenarios. |
| docs/security.md | Replaces “no rate limiting” disclaimer with rate limiting documentation and limitations. |
| CHANGELOG.md | Notes the new policy rate-limiting feature under [Unreleased]. |
You can also share your feedback on Copilot code review. Take the survey.
added 7 commits
March 14, 2026 22:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Adds per-
(principal_id, capability_id)sliding-window rate limiting toDefaultPolicyEngine, closing the #1 security gap identified indocs/security.md.Files touched
src/agent_kernel/policy.py— AddedRateLimiterclass (sliding-window, injectable clock) and_RateEntrydataclass. Added__init__toDefaultPolicyEnginewith optionalrate_limitsandclockparams. Rate check integrated at end ofevaluate(), after all role/sensitivity/row-cap checks pass, before returningPolicyDecision.tests/test_policy.py— 8 new tests covering all acceptance criteria with mock clock (notime.sleep()).docs/security.md— Replaced "no rate limiting" disclaimer with feature documentation.CHANGELOG.md— Added entry under[Unreleased].Why
Issue #39: A compromised or runaway agent can invoke capabilities at unlimited rate, exhausting API quotas, running up costs, or performing DoS against backends. Rate limiting was explicitly called out as the #1 security gap.
Design decisions
PolicyDecisionreturn — exactly as specified in the issue.f"{principal_id}:{capability_id}"— scoped per principal-capability pair, not global."service"role.DefaultPolicyEngine()with no args works identically to before (all params optional with defaults).How verified
ruff formatruff checkmypy src/pytest -qAcceptance criteria coverage
PolicyDeniedtest_read_rate_limit_exceededPolicyDeniedtest_write_rate_limit_exceededPolicyDeniedtest_destructive_rate_limit_exceededtest_rate_limit_configurabletest_service_role_gets_10x_limit(principal_id, capability_id)test_rate_limit_per_principal_capability_pairtime.sleep()_make_clock()test_rate_limit_window_slides+test_rate_limiter_window_expiresRisks / limitations
docs/security.md.Docs & agent instructions
docs/security.md: Updated — "no rate limiting" replaced with feature documentation.CHANGELOG.md: Updated with entry under[Unreleased].AGENTS.md,.github/copilot-instructions.md,.claude/CLAUDE.md): Reviewed — no updates needed. Existing "adding a policy rule" guidance and security invariants already cover this feature.Closes #39