Skip to content

fix(control-panel): deduplicate identical access-scope computation in control-panel-roles.ts #8373

Description

@JSONbored

Context

src/services/control-panel-roles.ts has two exported builders, buildControlPanelAccessScope (lines
~74-108) and buildControlPanelRoleSummary (lines ~110-142), that independently compute the SAME
access-scope facts from the SAME inputs using byte-identical logic:

  • installedReposargs.repositories.filter((repo) => repo.isInstalled)
  • accountInstallations — installations for this login that are NOT suspended
  • accountInstallationIds — the Set of those installations' ids
  • suspendedAccountInstallationIds — installations for this login that ARE suspended
  • ownedInstalledRepos — installed repos owned by this login or under one of its non-suspended
    installations, explicitly excluding any repo under a suspended installation
  • maintainerRepos — repos where this login has a maintainer-association PR, restricted to installed repos

Both blocks carry the identical inline comment: // A suspended installation revokes the App's access, so a repo under a suspended account installation must not grant control-panel scope — including via the owner-match branch, which previously ignored suspendedAt (#953). This is the fix for closed issue #953
("suspending the App does not revoke control-panel access to your own repos") — a real, previously-shipped
security bug in exactly this access-scope computation.

Because the fix for #953 was applied identically to two separate, independent copies of the same logic
instead of to one shared function, this file now carries the exact structural precondition for the "fix
applied to one place but not its structurally identical sibling" bug class: any future change to this
access-scope logic (a new eligibility rule, a new suspension state, an additional installation field) has
to be found and applied twice, in two functions in the same file, with nothing enforcing that both stay in
sync. canLoginAccessRepo and canWatchRepo (same file) both depend on
buildControlPanelAccessScope/loadControlPanelAccessScope's output for private-repo access decisions, so
a future silent divergence here is not a cosmetic issue — it is the same class of bug #953 already was.

Requirements

This is a pure, behavior-preserving refactor. It must not change what either function returns for any
existing input.
The scope is exclusively: extract the identical computation into one shared internal
helper, and have both existing functions call it.

  1. Add a private helper (e.g. resolveAccountRepoScope) in src/services/control-panel-roles.ts that takes
    the same inputs the duplicated blocks use (repositories, installations, login) and returns
    { installedRepos, ownedInstalledRepos, maintainerRepos, accountInstallations, accountInstallationIds, suspendedAccountInstallationIds } (or an equivalent shape covering everything both call sites currently
    derive from the duplicated block) — including pullRequests as an input where maintainerRepos
    depends on it.
  2. Update buildControlPanelAccessScope to call this helper instead of its own copy of the logic, and use
    the returned values to build its existing ControlPanelAccessScope return shape exactly as today.
  3. Update buildControlPanelRoleSummary to call the same helper instead of its own copy, and use the
    returned values to build its existing ControlPanelRoleSummary return shape exactly as today.
  4. Keep the #953 explanatory comment (suspended-installation exclusion) attached to the ONE shared
    computation, not duplicated.
  5. Do not change canLoginAccessRepo, canWatchRepo, loadControlPanelAccessScope,
    loadControlPanelRoleSummary, buildStaticControlPanelRoleSummary, buildRoleCards, or any other
    function in this file beyond routing the two builders through the new shared helper.
  6. Do not change the exported RoleSummaryInputs, ControlPanelAccessScope, or ControlPanelRoleSummary
    types.

Deliverables

  • One shared internal helper in src/services/control-panel-roles.ts replacing the two duplicated
    computation blocks.
  • buildControlPanelAccessScope and buildControlPanelRoleSummary both call the shared helper; neither
    contains its own copy of the installed/owned/maintainer/suspended computation afterward.

Test Coverage Requirements

99%+ Codecov patch coverage, branch-counted, on every changed line in src/**. Because this must be a
zero-behavior-change refactor:

  • Every existing test in the repo's control-panel-roles test file must continue to pass UNMODIFIED (no
    test assertions should need to change — if one does, that indicates a behavior change that is out of
    scope for this issue).
  • Add one explicit new regression test that calls BOTH buildControlPanelAccessScope and
    buildControlPanelRoleSummary with the SAME input fixture — a login whose installation is suspended but
    who still owns the installed repo (the exact [Bug]: suspending the App does not revoke control-panel access to your own repos #953 scenario) — and asserts both functions agree that the
    repo is excluded from scope/roles. This pins the shared-helper invariant this issue exists to protect:
    the two call sites can no longer silently diverge because they share one implementation.
  • Confirm branch coverage on the shared helper: suspended vs non-suspended installation, owner-match vs
    installation-id-match vs neither, and a maintainer-association PR on a non-installed repo (excluded).

Expected Outcome

src/services/control-panel-roles.ts computes account access scope in exactly one place. A future change to
this logic (a new eligibility rule, an additional suspension state, a new installation field) only needs to
be made once, and buildControlPanelAccessScope and buildControlPanelRoleSummary can never silently
diverge the way two independent copies of the same security-relevant logic otherwise could.

Links & Resources

  • src/services/control-panel-roles.tsbuildControlPanelAccessScope, buildControlPanelRoleSummary
    (the two duplicated blocks).
  • Closed issue [Bug]: suspending the App does not revoke control-panel access to your own repos #953 — "suspending the App does not revoke control-panel access to your own repos", the
    security bug already fixed identically in both places; this issue prevents a future regression of the
    same shape by removing the duplication itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions