Decide guest grants explicitly instead of by permission name

guest_grantable_permissions() opened any permission whose name ended
'.view' or '.read' to the guest role, on the reasoning that reading is
the safe default. That is true of a permission gating a list of asset
numbers and false of one gating bytes, and a suffix cannot tell them
apart. Twenty-four of eighty-five qualified and none had been decided:

  applications.view  reached the SYSTEM-context PowerShell bodies and
                     the installer binaries pushed to bays
  backups.view       returned the full payload that the separate
                     backups.download permission exists to protect
  assets.view        gated a global search that queries the staff
                     directory, USB history and network inventory with
                     no per-domain check of its own
  audit.view         usernames, addresses, the before/after of every
  settings.view      change, and every setting row - both admin
                     category, opened by naming alone
  users.view         gates nothing at all; list_users is
                     require_role('admin')

Every entry is now declared, by CORE_GUEST_GRANTABLE or by the plugin
that owns the permission, having read what its routes actually return.
Adding one is a code change, which is the point: it puts the decision
in a diff rather than in a suffix.

The two routes serving script bodies and installer packages move to a
new applications.download permission, which is not grantable, so a site
can still open the catalogue metadata to its floor without the bytes
going with it.

The declared set preserves the grants this site had already chosen:
applications.view, computers.view, endpoints.view, reports.view,
tools.secrets, warranty.view.

Two known gaps are recorded on the hooks that expose them rather than
silently carried: the computer serializer publishes every column plus
resolved rdp:// and vnc:// links, and the per-asset endpoint panels are
keyed on a small integer so walking ids reconstructs much of the roster
that endpoints.fleet exists to protect. Neither is widened here.

A shipped test was granting guests audit.view and passing.

Scope: product
(cherry picked from commit 347402896c)
This commit is contained in:
cproudlock
2026-08-31 07:01:52 -04:00
parent 5f27f81e88
commit 85e8a7b877
10 changed files with 192 additions and 41 deletions

View File

@@ -128,6 +128,25 @@ ADR-007 and ADR-002.
### Fixed
- **A permission became grantable to anonymous visitors because of how it was
spelled.** `guest_grantable_permissions()` opened any permission whose name
ended `.view` or `.read` to the guest role, on the reasoning that reading is
the safe default. That is true of a permission gating a list of asset numbers
and false of one gating bytes, and a suffix cannot tell them apart. Twenty-four
of eighty-five qualified, and nobody had decided any of them:
`applications.view` reached the SYSTEM-context PowerShell bodies and the
installer binaries a site pushes to its bays; `backups.view` returned the full
payload that the separate `backups.download` permission exists to protect;
`assets.view` gated a global search that queries the staff directory, the USB
history and the network inventory with no per-domain check of its own;
`audit.view` and `settings.view` are admin-category and were opened by naming
alone. Every entry is now DECLARED, by core or by the plugin that owns it,
having read what its routes actually return. Adding one is a code change,
which is the point - it puts the decision in a diff rather than in a suffix.
The two routes serving script bodies and installer packages moved to a new
`applications.download` permission, which is not grantable, so the catalogue
metadata can still be opened to a site's floor without the bytes going with it.
- **The published site's API documentation page had no spec to show.**
`shopdb/core/api/docs.py` reads `docs/openapi.json` at runtime to serve
`/api/docs`, and publication excluded the whole of `docs/` because the prose