Skip to content

feat(canvas): payload cap + agent write rate limit (#1800 slice 5)#1829

Closed
jaylfc wants to merge 2 commits into
feat/lead-identity-basefrom
feat/lead-identity-limits
Closed

feat(canvas): payload cap + agent write rate limit (#1800 slice 5)#1829
jaylfc wants to merge 2 commits into
feat/lead-identity-basefrom
feat/lead-identity-limits

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Builds on feat/lead-identity-base (slices 1+2+3).

What changed:

  • tinyagentos/routes/project_canvas.py: added a 64 KB payload size cap (HTTP 413) checked on element create and PATCH update; added a per-agent rolling-window write rate limiter (30 writes / 60 s, HTTP 429) scoped to POST create, PATCH update, and DELETE; session/human writes are never rate-limited.
  • tests/test_routes_project_canvas.py: added TestCanvasPayloadSizeCap (413 on oversized create/patch payload) and TestAgentWriteRateLimit (429 for agent exceeding window, 429 on agent DELETE exceeding window, human writes unaffected by rate limit).

DO NOT MERGE.

Docs-Reviewed: canvas payload cap + agent write rate limit per lead-agent-identity-and-canvas-access.md
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • master
  • dev

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 67794fc8-cac2-4aba-9101-affd1064bd78

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lead-identity-limits

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

def is_limited(self, key: str) -> bool:
with self._lock:
self._prune(key)
return len(self._log.get(key, [])) >= self._max

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Non-atomic check-then-act allows the rate limit to be exceeded under concurrency

The limit decision here and the increment in record are separate locked operations, but record is only called after the awaited store write (await cs.add_element / update_element / delete_element). Under a burst, more than 30 concurrent requests can all observe count < 30 from is_limited before any of them calls record, so all of them succeed and the 30/60s budget is exceeded.

Fix: combine the check and increment into a single atomic method (acquire lock once, if count >= max return limited without recording, else append timestamp and return allowed) and call it exactly once per request.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.



def _check_payload_size(payload: dict) -> None:
size = len(json.dumps(payload, default=str).encode("utf-8"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Payload cap is enforced after full request parsing and may mis-measure

_check_payload_size runs on the already-deserialized pydantic payload, i.e. only after FastAPI/pydantic have fully materialized the entire JSON request body in memory. A 64 KB payload cap therefore does not protect against a multi-MB request body (memory/DoS): the oversized body is already parsed before this guard returns 413. Also json.dumps(payload, default=str) may over/under-estimate size versus the store's actual serialization if payload ever holds non-JSON-native objects.

For real protection, enforce a raw request-body size limit (ASGI/gateway layer or a streaming Request body read) before deserialization.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

self._log.move_to_end(key)


_canvas_write_limiter = _CanvasWriteLimiter()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: In-memory singleton limiter: per-process, non-persistent, and global per agent

The limiter is a module-level in-memory dict, so (a) it resets on every process restart, (b) it is not shared across multiple workers/instances — so the effective limit becomes 30 * workers per window — and (c) it keys only on actor_id, meaning one agent's 30/60s budget is shared across all projects it can write to rather than being scoped per (agent, project).

If cross-project isolation or multi-worker correctness matters, back this with a shared store (Redis/DB) and key on (actor_id, project_id). Also consider adding a Retry-After header on the 429 responses for correct client backoff.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/project_canvas.py 60 Non-atomic check-then-act (is_limited then record across the awaited store write) lets a concurrent burst exceed the 30/60s budget.
tinyagentos/routes/project_canvas.py 76 64 KB payload cap is enforced after FastAPI/pydantic fully parse the request body, so a multi-MB body is already in memory before 413; default=str may also mis-measure size.

SUGGESTION

File Line Issue
tinyagentos/routes/project_canvas.py 72 Limiter is an in-memory per-process singleton: resets on restart, not shared across workers (effective limit = 30×workers), and keys only on actor_id (budget shared across all projects). Consider a shared store + per-(agent,project) key and a Retry-After header on 429.
Files Reviewed (2 files)
  • tinyagentos/routes/project_canvas.py - 3 issues carried forward (no code changes in this increment; lines 60, 72, 76 still active).
  • tests/test_routes_project_canvas.py - The previously-flagged deleted test classes (TestAgentCanvasWriteGating, TestAgentCanvasCrossProject, TestCanvasAttribution, TestCanvasEventActor) were re-added in this increment, closing the coverage gap — that WARNING is now RESOLVED. No new issues found in the added tests.

Fix these issues in Kilo Cloud

Previous Review Summary (commit 1c05d54)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 1c05d54)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/project_canvas.py 60 Non-atomic check-then-act (is_limited then record across the awaited store write) lets a concurrent burst exceed the 30/60s budget.
tinyagentos/routes/project_canvas.py 76 64 KB payload cap is enforced after FastAPI/pydantic fully parse the request body, so a multi-MB body is already in memory before 413; default=str may also mis-measure size.
tests/test_routes_project_canvas.py n/a (deleted region) This PR deletes several behavioral/security test classes (TestAgentCanvasWriteGating, TestAgentCanvasCrossProject, TestCanvasAttribution, TestCanvasEventActor) with no replacement anywhere in the repo. Coverage for agent write gating, cross-project isolation, attribution, and event-actor stamping (D4) is lost.

SUGGESTION

File Line Issue
tinyagentos/routes/project_canvas.py 72 Limiter is an in-memory per-process singleton: resets on restart, not shared across workers (effective limit = 30×workers), and keys only on actor_id (budget shared across all projects). Consider a shared store + per-(agent,project) key and a Retry-After header on 429.
Files Reviewed (2 files)
  • tinyagentos/routes/project_canvas.py - 3 issues
  • tests/test_routes_project_canvas.py - 1 issue (deleted test coverage)

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 73.9K · Output: 6.8K · Cached: 77.1K

… 5 per lead-agent-identity-and-canvas-access.md
@jaylfc

jaylfc commented Jul 15, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #1838, which landed all seven slices as one consistent set on dev (with the CI-caught active-handle migration-order fix folded). Closing.

@jaylfc jaylfc closed this Jul 15, 2026
@jaylfc jaylfc deleted the feat/lead-identity-limits branch July 15, 2026 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant