Skip to content

Commercial packages must require Global Admin to access/sync; free packages sync without special permission #830

Closed

Description

@rbuergi

Requirement (maintainer, 2026-08-06)

The other plugins need special permission of Global Admin to be able to access commercial packages ⇒ only free ones can be synced without permission.

Current state

  • PluginCatalogSettingsTab gates the whole catalog surface on hub.IsGlobalAdmin(viewerId) (PluginCatalogSettingsTab.cs:65) — all-or-nothing, and about visibility, not about the sync/install action.
  • Package carries a Price (Package.cs), and NodeRepoPackageSource reads it, but no code path distinguishes free from commercial when installing or auto-updating.
  • PluginUpdateWatcher's auto-update applies a delta install with no price/permission check at all — a commercial package that is already an install record keeps updating itself regardless of who (if anyone) is entitled.

So today the boundary is drawn in the wrong place: the UI is admin-only, while the machine path is ungated.

Proposed rule

Package Access / sync
Free (Price null or 0) syncable and auto-updatable with no special permission
Commercial (Price > 0) requires Global Admin on the installing instance to access, install, or auto-update

Enforce it at the action, not just the surface:

  1. PackageInstaller (and whatever the Store's provision funnel calls) rejects install of a priced package unless the acting principal is a global admin — under the post-fix(plugin-catalog): installs run as SYSTEM — the clicking user owns nothing mid-install #820 "the click authorizes, the SYSTEM executes" pattern, the authorizing principal is the one that must be admin.
  2. PluginUpdateWatcher skips (and surfaces) priced packages whose install record was not admin-authorized, rather than silently updating them.
  3. The registry endpoint (/api/plugins) should not enumerate commercial package payloads to unauthenticated callers.

Tests

Production-shape (post-#804/#805/#825, as in PluginsPartitionReadableTest): a signed-in non-admin installs a free package → succeeds; the same principal installs a priced package → denied; a global admin installs the priced package → succeeds; auto-update applies to the free package and skips the priced one for a non-admin-authorized record.

Context

Found while wiring MeshWeaver.Reinsurance in as a catalog source on memex-cloud (its modules are unpriced; the education courses are priced at CHF 900, so both classes now sit in the same catalog on one instance).

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions