Bring the response document onto the wiki with the rest
It was the only security write-up still living in the repository, at
docs/security-review-2026-08.md — which is why the link Ed sent the reporter had
/blob/ in it. There was never a round-one wiki page; the wiki's whole history
only ever held the round-two and round-three pages.
Two things in it had rotted since it was sent, and both are now fixed here and
flagged in place rather than silently corrected:
- it linked to security/findings-2026-08, a branch since merged and deleted
- all eight commit ids in its table were pre-rebase and are orphaned; the
table now carries the ids that exist on main, with a note that F5 sits out
of sequence because it reached main as a cherry-pick
The page opens by saying what it is — a record of what was said at the time,
not a live document — and points at the round pages for the current position,
because its own Outstanding section is now historical and mostly closed.
Linked from Security.md, the round-two developer guide and the sidebar.
Correct the outstanding count, and add the guard that was missed
Ed asked whether the Security page's summary — "two significant items remain
open" — was accurate. It was not. Checking it turned up a third: a fail-open
access guard in the task service, the last of the round-two list's "S3's
cousins", now fixed in 980b0939.
Both round-three pages gain the finding, the plain-English one as item 8 and the
developer guide as 6b with the lock-wait reproduction. Security.md now lists the
two genuinely open items separately from the two unscheduled features, rather
than rolling everything into one sentence ending "and the rest" — which was the
phrasing that let a miscount hide.
Document round three of the security work
Two new pages for the nine findings deferred when round two merged, following
the same pair as before: plain English for an operator, and a developer guide
that gives each finding its risk, a reproducible proof of concept, the
mitigation and why that mitigation actually closes it.
The existing round-two pages now say what has been superseded and what has not,
rather than leaving a reader to work out that their outstanding sections are
partly historical. Security.md carries both new links, and the sidebar lists
all four.
Care taken to keep the same framing Erlend asked for: the outstanding sections
lead, they name CSRF and lms/content as open in main today, and the round-three
guide records where a mitigation is conditional rather than absolute — the CSP
layer on the branding directory is skipped entirely without mod_headers, which
was measured on a stock WAMP rather than assumed.
Mark the security work merged, and keep the open findings visible
Erlend signed the branch off on 2026-08-12 and it has fast-forwarded into
main at 86f8d257, so the "awaiting sign-off" and "not yet merged" banners on
both hardening pages were wrong.
His one substantive request was about framing rather than code: the deferred
items must not read as closed. S2 is four live cross-tenant bugs reachable in
main today, not a hardening backlog. Both pages now say that plainly, and the
Security page — which described none of this and was the page an evaluator
would actually land on — now carries the disclosure history and points at the
outstanding list.
Also corrects the default-admin bullet on Security: the published password is
still admin/freeitsm, but since S5 the account is seeded with
must_change_password so it cannot survive first sign-in, and Database
Verification flags older installs still using it.
Absorb the README's deep-dive content ahead of the README slim-down
The project README is being reduced to a landing page; every fact it
held that the wiki lacked is merged here first (615 lines across 33
pages): the full email-threading & reply-flow design onto Tickets,
directory layout + module-page pattern + file-locations reference onto
Architecture, per-endpoint tables onto API Reference, MFA login flow +
LDAP subsection + injection/audit notes onto Security, the icon library
and editor internals onto Network Mapper, Branding + landing-page and
help/search internals + debug-tool depth onto System, planned objects
onto CMDB, custody/locations/warranty onto Assets, and more.
Also corrects stale claims found during the audit: the Knowledge
audience table still said customer-visible articles had no reader
(the portal Knowledge section shipped), a pre-#903 Portal-Privacy
ambiguity row, the Workflows trigger-wiring section (catalogue spans
every module now), the superseded System module-access bullet, the
Multi-Tenancy users page marked "Planned" for a shipped feature,
and the locale count (21 languages incl. Ukrainian, not 20).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RBAC wiki: verified against the code, and two stale claims corrected
Checked every claim the Roles pages make against what the code actually does —
not by reading the wiki back to myself, but by generating the truth from
capabilities.php / the manifests and comparing:
16 modules ✓ 76 capabilities ✓ 143 guarded endpoints ✓
every module has a <module>.manage umbrella ✓
the umbrella satisfies every capability in its module ✓
9 personal-preference tabs declare no capability ✓
an undefined key grants nothing ✓
module 'system' resolves to is_admin ✓
the registry is derived from the manifests ✓
every per-module capability count and every 'sensitive' badge ✓
Two things were stale, both now fixed:
1. The setting-key list in settings_keys.php was described as "a residual list of
modules not yet converted, which shrinks to nothing". It doesn't: every module
IS converted, and what remains is the System module's keys — permanently, by
design, because System is deliberately administrator-only. Corrected in the
wiki and in the code comment.
2. The "guard writes, never reads" rule appeared WITHOUT its credentials exception
on both Roles & Permissions and Security. Only the dev guide carried it. Since
that exception exists precisely because the rule was applied too comfortably —
get_mailboxes was a "read", and it shipped the plaintext OAuth secret to every
analyst — stating the rule without it is the dangerous half.
Also documented two manifest features that had gone in late and never been written
up: a tab may carry a plain 'label' instead of 'label_key' where a module isn't
translated, and renderSettingsTabBar() takes the tab-switcher's function name
(Problem Management's is pmsTab(), and its panels are id="tab-x" not "x-tab").
A converter that assumes the conventions renders a tab bar whose tabs open nothing.
Plus the full field reference for a manifest tab, and the table of shared endpoints
that need per-key authorisation — a recurring shape, not a one-off.
RBAC: the manifest is now the single declaration, and document D005
Roles — Developer Guide: the recipe changed. The capability registry, the module
list and the setting-key map are all DERIVED from <module>/settings/manifest.php
now, so adding a capability is a constant plus a manifest entry — there is no
capRegistry() to hand-edit and no settings_keys.php row to promote. Calls out the
one seam derivation doesn't cross (a Cap:: constant no manifest claims, which is
a capability nobody can hold — so any guard using it 403s everyone but admins,
permanently and invisibly to the admin who bypasses the check).
Adds a section on D005, the endpoint audit, and the discipline behind it: type
safety catches a MISSPELLED permission, nothing catches one nobody wrote. Lists
the five holes that were each found by hand, by accident. And the rule that keeps
the tool useful — if you add an auth mechanism, teach it, because its first run
gave five criticals of which four were false alarms, and a scanner that cries
wolf is ignored within a week.
Security: adds the auditing section so the tool is discoverable from the security
page, not just the developer guide.
RBAC: bring the wiki up to date with per-tab capabilities
Both Roles pages predated the per-tab decision and described a system that no
longer exists: rbacCapabilities() (now capRegistry), bare-string guards (now
Cap:: constants), "one manage per module, split later" (we went per-tab), and a
rename that orphans grants and needs a data migration (there's an alias map now).
Neither mentioned the settings manifest, the shared-endpoint key ownership, the
sensitive badge, the umbrella, or the security fix.
Roles & Permissions — rewritten around the per-tab model: why a tab is the unit
(vCenter credentials sit next to a lookup list), the umbrella, sensitive badging,
and a full "how it's enforced" section covering all three mechanisms — panels not
rendered rather than hidden, hard guards on every write endpoint, and per-KEY
authorisation on the shared settings endpoint (including the #829 hole it closed).
Plus what is deliberately NOT guarded, and why gating a read breaks the module.
Roles — Developer Guide — rewritten as the extension guide: why capabilities are
constants (a mistyped string 403s silently and is invisible to admins, who bypass
the check — so the typo becomes a privilege escalation), adding/renaming/removing
a capability, and the full six-step recipe for converting a module, worked against
Asset Management. Now carries the traps that actually bit: a get_* endpoint that
isn't a read (get_vcenter performs the sync and Servers calls it), endpoints with
no guard at all, and the reminder that converting a module is an audit of it.
Plus the three-actor HTTP test matrix and a checklist.
Security — the Authorisation section was wrong, not just stale: it claimed the
System module "cannot be disabled" and is always accessible to active analysts,
which stopped being true when is_admin shipped. Rewritten as the three layers,
each with its gate and its default, and the note that hiding a button is never
the control.
Raising the PHP floor — made the constants-to-enums bridge concrete rather than
asserted. Shows the actual migration: call sites are a symbol rename an IDE can
do and the compiler verifies (a missed one FATALS; you cannot half-finish it),
against hunting 200 string literals where a miss keeps working and a typo 403s
in silence. The real work is confined to one file. Also states the honest
caveat: constants make the typo loud, an enum makes the wrong thing impossible.
Add AI Providers page (shared provider layer + OpenRouter); refresh AI references across wiki
Add SSO/OIDC page + Keycloak (Docker) setup subpage
Initial wiki: home, 18 module pages, installation, architecture, security, API reference, browser extension, sidebar