Skip to content

feat(canvas): agent scope + permission gating and honest attribution (#1800 slice 3)#1827

Merged
jaylfc merged 2 commits into
feat/lead-identity-basefrom
feat/lead-identity-route-gating
Jul 14, 2026
Merged

feat(canvas): agent scope + permission gating and honest attribution (#1800 slice 3)#1827
jaylfc merged 2 commits into
feat/lead-identity-basefrom
feat/lead-identity-route-gating

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 14, 2026

Copy link
Copy Markdown
Owner

slice 3 of #1800 identity epic, stacked on the base branch, DO NOT MERGE.


Summary by Gitar

  • Access Control:
    • Added canvas_read and canvas_write agent scopes and implemented _authorize_canvas_actor to enforce gating across all canvas routes.
    • Added can_read_canvas and can_edit_canvas flags to project_members, requiring these flags for agent actions.
  • Authorization:
    • Restricted set_canvas_permission to project owners and admins only.
    • Added defense-in-depth permission checks within ProjectCanvasStore to prevent bypass by direct callers.
  • Attribution & Events:
    • Standardized actor attribution (kind, id) in canvas.* events and element metadata.
  • Testing:
    • Added comprehensive test suite for scope gating, cross-project access, and proper actor attribution.

This will update automatically on new commits.

Gate the canvas routes by agent scope and per-project canvas permission
(slice 3 of the lead-agent identity epic), fix element attribution so a
session user is recorded by id and an agent principal by its agent id, and
stamp a uniform {kind, id} actor on every canvas event.

Docs-Reviewed: canvas route gating + attribution + event actor 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: c4d6a853-bec1-40e9-a3a4-9048f4152a2d

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-route-gating

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

author_kind=actor_kind, author_id=actor_id,
element_id=element_id,
)
except ValueError as e:

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: create_canvas_element does not catch CanvasPermissionError

The store's add_element now calls _check_edit_permission (store.py:130), which raises CanvasPermissionError for agents lacking can_edit_canvas. update_canvas_element and delete_canvas_element catch this and return 403, but this create handler only catches ValueError, so an agent that reaches add_element without the edit flag (e.g. if the route gate and store gate ever disagree — the stated purpose of the "defense in depth" check) would surface a 500 instead of a clean 403. Add a dedicated except CanvasPermissionError as e: returning 403, mirroring the update/delete handlers, before the except ValueError.


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

# Middleware normally 401s unauthenticated requests before the route
# runs; a middleware-bypassing test context reaches here, so fall back
# to a system actor (there is no real principal to attribute to).
return ("user", "system")

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: Unauthenticated requests fall back to a privileged system actor

When request.state.user_id is unset and there is no Bearer header, this returns ("user", "system"), which is then treated as an authorized writer. ProjectCanvasStore._check_edit_permission also short-circuits for any author_kind == "user", so the system actor can read, create, update, and delete elements on ANY project's canvas without being a member. The whole gate therefore depends on the auth middleware always setting user_id or 401-ing first; a middleware bypass, misconfiguration, or a route registered without it becomes a privilege-escalation gap. Return 401 (or 403) here rather than authorizing a synthetic system principal.


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

if exc.status_code == 403 and exc.detail == PROJECT_SCOPE_MISMATCH_DETAIL:
return JSONResponse({"error": "not found"}, status_code=404)
raise
if cid is None:

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: Unreachable cid is None branch

check_agent_scope_for_project only returns None when there is no Authorization header, but execution only reaches this point after the auth_header.lower().startswith("bearer ") guard (line 60), so cid can never be None here. This branch is dead code; the except HTTPException above already covers the token cases (including the project-mismatch 403 that is collapsed to 404).


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: 4 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
tinyagentos/routes/project_canvas.py 154 create_canvas_element only catches ValueError; the store's _check_edit_permission raises CanvasPermissionError for agents lacking edit permission, so a denied agent POST surfaces a 500 instead of 403 (update/delete handle it correctly).
tinyagentos/routes/project_canvas.py 73 No-auth requests fall back to ("user", "system"), treated as an authorized writer; the whole gate depends on middleware always setting user_id or 401-ing, a privilege-escalation gap if bypassed.

SUGGESTION

File Line Issue
tinyagentos/routes/project_canvas.py 65 New session visibility gate uses project is not None guard, so a non-existent project bypasses the 404 and returns 200 empty, letting an authenticated non-owner distinguish existence vs. forbidden — contradicting the existence-hiding goal. Collapse non-existent projects into the 404 too.
tinyagentos/routes/project_canvas.py 85 Unreachable cid is None branch: check_agent_scope_for_project only returns None without an Authorization header, which is already guarded above.
Files Reviewed (this increment)
  • tinyagentos/routes/project_canvas.py - session visibility gate (2 prior + 1 new issue)
  • tinyagentos/auth_middleware.py - reviewed (PATCH elements route added to allowlist)
  • tinyagentos/projects/canvas/mcp_tools.py - reviewed (read gating added)
  • tinyagentos/projects/canvas/store.py - reviewed (check_read_permission added)
  • tests/projects/test_canvas_mcp_tools.py - reviewed
  • tests/test_auth_middleware.py - reviewed
  • tests/test_routes_project_canvas.py - reviewed

Fix these issues in Kilo Cloud

Previous Review Summary (commit e97bdc1)

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

Previous review (commit e97bdc1)

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 145 create_canvas_element only catches ValueError; the store's new _check_edit_permission raises CanvasPermissionError for agents lacking edit permission, which would surface as a 500 instead of 403 (update/delete handle it correctly).
tinyagentos/routes/project_canvas.py 64 No-auth requests fall back to ("user", "system"), which is treated as an authorized writer; the whole gate depends on middleware always setting user_id or 401-ing, a privilege-escalation gap if bypassed.

SUGGESTION

File Line Issue
tinyagentos/routes/project_canvas.py 76 if cid is None: branch is unreachable here since check_agent_scope_for_project only returns None without an Authorization header, which is already guarded above.
Files Reviewed (9 files)
  • tinyagentos/routes/project_canvas.py - 3 issues
  • tinyagentos/projects/canvas/store.py - reviewed
  • tinyagentos/projects/project_store.py - reviewed
  • tinyagentos/projects/canvas/mcp_tools.py - reviewed
  • tests/projects/test_canvas_integration.py - reviewed
  • tests/projects/test_canvas_mcp_tools.py - reviewed
  • tests/projects/test_canvas_store.py - reviewed
  • tests/projects/test_routes_canvas.py - reviewed
  • tests/test_routes_project_canvas.py - reviewed

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 81.3K · Output: 9K · Cached: 254.1K

- Finding 1: add the PATCH /canvas/elements/{eid} route to the agent canvas
  allowlist so a canvas_write token can update elements; the PATCH permissions
  route stays owner/admin only. Flip the middleware allowlist test accordingly.
- Finding 2: gate the session branch of _authorize_canvas_actor on project
  visibility (owner/admin). A non-owner collapses to the same existence-hiding
  404 the agent path uses; owner/admin and user attribution are unchanged.
- Finding 4: add a can_read_canvas store gate and enforce it on the in-process
  MCP read path (canvas_list_elements) so a write-only agent cannot read the
  board, mirroring the write flag gate.

Docs-Reviewed: fold adversarial review findings per lead-agent-identity-and-canvas-access.md
ps = request.app.state.project_store
project = await ps.get_project(project_id)
is_admin = bool(getattr(request.state, "is_admin", False))
if project is not None and not is_admin and project.get("user_id") != uid:

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: Non-existent projects bypass the new gate and break existence-hiding

The project is not None guard means that when the project does not exist, the condition is False and the session user falls through to return ("user", ...). Downstream routes like list_canvas_elements then return 200 with {"elements": []} instead of 404. So an authenticated non-owner can distinguish "project exists but forbidden" (404) from "project does not exist" (200 empty) — contradicting the comment's claim that a non-owner collapses into the same existence-hiding 404 as the agent path. Collapse non-existent projects into the 404 as well:

Suggested change
if project is not None and not is_admin and project.get("user_id") != uid:
if project is None or (not is_admin and project.get("user_id") != uid):

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

@jaylfc
jaylfc merged commit bffab44 into feat/lead-identity-base Jul 14, 2026
3 checks passed
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