Commit aff7376
authored
fix(tasks): require owner permission to cancel a task via REST (#308)
## Problem
`POST /tasks/{task_id}/cancel` authorizes
`AuthorizedOperationType.update`. Editors hold `update`, so a task
editor who is not the owner can cancel a task through the REST route.
Cancellation is intended to be **owner-only**.
The RPC `task/cancel` path already authorizes the owner-only `cancel`
action, so the two entry points to the same operation disagreed.
## Fix
Authorize `cancel` on the REST route, matching the RPC path and the
schema. The sibling lifecycle routes
(`complete`/`fail`/`terminate`/`timeout`) intentionally keep `update` —
those are state transitions, not the owner-only cancel.
## Tests
Adds a unit test pinning the REST cancel route to the `cancel` action
(mirrors the existing per-RPC operation-routing tests). End-to-end
behavioral coverage lives in the authorization e2e suite.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes a privilege escalation bug in the REST `POST
/tasks/{task_id}/cancel` endpoint where it previously authorized
`update` (allowing editors), but cancellation is owner-only. The fix
aligns the REST route with the RPC `task/cancel` path by switching to
`AuthorizedOperationType.cancel`.
- **`tasks.py`**: Changes `cancel_task`'s `DAuthorizedId` call from
`AuthorizedOperationType.update` to `AuthorizedOperationType.cancel`,
adding a clarifying comment about the intent.
- **`test_tasks_authz.py`**: Adds `TestRestCancelRouteRequiresOwner` — a
unit test that introspects the function signature to assert the
dependency checks `cancel`, mirroring the existing per-RPC routing
tests.
<details><summary><h3>Confidence Score: 5/5</h3></summary>
Safe to merge — the change is a one-line auth operation swap on a single
route, and the new test directly pins the expected behavior.
The change is minimal and surgical: one enum value replaced in one
route, with a well-scoped unit test that introspects the function
signature at test time to verify the correct operation is wired. The
sibling lifecycle routes (complete/fail/terminate/timeout) are
intentionally left on update and are unaffected. The RPC path already
used cancel and is also untouched. No edge cases, no new dependencies,
no schema changes.
No files require special attention.
</details>
<h3>Important Files Changed</h3>
| Filename | Overview |
|----------|----------|
| agentex/src/api/routes/tasks.py | Single-line auth fix: cancel_task's
DAuthorizedId now uses AuthorizedOperationType.cancel instead of update;
comment explains the intentional difference from sibling lifecycle
routes. |
| agentex/tests/unit/api/test_tasks_authz.py | New
TestRestCancelRouteRequiresOwner class introspects the cancel_task
signature at test time and asserts the dependency resolves to the cancel
operation; mirrors existing RPC routing test patterns. |
</details>
<details><summary><h3>Sequence Diagram</h3></summary>
```mermaid
sequenceDiagram
participant Client
participant REST as REST POST /tasks/{id}/cancel
participant RPC as RPC task/cancel
participant Authz as Authorization Service
Note over REST,Authz: Before this PR (bug)
Client->>REST: "POST /tasks/{id}/cancel"
REST->>Authz: check(task, update) editors allowed
Authz-->>REST: authorized (editor or owner)
REST-->>Client: task canceled
Note over REST,Authz: After this PR (fix)
Client->>REST: "POST /tasks/{id}/cancel"
REST->>Authz: check(task, cancel) owner-only
Authz-->>REST: authorized (owner only)
REST-->>Client: task canceled
Client->>RPC: task/cancel
RPC->>Authz: check(task, cancel) owner-only (unchanged)
Authz-->>RPC: authorized (owner only)
RPC-->>Client: task canceled
```
</details>
<sub>Reviews (1): Last reviewed commit: ["fix(tasks): require owner
permission to
..."](a186b78)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=37712273)</sub>
<!-- /greptile_comment -->1 parent d65011a commit aff7376
2 files changed
Lines changed: 33 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
258 | 258 | | |
259 | 259 | | |
260 | 260 | | |
261 | | - | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
262 | 265 | | |
263 | 266 | | |
264 | 267 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
209 | 209 | | |
210 | 210 | | |
211 | 211 | | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
212 | 241 | | |
213 | 242 | | |
214 | 243 | | |
| |||
0 commit comments