Skip to content

Project routes ignore agent grants: an approved project_tasks scope grants nothing #2095

Description

@jaylfc

Found while onboarding the taOSmobile agent on 2026-07-21. Jay approved an invite granting eight scopes including project_tasks, the grants are recorded correctly, and the agent still cannot create tasks.

The gap

create_task in tinyagentos/routes/projects.py:587 authorises via _get_owned_project, which is:

if not user.is_admin and user.user_id != p["user_id"]:
    return JSONResponse({"error": "not found"}, status_code=404)

Owner or admin only. The grants table is never consulted. So an agent holding an active project_tasks grant on that exact project gets a masked 404, identical to what a stranger sees.

Verified on the live instance: GET /api/agents/registry/grants?canonical_id=... returns all eight scopes as active, project-scoped to prj-qlvxdq:

a2a_send, a2a_receive, project_tasks, canvas_read,
canvas_write, files_read, files_write, memory_read   (tier=once, project=prj-qlvxdq)

tier='once' is a misleading label but not the problem: the store docstring defines it as "single-decision, no expiry", so these are permanent.

Why it matters

The consent flow, the invite scopes, the approval prompt and the grant feed all work. The enforcement point does not read any of it, so approving project_tasks for an external agent currently grants nothing. That is worse than refusing the scope, because the UI reports success and the agent is left guessing why every call 404s.

It also means the scope set offered at invite time is partly fictional. Worth auditing which other scopes have a real enforcement point and which are similarly decorative.

Fix

Authorise project routes against owner, admin, or an active grant for the required scope on that project. Roughly:

  • Add a helper alongside _get_owned_project, e.g. _get_project_for_scope(pstore, grants, project_id, user, scope).
  • Keep the masked 404 for genuine strangers so existence is still hidden.
  • Apply it to the task routes first (create_task, claim, release, close, comment), since project_tasks is the scope we actually hand out.
  • Add a negative test per route: an agent WITHOUT the grant still gets 404, an agent WITH it succeeds. Without both halves the test proves nothing.

Audit needed

Check every scope offered by the invite dialog against a real enforcement point:
project_tasks, canvas_read, canvas_write, files_read, files_write, memory_read, memory_write, decisions_read, decisions_write, registry_feeds_read, tools_execute, a2a_send, a2a_receive.

a2a_send and a2a_receive are known to be enforced (the bus reads the grant feed). The rest need confirming rather than assuming.

Related: #1920 and PR #1921 (agent requests additional scopes, which is the tier above this and pointless until this is fixed), #133 (per-agent permission model), #552 (multi-user isolation).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions