Skip to content

fix(roles): gate the sidenav pin list and #add-item on the admin role - #1603

Merged
KodeStar merged 1 commit into
linuxserver:2.xfrom
azuretek:fix/roles-mode-sidenav-leak
Sep 9, 2026
Merged

fix(roles): gate the sidenav pin list and #add-item on the admin role#1603
KodeStar merged 1 commit into
linuxserver:2.xfrom
azuretek:fix/roles-mode-sidenav-leak

Conversation

@azuretek

@azuretek azuretek commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #1602.

Problem

In roles mode the dashboard filters tiles by the caller's roles, but the sidenav pin list beside it is rendered for every visitor from all_apps, which carries no role filter. A non-admin visitor receives the title and id of every item in the database plus an items.pintoggle link for each. #add-item, which opens that list, is ungated as well, while #config-buttons twelve lines below it in the same template is already gated on $enable_auth_admin_controls.

Change

Two @if($enable_auth_admin_controls) gates, one around the .sidenav block and one around #add-item, plus tests.

Pinning is global item state in roles mode rather than a per-visitor preference, so the pin list belongs behind the same check as the other controls that write global state.

Outside roles mode this is a no-op: AppServiceProvider sets enable_auth_admin_controls to true unconditionally when auth_roles_enable is off, so the default single-user install renders exactly as before. There is a test for that.

I considered role-filtering all_apps instead and did not, for two reasons. It would still leave a non-admin able to unpin their own tiles for everyone, and it takes away an admin's ability to unpin an item outside their own roles, since the pin list is the only place that can be done. test_admin_keeps_the_pin_control_for_items_of_other_roles pins that down. I am happy to add the filter on top if you would rather have both.

Test

tests/Feature/DashRolesTest.php, 7 cases: the leak, the ungated control, a multi-role visitor, two admin control cases, and roles mode disabled.

On 2.x without the change, 4 of the 7 fail and the two admin control cases and the roles-disabled case pass:

   FAIL  Tests\Feature\DashRolesTest
  ⨯ sidenav pin list only lists items the visitor has a role for
  ⨯ sidenav pin list is empty of other roles items when the visitor has no…
  ⨯ pin item control is hidden from non admin roles
  ✓ admin role still sees every item and the pin control
  ✓ admin keeps the pin control for items of other roles
  ⨯ a visitor holding several roles sees all of them
  ✓ with roles disabled every item is still listed

  Not to contain: Unifi Controller
  at tests/Feature/DashRolesTest.php:97
  Tests:    4 failed, 3 passed

With the change, all 7 pass.

Suite and linter, php artisan test and ./vendor/bin/phpcs --report=summary:

2.x this branch
tests 152 passed, 1 skipped, 362 assertions 159 passed, 1 skipped, 392 assertions
phpcs 16 errors, 9 warnings, 8 files 16 errors, 9 warnings, 8 files

The 7 added tests are the whole difference. The phpcs findings are all pre-existing and unchanged.

Live verification

Built as an overlay on the stock linuxserver/heimdall image we run and A/B'd against a copy of our live config, same environment, same database, both containers up at once:

probe stock patched
as non-admin role, dashboard tiles 6 6
as non-admin role, sidenav pin-list entries 16 0
as non-admin role, items.pintoggle links 16 0
as non-admin role, #add-item 1 0
as admin role, dashboard tiles 7 7
as admin role, sidenav pin-list entries 16 16
as admin role, items.pintoggle links 16 16
as admin role, #add-item 1 1
as admin role, #config-buttons 1 1

The non-admin's own tiles are untouched and every admin value is identical.

In reverse-proxy roles mode the dashboard filters tiles by the caller's
roles, but the chrome around it does not. The sidenav pin list is built
from all_apps, which is not role-filtered, and it is rendered for every
visitor: a non-admin is served the title of every item in the database
plus an items.pintoggle link for each one, while the dashboard above it
correctly shows only their own. #add-item, which opens that list, is
rendered unconditionally too, even though #config-buttons beside it is
already gated on the admin role.

Pinning is global item state in roles mode rather than a per-visitor
preference, so the pin list belongs behind the same admin check as the
config buttons. Gate both.

Role-filtering all_apps instead was the other option and is not enough
on its own: it would leave a non-admin able to unpin their own tiles for
everyone, and it takes an admin's ability to unpin an item outside their
own roles. There is a test covering that.

@KodeStar KodeStar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally.

  • Agree with gating on the admin role rather than role-filtering all_apps: pinning is global item state, and filtering would silently drop an admin's ability to unpin items outside their own roles.
  • Removing the nav is safe for the front end: the #add-item / .close-sidenav handlers are delegated from #app, and $('#pinlist').on(...) is a no-op when the element is absent.
  • Outside roles mode enable_auth_admin_controls is unconditionally true, so the default install is unchanged; the roles-disabled test covers that.
  • Ran the full suite on the branch: 159 passed, 1 skipped, 392 assertions, phpcs unchanged.
  • Confirmed the new tests catch the leak: with the 2.x resources/views tree and only the test file applied, 4 of 7 fail; all 7 pass with the fix.
  • Merged together with #1605 locally: 163 passed, no conflicts.

One note for a follow-up rather than this PR: the pin/unpin/pintoggle routes and the item CRUD routes have no role check server-side in roles mode (CheckAllowed never looks at the roles header), so a non-admin who knows the URL can still toggle a pin directly. This PR closes the leak in the rendered page, which is what #1602 describes; route-level enforcement should be its own change.

@github-project-automation github-project-automation Bot moved this from PRs to PRs Approved in Issue & PR Tracker Sep 9, 2026
@KodeStar
KodeStar merged commit 9ad5864 into linuxserver:2.x Sep 9, 2026
2 checks passed
@LinuxServer-CI LinuxServer-CI moved this from PRs Approved to Done in Issue & PR Tracker Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Roles mode serves every item's title and pin link to visitors holding no admin role

3 participants