From 36cdc2ce2be0b8ccc74227d178b92047d59cd65f Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 19 Jul 2026 15:01:09 +0200 Subject: [PATCH] fix(kanban): authorize body task_id when moving a task into a bucket (GHSA-5pg6-m483-7vrg) --- pkg/models/kanban_task_bucket.go | 10 +++++++++- pkg/webtests/huma_task_bucket_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/pkg/models/kanban_task_bucket.go b/pkg/models/kanban_task_bucket.go index 327eef07b..50d48b141 100644 --- a/pkg/models/kanban_task_bucket.go +++ b/pkg/models/kanban_task_bucket.go @@ -56,7 +56,15 @@ func (b *TaskBucket) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) { ProjectID: b.ProjectID, ProjectViewID: b.ProjectViewID, } - return bucket.canDoBucket(s, a) + canDoBucket, err := bucket.canDoBucket(s, a) + if err != nil || !canDoBucket { + return false, err + } + + // The task comes from the request body and may live in a different + // project than the bucket, so it needs its own write check. + task := &Task{ID: b.TaskID} + return task.CanWrite(s, a) } func (b *TaskBucket) upsert(s *xorm.Session) (err error) { diff --git a/pkg/webtests/huma_task_bucket_test.go b/pkg/webtests/huma_task_bucket_test.go index e1623bf67..7e383f17c 100644 --- a/pkg/webtests/huma_task_bucket_test.go +++ b/pkg/webtests/huma_task_bucket_test.go @@ -115,6 +115,32 @@ func TestTaskBucketV2(t *testing.T) { assert.Contains(t, rec.Body.String(), fmt.Sprintf(`"code":%d`, models.ErrCodeProjectViewDoesNotExist)) }) + t.Run("task from a foreign project is forbidden", func(t *testing.T) { + e, err := setupTestEnv() + require.NoError(t, err) + // testuser1 owns project 1 but has no access to project 20 (owner 13), + // where task 34 lives. The body task id must be authorized, not just + // the URL bucket (GHSA-5pg6-m483-7vrg). + token := humaTokenFor(t, &testuser1) + + rec := humaRequest(t, e, http.MethodPut, fmt.Sprintf(path, 3), `{"task_id":34}`, token, "") + require.Equal(t, http.StatusForbidden, rec.Code, "body: %s", rec.Body.String()) + + db.AssertMissing(t, "task_buckets", map[string]interface{}{ + "task_id": 34, + "project_view_id": 4, + }) + // The task's own bucket placement stays untouched. + db.AssertExists(t, "task_buckets", map[string]interface{}{ + "task_id": 34, + "bucket_id": 5, + }, false) + db.AssertExists(t, "tasks", map[string]interface{}{ + "id": 34, + "done": false, + }, false) + }) + t.Run("no write access is forbidden", func(t *testing.T) { e, err := setupTestEnv() require.NoError(t, err)