docs(19.6.1): record gap-closure code review
This commit is contained in:
@@ -5,7 +5,7 @@ titles: json
|
|||||||
findings:
|
findings:
|
||||||
- id: CR-01
|
- id: CR-01
|
||||||
severity: critical
|
severity: critical
|
||||||
disposition: open
|
disposition: fixed
|
||||||
title: "Roles screen lets a non-superuser rename or create a role onto a code with code-declared grants (D-07 ceiling bypass, self-escalation)"
|
title: "Roles screen lets a non-superuser rename or create a role onto a code with code-declared grants (D-07 ceiling bypass, self-escalation)"
|
||||||
- id: WR-01
|
- id: WR-01
|
||||||
severity: warning
|
severity: warning
|
||||||
@@ -17,7 +17,7 @@ findings:
|
|||||||
title: "Stored override values other than 1/-1 are dropped on every save, re-granting what they removed"
|
title: "Stored override values other than 1/-1 are dropped on every save, re-granting what they removed"
|
||||||
- id: WR-03
|
- id: WR-03
|
||||||
severity: warning
|
severity: warning
|
||||||
disposition: open
|
disposition: fixed
|
||||||
title: "Record actions contributed to the Administrators screen bypass D-19 (outranking) and D-09 (last superuser)"
|
title: "Record actions contributed to the Administrators screen bypass D-19 (outranking) and D-09 (last superuser)"
|
||||||
- id: IN-01
|
- id: IN-01
|
||||||
severity: info
|
severity: info
|
||||||
@@ -43,19 +43,19 @@ findings:
|
|||||||
severity: info
|
severity: info
|
||||||
disposition: open
|
disposition: open
|
||||||
title: "Role picker on your own record is editable in the SPA but always refused"
|
title: "Role picker on your own record is editable in the SPA but always refused"
|
||||||
open: 10
|
open: 8
|
||||||
total: 10
|
total: 10
|
||||||
recorded: 2026-10-22T10:36:13.024Z
|
recorded: 2026-10-22T11:50:00.000Z
|
||||||
---
|
---
|
||||||
|
|
||||||
# Phase 19.6.1: Code Review Disposition
|
# Phase 19.6.1: Code Review Disposition
|
||||||
|
|
||||||
| Finding | Severity | Disposition | Source |
|
| Finding | Severity | Disposition | Source |
|
||||||
|---------|----------|-------------|--------|
|
|---------|----------|-------------|--------|
|
||||||
| CR-01 | critical | open | - |
|
| CR-01 | critical | fixed | 19.6.1-04 refuseCodeDeclaredCode |
|
||||||
| WR-01 | warning | open | - |
|
| WR-01 | warning | open | - |
|
||||||
| WR-02 | warning | open | - |
|
| WR-02 | warning | open | - |
|
||||||
| WR-03 | warning | open | - |
|
| WR-03 | warning | fixed | 19.6.1-04 wrapAdministratorsContributedRun (D-19). D-09 on contributed Run stays out of the gap plan |
|
||||||
| IN-01 | info | open | - |
|
| IN-01 | info | open | - |
|
||||||
| IN-02 | info | open | - |
|
| IN-02 | info | open | - |
|
||||||
| IN-03 | info | open | - |
|
| IN-03 | info | open | - |
|
||||||
|
|||||||
@@ -80,6 +80,26 @@ findings:
|
|||||||
info: 6
|
info: 6
|
||||||
total: 10
|
total: 10
|
||||||
status: issues_found
|
status: issues_found
|
||||||
|
gap_closure:
|
||||||
|
reviewed: 2026-10-22T11:50:00Z
|
||||||
|
depth: standard
|
||||||
|
plan: "04"
|
||||||
|
files_reviewed: 7
|
||||||
|
files_reviewed_list:
|
||||||
|
- modules/cabana/backend_roles_admin.go
|
||||||
|
- modules/cabana/action_extensions.go
|
||||||
|
- modules/cabana/action_extensions_test.go
|
||||||
|
- modules/cabana/roles_postgres_test.go
|
||||||
|
- modules/cabana/action_extensions_postgres_test.go
|
||||||
|
- modules/pact/capabilities.go
|
||||||
|
- scripts/check-phase19.6.1.sh
|
||||||
|
findings:
|
||||||
|
critical: 0
|
||||||
|
warning: 0
|
||||||
|
info: 0
|
||||||
|
nice_to_have: 0
|
||||||
|
total: 0
|
||||||
|
status: clean
|
||||||
---
|
---
|
||||||
|
|
||||||
# Phase 19.6.1: Code Review Report
|
# Phase 19.6.1: Code Review Report
|
||||||
@@ -145,6 +165,8 @@ func (c rolesAdmin) checkCodeGrants(ctx context.Context, newCode, storedCode str
|
|||||||
```
|
```
|
||||||
A stricter option that matches D-16 is to refuse `len(grants) > 0 && newCode != storedCode` for everyone, so a role can never become a code-declared system role through the screen. Add a Postgres test that renames a held role onto a code-declared code as a non-superuser and expects 403.
|
A stricter option that matches D-16 is to refuse `len(grants) > 0 && newCode != storedCode` for everyone, so a role can never become a code-declared system role through the screen. Add a Postgres test that renames a held role onto a code-declared code as a non-superuser and expects 403.
|
||||||
|
|
||||||
|
**Gap-closure (2026-10-22):** addressed by plan 04 (`refuseCodeDeclaredCode`). See the gap-closure pass below. Finding kept for history.
|
||||||
|
|
||||||
## Warnings
|
## Warnings
|
||||||
|
|
||||||
### WR-01: D-07/D-19 ceilings ignore a scoped personal API token's scope
|
### WR-01: D-07/D-19 ceilings ignore a scoped personal API token's scope
|
||||||
@@ -179,6 +201,8 @@ Winter strips 0 on write, so the precondition needs rows written by SQL or other
|
|||||||
The first planned consumer is 19.7's frontend-link action, which binds a frontend account to a backend admin that MCP then acts as. Through the seam, any `backend.manage_users` holder could link their own frontend account to an administrator who outranks them, which is the account-takeover path D-19 was written to close. Every contributor would have to re-implement unexported guards it cannot reach.
|
The first planned consumer is 19.7's frontend-link action, which binds a frontend account to a backend admin that MCP then acts as. Through the seam, any `backend.manage_users` holder could link their own frontend account to an administrator who outranks them, which is the account-takeover path D-19 was written to close. Every contributor would have to re-implement unexported guards it cannot reach.
|
||||||
**Fix:** When compiling extensions whose target is `AdministratorsControllerID`, wrap each contributed `Run` (and `Applies`) so that `guardOutranked(ctx, reg, principal, record.(*BackendUser))` runs inside the action transaction before the contributor's `Run`. Alternatively, export a `cabana.GuardAdministratorWrite(ctx, record)` helper and document in `pact.AdminActionExtension` that it is required. Add a test with a contributed action on an outranking target that expects 403.
|
**Fix:** When compiling extensions whose target is `AdministratorsControllerID`, wrap each contributed `Run` (and `Applies`) so that `guardOutranked(ctx, reg, principal, record.(*BackendUser))` runs inside the action transaction before the contributor's `Run`. Alternatively, export a `cabana.GuardAdministratorWrite(ctx, record)` helper and document in `pact.AdminActionExtension` that it is required. Add a test with a contributed action on an outranking target that expects 403.
|
||||||
|
|
||||||
|
**Gap-closure (2026-10-22):** D-19 half addressed by plan 04 (`wrapAdministratorsContributedRun`). D-09 (last superuser on a contributed Run) and wrapping `Applies` were out of that plan's contract and are not re-filed. Finding kept for history.
|
||||||
|
|
||||||
## Info
|
## Info
|
||||||
|
|
||||||
### IN-01: Last-superuser guard silently depends on READ COMMITTED
|
### IN-01: Last-superuser guard silently depends on READ COMMITTED
|
||||||
@@ -219,6 +243,56 @@ The first planned consumer is 19.7's frontend-link action, which binds a fronten
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_Reviewed: 2026-10-22T10:34:44Z_
|
## Gap-closure pass (2026-10-22)
|
||||||
|
|
||||||
|
**Reviewed:** 2026-10-22T11:50:00Z
|
||||||
|
**Depth:** standard
|
||||||
|
**Plan:** 04 (commits `068adaa`, `cb460b6`, `e620860`, `6bd2506`)
|
||||||
|
**Files Reviewed:** 7
|
||||||
|
**Status:** clean
|
||||||
|
|
||||||
|
This pass reviewed only the plan-04 gap-closure sources at HEAD. It does not re-review the original 70-file phase.
|
||||||
|
|
||||||
|
### Gap-closure files
|
||||||
|
|
||||||
|
- `modules/cabana/backend_roles_admin.go` (`refuseCodeDeclaredCode`, `FormBeforeCreate`, `FormBeforeUpdate`)
|
||||||
|
- `modules/cabana/action_extensions.go` (`wrapAdministratorsContributedRun`, `compileActionExtensions` wrap)
|
||||||
|
- `modules/cabana/action_extensions_test.go`
|
||||||
|
- `modules/cabana/roles_postgres_test.go` (`TestRolesCodeDeclaredCodeRefused`)
|
||||||
|
- `modules/cabana/action_extensions_postgres_test.go` (`TestActionExtensionOutranked`)
|
||||||
|
- `modules/pact/capabilities.go` (`AdminActionExtension` comment)
|
||||||
|
- `scripts/check-phase19.6.1.sh` (`DOCKER_TESTS` names)
|
||||||
|
|
||||||
|
### Gap-closure summary
|
||||||
|
|
||||||
|
No new Critical, Warning, Info, or Nice-to-have findings.
|
||||||
|
|
||||||
|
`refuseCodeDeclaredCode` is called from create (stored code `""`) and update (stored code from the save transaction). It refuses when `submitted != stored` and `len(registry.rolePermissions(submitted)) > 0`, including for a superuser. Same-code saves of a system role still pass. `rolePermissions` is nil-safe. The 422 shape reuses `backend::lang.user.role.code_locked` on `code`. `guardOutranked` stays unexported.
|
||||||
|
|
||||||
|
`compileActionExtensions` wraps a contributed `Run` only when `ext.Controller == AdministratorsControllerID`, after `checkContributedAction` has required a non-nil `Run`. The wrapper type-asserts `*BackendUser`, fails closed without calling `next` on a bad record, then calls `guardOutranked` inside `CRUDService.RecordAction`'s transaction. `lifecycleFailure` / `actionFailure` pass `*ForbiddenError` through as 403. Other controller IDs are stored unchanged. `Applies` is not wrapped, as the plan required.
|
||||||
|
|
||||||
|
The boot smoke now supplies a superuser principal so the wrap's `actsAsSuperuser` short-circuit avoids needing `TxFromContext` in a unit test. The Postgres tests cover the HTTP paths: create/rename onto a unique code-declared code with no row (editor and superuser), a unique non-declared create, a same-code system-role save, an outranking contributed `acme_ping` (403, spy empty), built-in deactivate still 403, and a covered target still 200 inside the transaction. Both names are in `DOCKER_TESTS` and not in `SHORT_TESTS`. The `AdminActionExtension` comment states that cabana, not the contributor, applies `user.outranked`. No consuming-application names, no new dependency, YAML parser unchanged.
|
||||||
|
|
||||||
|
Prior findings WR-01, WR-02, and IN-01..IN-06 were out of this pass. WR-03's D-09 clause (contributed Run still does not call `guardLastSuperuser`) remains as originally written; a non-superuser still cannot load a superuser row (`scopeAdministrators` → 404), so that leftover is the superuser-on-superuser path the plan did not take.
|
||||||
|
|
||||||
|
### Gap-closure Critical Issues
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Gap-closure Warnings
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Gap-closure Info
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
### Gap-closure Nice-to-have
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Reviewed: 2026-10-22T10:34:44Z (original); 2026-10-22T11:50:00Z (gap-closure plan 04)_
|
||||||
_Reviewer: Claude (msd-code-reviewer)_
|
_Reviewer: Claude (msd-code-reviewer)_
|
||||||
_Depth: standard_
|
_Depth: standard_
|
||||||
|
|||||||
Reference in New Issue
Block a user