fix(19.6.1-04): refuse code-declared role capture and wrap Administrators Runs

- Roles create and update refuse a submitted code that code-declared grants attach to
- compileActionExtensions wraps contributed Administrators Runs with guardOutranked
- Prove both holes on Postgres; keep the boot smoke callable with a superuser principal

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-10-22 13:29:38 +02:00
parent 422bd1e01f
commit 068adaabf9
5 changed files with 143 additions and 3 deletions

View File

@@ -261,6 +261,72 @@ func TestRolesCodeDeclaredLocks(t *testing.T) {
}
}
// TestRolesCodeDeclaredCodeRefused (CR-01, SC-2, D-07, D-16): a role never
// becomes a code-declared system role through the Roles screen. Create and
// rename onto a code that pact.Permission.Roles attaches grants to answer
// 422 code_locked, including when the editor is a superuser. A unique
// non-declared code still creates and a same-code save of a system role
// still succeeds.
func TestRolesCodeDeclaredCodeRefused(t *testing.T) {
declared := fmt.Sprintf("plugcap-%d", time.Now().UnixNano())
env := newScreensEnv(t, grantPlugin{id: "acme.capturer", grants: []pact.Permission{{Code: "acme.capturer.secret", Roles: []string{declared}}}})
editor := env.seedAdmin(t, adminSeed{Name: "codecap-editor", RolePermissions: map[string]int{cabana.PermissionManageRoles: 1}})
editorToken := env.login(t, editor.Login)
mutable := env.seedRole(t, roleSeed{Name: "codecap-mutable"})
before := env.roleRow(t, mutable.ID)
env.expectFieldRefusal(t, http.StatusUnprocessableEntity, http.MethodPut, roleURL(mutable.ID), editorToken,
roleBody(mutable, map[string]any{"code": declared}), "code", enCodeLocked, false)
if env.roleRow(t, mutable.ID) != before {
t.Fatal("a refused rename onto a code-declared code touched the role")
}
env.expectFieldRefusal(t, http.StatusUnprocessableEntity, http.MethodPost, rolesPath, editorToken,
map[string]any{"name": "Capture " + env.stamp, "code": declared}, "code", enCodeLocked, false)
if env.roleCountByCode(t, declared) != 0 {
t.Fatal("a refused create inserted a role with the code-declared code")
}
super := env.seedAdmin(t, adminSeed{Name: "codecap-super", Superuser: true})
superToken := env.login(t, super.Login)
env.expectFieldRefusal(t, http.StatusUnprocessableEntity, http.MethodPut, roleURL(mutable.ID), superToken,
roleBody(mutable, map[string]any{"code": declared}), "code", enCodeLocked, false)
if env.roleRow(t, mutable.ID) != before {
t.Fatal("a superuser rename onto a code-declared code touched the role")
}
env.expectFieldRefusal(t, http.StatusUnprocessableEntity, http.MethodPost, rolesPath, superToken,
map[string]any{"name": "Super capture " + env.stamp, "code": declared}, "code", enCodeLocked, false)
if env.roleCountByCode(t, declared) != 0 {
t.Fatal("a superuser create inserted a role with the code-declared code")
}
plain := "plain-" + env.stamp
created := dataOf(env.expect(t, http.StatusCreated, http.MethodPost, rolesPath, editorToken,
map[string]any{"name": "Plain " + env.stamp, "code": plain, "description": "Not declared"}))
createdID := uint(created["id"].(float64))
t.Cleanup(func() { env.gdb.Exec(`DELETE FROM backend_user_roles WHERE id = ?`, createdID) })
var developer cabana.BackendUserRole
if err := env.gdb.Where("code = ?", "developer").First(&developer).Error; err != nil {
t.Fatal(err)
}
env.expect(t, http.StatusOK, http.MethodPut, roleURL(developer.ID), superToken,
roleBody(developer, map[string]any{"description": "Same code " + env.stamp}))
t.Cleanup(func() {
env.gdb.Exec(`UPDATE backend_user_roles SET description = ? WHERE id = ?`, developer.Description, developer.ID)
})
}
// roleCountByCode counts backend_user_roles rows with code.
func (e *screensEnv) roleCountByCode(t *testing.T, code string) int64 {
t.Helper()
var n int64
if err := e.gdb.Raw(`SELECT count(*) FROM backend_user_roles WHERE code = ?`, code).Scan(&n).Error; err != nil {
t.Fatal(err)
}
return n
}
// reloadRoleByCode reads the role with code.
func (e *screensEnv) reloadRoleByCode(t *testing.T, code string) cabana.BackendUserRole {
t.Helper()