security(dci): stop DCI Administrator group from granting system admin - #327
Merged
gonzalesedwin1123 merged 6 commits intoJul 28, 2026
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 19.0-staging-sec-batch1 #327 +/- ##
==========================================================
Coverage ? 78.18%
==========================================================
Files ? 225
Lines ? 14929
Branches ? 0
==========================================================
Hits ? 11672
Misses ? 3257
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Member
Author
TDD red: the group currently implies base.group_system, escalating any DCI Administrator to full Settings/System admin. New tests assert the implication is gone (direct and transitive), that spp_admin implies the group instead, and that the 19.0.2.0.2 migration strips the link from existing databases.
spp_dci.group_dci_admin listed base.group_system in implied_ids. In Odoo, implied_ids grants the implied groups to members - it is not a membership precondition - so assigning the scoped DCI/PII visibility role silently made the user a full Settings/System administrator. - drop the base.group_system link from the group (noupdate record) - link the group into spp_security.group_spp_admin.implied_ids instead, per the extension pattern documented in groups_admin.xml, so OpenSPP and system admins keep visibility of the gated PII fields - ship migrations/19.0.2.0.2/post-migration.py to strip the unsafe link from databases installed from the 2026.07 release, where the noupdate flag blocks the XML fix; membership is a computed transitive closure, so removal revokes the escalation immediately - bump to 19.0.2.0.2 with HISTORY fragment
Review follow-ups: the noupdate record keeps the old comment (which documented the inverted implied_ids mental model) on upgraded databases, so the migration now refreshes it; the migration test captures the operator-facing warning with assertLogs so test output stays clean and the message is pinned.
Staff-review polish (no behaviour change to the escalation fix): - the migration now refreshes the stale group comment even on databases where the base.group_system link was already removed manually, and only logs/counts when it actually unlinks - the migration test simulates the released stale comment and asserts a second run is a silent no-op
gonzalesedwin1123
force-pushed
the
security-dci-admin-group-implies-system
branch
from
July 28, 2026 07:05
33cf136 to
d171acb
Compare
This was referenced Jul 28, 2026
gonzalesedwin1123
marked this pull request as ready for review
July 28, 2026 08:00
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
gonzalesedwin1123
added a commit
that referenced
this pull request
Aug 13, 2026
…g keys, GRM rule ACL (#327, #329, #265, #266) (#399) * security(dci): stop DCI Administrator group from granting system admin (#327) Reviewed head: d171acb * security(key_management): stop Key Management Admin group from granting system admin (#329) Reviewed head: 180c7fc * security(oauth): restrict OAuth signing-key settings to system admins (#265) Reviewed head: e9dc451 * security(grm): restrict GRM automation rules to GRM staff (drop portal write/create) (#266) Reviewed head: bebd609 * fix(spp_oauth): restore @api.model on default_get, field-gate signing keys (#399) Review response on PR #399 (findings 1-2): - default_get() override lacked @api.model. call_kw reads the dispatch marker off the most-derived method, so every external RPC call to res.config.settings.default_get crashed with a TypeError once this module was installed (web Settings UI unaffected - it resolves defaults in-process). Regression-tested through the real dispatcher. - oauth_priv_key/oauth_pub_key now carry groups="base.group_system": a settings save by an unauthorized principal fails closed with AccessError instead of silently deleting the stored parameters via set_param(False). The default_get pop stays - field groups are not enforced in default_get. Defence-in-depth: core's ACL already limits the model to system admins; the gate holds if that is ever re-widened. * fix(spp_dci): make DCI PII visibility opt-in per admin, harden migration (#399) Review response on PR #399 (findings 3, 7, 8): - Drop the spp_security.group_spp_admin -> group_dci_admin implication: PII rendering is now opt-in per administrator via an explicit, reviewable Access Rights grant instead of an automatic side effect of adminship, mirroring the deliberate key-custody separation in spp_key_management. The pinning test is inverted to guard against a consistency sweep reintroducing the link. The link never shipped in any release and no production DB is built from this staging branch, so no unlink migration is needed; the upgrade-test seed DB is rebuilt for the refreshed evidence run. - Migration comment refresh now strips only the stale sentence ("Members must already be system administrators") so operator rewrites and extensions survive, honouring noupdate. - Migration warning counts all_user_ids (transitive membership), matching the spp_key_management migration's counting basis - safe now that nothing implies the group. - Declare the spp_security dependency explicitly: the security tests pin design decisions against spp_security.group_spp_admin, and the dependency was previously only transitive via spp_registry. * fix(spp_key_management): correct encrypted_key wrap comment, add app icon (#399) Review response on PR #399 (findings 5, 6): - The encrypted_key field comment claimed the value is always KMS ciphertext; for the database provider it is wrapped by the master KEK, which in zero-config setups is derived from database.uuid. State both provider families accurately. The field gate itself is unchanged (the KMS-provider sudo rework is tracked in #330/#331). - The top-level Key Management menu now carries the module icon - every other top-level app menu sets web_icon; without it the app switcher shows a generic placeholder tile. Pinned in the existing load_menus test via web_icon_data. * test(spp_grm_cel): internal manager fixture, escalation counter regression (#399) Review response on PR #399 (findings 10, 11, 12): - The GRM manager fixture now links base.group_user: the spp_grm group chain carries no user-type group, so the manager was created as a share=True external principal and the staff-retention tests proved less than intended. - New regression test: a caller with read-only rule access gets a fully applied escalation with the counter incremented. The actor is a portal user - the population the sudo'd counter write serves in practice (officers/managers hold rule write; internal base users and GRM viewers are read-only too but do not drive ticket flow). Reverting the 19.0.2.0.1 sudo fix now fails loudly instead of passing the suite. - Portal-read docstrings reworded: the read row is a current implementation dependency (rule evaluation runs as the acting user, reachable by portal via direct-RPC ticket create/stage-write), not a security requirement. Tightening it - sudo evaluation, dropping the read rows, and the missing portal record rule on spp.grm.ticket - is tracked in #413. - HISTORY: scope the half-way claim to the counter/chatter state this fix addresses; notification/case partial failures are pre-existing. * docs: regenerate READMEs from CI's pinned generator (#399) Applied verbatim from the pre-commit CI run's printed diff (run 31679116024) - local regeneration is not byte-reproducible against CI's hook env, so CI output is the sole authority for generated files. * fix(spp_dci): put noupdate on the odoo root, drop deprecated data node (#399) With the group_spp_admin record removed, the noupdate <data> wrapper became the file's sole element, which oca-checks-odoo-module flags as xml-deprecated-data-node (the failing 'Checks for Odoo modules' CI hook). Same noupdate semantics, modern form.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
spp_dci.group_dci_admin(the group gating raw DCI payloads and PII fields in everyspp_dci_*view) was defined withimplied_idslinkingbase.group_system. In Odoo,implied_idsgrants the implied groups to members — it is not a membership precondition, though the record's own comment ("Members must already be system administrators") shows it was believed to be one. So assigning the scoped DCI/PII-visibility role silently made that user a full Settings/System administrator. The pinning test added with the group (test_dci_admin_group_implies_system) locked the escalation into CI. The commit that introduced this (a5826347) is in the released2026.07tag, so deployed databases carry the escalation.Fix
base.group_systemfromgroup_dci_admin.implied_idsand correct the record comment.group_dci_adminintospp_security.group_spp_admin.implied_ids— the exact extension pattern documented inspp_security/security/groups_admin.xml(base.group_system→group_spp_admin→ domain groups). System/OpenSPP admins keep seeing the gated PII fields; DCI Administrator grants nothing beyond itself.spp_securityis already inspp_dci's dependency closure (spp_dci → spp_registry → spp_security). The extension record sits outside thenoupdateblock so upgrades apply it.noupdate="1", so the XML fix never reaches installed DBs —migrations/19.0.2.0.2/post-migration.pystrips the link via the ORM and logs how many users held the group. Odoo 19 computes effective membership as a transitive closure ofimplied_ids(res_groups.py: all_implied_ids), so removing the link retroactively revokes system admin from affected users; there is no stored per-user expansion to clean.19.0.2.0.1 → 19.0.2.0.2+HISTORY.mdfragment.No changes needed in the five consumer modules (
spp_dci_client_crvs/_dr/_ibr/_sr,spp_dci_server): theirgroups="spp_dci.group_dci_admin"view gates and arch-assertion tests are unaffected.Tests (TDD — red commit first)
test_dci_admin_does_not_grant_system(replaces the escalation-pinning test):base.group_systemabsent fromimplied_idsandall_implied_ids(closure — blocks reintroduction through an intermediate group); behavioral check that a user granted the group does nothas_group('base.group_system').test_spp_admin_implies_dci_admin: admins keep PII visibility (closure + behavioral).test_migration_removes_escalation: recreates the released vulnerable state, runs the migration'smigrate(), asserts the link and its closure effects are gone.spp_dci22/22, plus consumer suitesspp_dci_client_crvs98,spp_dci_client_dr144,spp_dci_client_ibr103,spp_dci_client_sr134,spp_dci_server332 — all 0 failed. Lint (ruff/prettier/pylint-odoo/OCA checks) clean; security audit 0 errors / 0 warnings.Operational note for deployments
On
2026.07installs, anyone granted DCI Administrator has been a de-facto system admin since the grant. The migration revokes the privilege on upgrade, but operators should audit actions taken by those users while escalated (settings, user/group management, module installs) — removal does not undo what was done.Reviewed
Adversarial staff review: fix confirmed correct and complete for the stated vulnerability, verified against Odoo 19 source. Confirmed: the
<data noupdate="1">+ bare extension-record XML structure is standard and matches ~15 sibling modules; load order guaranteesspp_security.group_spp_adminexists; Odoo'sMigrationManagerruns the19.0.2.0.2script on a19.0.2.0.1 → 19.0.2.0.2upgrade (inclusive upper bound) and post-migration runs after the non-noupdate extension record is applied; Odoo 19 does not materialize implied groups intores_groups_users_rel, andres.groups.write()invalidates the registry group and ACL caches, so the ORM unlink revokes the escalation immediately; the group did not exist in v17openspp-modules, so no legacy materialized-membership residue is possible. Review polish applied: the migration also refreshes the record's stale comment (thenoupdateflag would otherwise preserve the misleading "must already be system administrators" text on upgraded DBs), and the migration test pins the operator-facing warning withassertLogs. One out-of-scope sibling finding was raised to the maintainers separately.Related
Sibling escalation of the same class (
implied_idslinkingbase.group_system), found by this PR's review: #329 (spp_key_management.group_key_admin). A repo-wide sweep found only these two instances; #334 tracks a CI guard to prevent a third.Merge order & release-note note
Per the cross-PR interaction analysis (2026-07-24,
internal/plans/security-prs-interaction-analysis.md): no hard ordering — every combination with the other open security PRs commutes. Soft preference: land this early (or in the same release as #336/#265) — until the implication is stripped, DCI admins still transitbase.group_systemand pass every new system-admin gate those PRs add, so this PR is what makes their exclusions effective.Release notes must state: DCI Administrator no longer grants system admin, and all DCI-server administration (bearer tokens, sender registries, subscriptions, server keys, the settings block) is
base.group_system-gated — deployments whose DCI operator held only this group must grantbase.group_systemexplicitly. #269/#326 add further system-only fields in the same window. The migration logs a warning, but upgrade-log warnings are easy to miss.