Gitea Version
1.27.3
What happened?
Bug: Administrator users cannot be unblocked, and users cannot block an administrator
Description
Gitea 1.27.3 has inconsistent behavior when blocking/unblocking users who have administrator privileges.
A normal user can block another normal user successfully. However, when the target user is assigned administrator privileges, the blocking/unblocking behavior becomes incorrect.
Observed behavior
- An administrator can block a normal user successfully.
- A normal user can block another normal user successfully.
- A normal user cannot block a user who has administrator privileges.
- If a normal user had already blocked a user and that user is subsequently assigned administrator privileges, the existing block relationship remains in the database, but the normal user can no longer unblock that administrator through the web UI.
- The unblock request returns HTTP
400 Bad Request.
Gitea Version
Gitea version 1.27.3
built with go1.26.7-X:jsonv2
Docker image:
Environment
- Gitea 1.27.3
- SQLite3
- Nginx reverse proxy
- Gitea HTTP port 3000
Scenario 1: Administrator blocks a normal user
When logged in as an administrator:
Administrator → blocks → Normal user
The operation succeeds.
Scenario 2: Normal user blocks another normal user
When both users are normal users:
Normal user A → blocks → Normal user B
The operation succeeds.
Scenario 3: Normal user attempts to block an administrator
When the target user has administrator privileges:
Normal user → blocks → Administrator
The operation fails.
Scenario 4: Existing block becomes an administrator
This is the reproducible case.
Initially:
rayven.delacruz → blocks → root
At the time the block was created, the relationship exists in the database:
blocker_id = 2
blockee_id = 1
The root user is subsequently assigned administrator privileges.
The existing block relationship remains:
id blocker_id blockee_id
1 2 1
However, attempting to unblock root from:
submits:
action=unblock
blockee=root
and Gitea returns:
The block relationship remains in the database.
Relevant Source Code
In Gitea 1.27.3, BlockedUsersPost() calls:
case "unblock":
err = user_service.UnblockUser(ctx, ctx.Doer, blocker, blockee)
UnblockUser() calls:
if !CanUnblockUser(ctx, doer, blocker, blockee) {
return user_model.ErrCanNotUnblock
}
CanUnblockUser() calls:
if !user_model.IsUserBlockedBy(ctx, blockee, blocker.ID) {
return false
}
However, IsUserBlockedBy() contains:
if blockee.IsAdmin {
return false
}
before checking the user_blocking table.
Therefore, if the blockee is an administrator, the function returns false even when an actual block relationship exists.
The subsequent flow becomes:
Administrator blockee
↓
IsUserBlockedBy() returns false
↓
CanUnblockUser() returns false
↓
ErrCanNotUnblock
↓
HTTP 400 Bad Request
Database Evidence
The user_blocking table contains:
id blocker_id blockee_id
1 2 1
The users are:
1 | root
2 | rayven.delacruz
Therefore:
rayven.delacruz (ID 2)
↓
blocks
↓
root (ID 1)
The relationship exists even though root is an administrator.
Expected Behavior
Administrator status should not make an existing block relationship invisible to the unblock operation.
If:
and User B is subsequently granted administrator privileges, User A should still be able to manage/remove their existing block relationship.
Additionally, the behavior of blocking should be consistent and clearly defined for administrator accounts.
Actual Behavior
When the target user is an administrator:
Normal user → Administrator
the normal user cannot block the administrator.
If a previously blocked user is subsequently made an administrator, the existing block remains in the database but cannot be removed through the web UI.
Relevant Logs
2026/08/31 09:31:36 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45094, 400 Bad Request in 5.1ms @ setting/block.go:31(setting.BlockedUsersPost)
2026/08/31 09:31:37 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45100, 400 Bad Request in 6.7ms @ setting/block.go:31(setting.BlockedUsersPost)
Suspected Cause
The following check in IsUserBlockedBy() appears to cause the inconsistency:
if blockee.IsAdmin {
return false
}
This causes administrator users to be treated as if they are not blocked, even when a corresponding user_blocking database record exists.
Suggested Fix
Consider separating the concepts of:
- Whether a user is currently blocked.
- Whether a user is allowed to initiate a new block.
- Whether an existing block can be removed.
IsUserBlockedBy() should potentially continue to report an existing block relationship regardless of the blockee's administrator status, while permission checks for creating or managing blocks can be handled separately.
Impact
This can leave stale/unmanageable block relationships when a user changes administrator status.
It also creates inconsistent behavior depending on whether the target user is an administrator.
Reproducibility
The issue is reproducible consistently on Gitea 1.27.3.
From server logs
itea.1.zb3627ziv7er@ldap | 2026/08/30 13:51:50 cmd/web.go:231:serveInstalled() [I] PID: 17 Gitea Web Finished
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:32 HTTPRequest [I] router: completed GET /user/settings/blocked_users for 10.5.0.4:43478, 200 OK in 17.2ms @ setting/block.go:19(setting.BlockedUsers)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:33 HTTPRequest [I] router: completed GET /avatar/627624ba36c0f5cbaf9b3bbe7c992d11?size=48 for 10.5.0.4:43490, 303 See Other in 16.8ms @ user/avatar.go:38(user.AvatarByEmailHash)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:36 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45094, 400 Bad Request in 5.1ms @ setting/block.go:31(setting.BlockedUsersPost)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:37 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45100, 400 Bad Request in 6.7ms @ setting/block.go:31(setting.BlockedUsersPost)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:37 HTTPRequest [I] router: polling GET /user/events for 10.5.0.4:43532, elapsed 3929.5ms @ events/events.go:18(events.Events)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:38 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45108, 400 Bad Request in 6.7ms @ setting/block.go:31(setting.BlockedUsersPost)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:40:02 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:42300, 400 Bad Request in 11.5ms @ setting/block.go:31(setting.BlockedUsersPost)
How are you running Gitea?
No response
Gitea Version
1.27.3
What happened?
Bug: Administrator users cannot be unblocked, and users cannot block an administrator
Description
Gitea 1.27.3 has inconsistent behavior when blocking/unblocking users who have administrator privileges.
A normal user can block another normal user successfully. However, when the target user is assigned administrator privileges, the blocking/unblocking behavior becomes incorrect.
Observed behavior
400 Bad Request.Gitea Version
Docker image:
Environment
Scenario 1: Administrator blocks a normal user
When logged in as an administrator:
The operation succeeds.
Scenario 2: Normal user blocks another normal user
When both users are normal users:
The operation succeeds.
Scenario 3: Normal user attempts to block an administrator
When the target user has administrator privileges:
The operation fails.
Scenario 4: Existing block becomes an administrator
This is the reproducible case.
Initially:
At the time the block was created, the relationship exists in the database:
The
rootuser is subsequently assigned administrator privileges.The existing block relationship remains:
However, attempting to unblock
rootfrom:submits:
and Gitea returns:
The block relationship remains in the database.
Relevant Source Code
In Gitea 1.27.3,
BlockedUsersPost()calls:UnblockUser()calls:CanUnblockUser()calls:However,
IsUserBlockedBy()contains:before checking the
user_blockingtable.Therefore, if the blockee is an administrator, the function returns
falseeven when an actual block relationship exists.The subsequent flow becomes:
Database Evidence
The
user_blockingtable contains:The users are:
Therefore:
The relationship exists even though
rootis an administrator.Expected Behavior
Administrator status should not make an existing block relationship invisible to the unblock operation.
If:
and User B is subsequently granted administrator privileges, User A should still be able to manage/remove their existing block relationship.
Additionally, the behavior of blocking should be consistent and clearly defined for administrator accounts.
Actual Behavior
When the target user is an administrator:
the normal user cannot block the administrator.
If a previously blocked user is subsequently made an administrator, the existing block remains in the database but cannot be removed through the web UI.
Relevant Logs
Suspected Cause
The following check in
IsUserBlockedBy()appears to cause the inconsistency:This causes administrator users to be treated as if they are not blocked, even when a corresponding
user_blockingdatabase record exists.Suggested Fix
Consider separating the concepts of:
IsUserBlockedBy()should potentially continue to report an existing block relationship regardless of the blockee's administrator status, while permission checks for creating or managing blocks can be handled separately.Impact
This can leave stale/unmanageable block relationships when a user changes administrator status.
It also creates inconsistent behavior depending on whether the target user is an administrator.
Reproducibility
The issue is reproducible consistently on Gitea 1.27.3.
From server logs
itea.1.zb3627ziv7er@ldap | 2026/08/30 13:51:50 cmd/web.go:231:serveInstalled() [I] PID: 17 Gitea Web Finished
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:32 HTTPRequest [I] router: completed GET /user/settings/blocked_users for 10.5.0.4:43478, 200 OK in 17.2ms @ setting/block.go:19(setting.BlockedUsers)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:33 HTTPRequest [I] router: completed GET /avatar/627624ba36c0f5cbaf9b3bbe7c992d11?size=48 for 10.5.0.4:43490, 303 See Other in 16.8ms @ user/avatar.go:38(user.AvatarByEmailHash)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:36 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45094, 400 Bad Request in 5.1ms @ setting/block.go:31(setting.BlockedUsersPost)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:37 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45100, 400 Bad Request in 6.7ms @ setting/block.go:31(setting.BlockedUsersPost)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:37 HTTPRequest [I] router: polling GET /user/events for 10.5.0.4:43532, elapsed 3929.5ms @ events/events.go:18(events.Events)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:31:38 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:45108, 400 Bad Request in 6.7ms @ setting/block.go:31(setting.BlockedUsersPost)
gitea.1.isvocrqxants@ldap | 2026/08/31 09:40:02 HTTPRequest [I] router: completed POST /user/settings/blocked_users for 10.5.0.4:42300, 400 Bad Request in 11.5ms @ setting/block.go:31(setting.BlockedUsersPost)
How are you running Gitea?
No response