-
-
Notifications
You must be signed in to change notification settings - Fork 27
Module Access Reads Were Open
Security Β· Present in 1.0.0 β 2.10.1 Β· Fixed in 2.10.2 (#2082β#2084) Β· Found while building Report Packs
π οΈ The rule this changed, for anyone adding an endpoint: Module Access β Developer Guide
Module access lets an administrator decide which modules each team can use β a service desk team with Tickets but not Contracts, say. The restriction hid the screens, refused direct visits to a module's pages, and refused every change.
But most of the requests that read a module's data only checked that somebody was signed in. A signed-in analyst who knew β or guessed β the address of one of those requests could read the data behind a module they were never given:
- ticket conversations, notes, attachments and audit trails
- contracts, suppliers and supplier contacts
- asset lists, servers and who holds each device
- software inventories, licences and the software API keys β the keys the inventory agents and the Watchtower browser extension sign in with
- change and problem records, workflows, process maps and network diagrams
- the system log
They stayed inside the companies they could already access, so this was never a leak between companies. It needed somebody already signed in to FreeITSM, and only mattered on installs that restrict modules.
-
Some secret settings were readable by any signed-in analyst. The settings request decrypted every
*_password,*_secret,*_tokenand*_api_keysetting but masked only a fixed list, so the satisfaction-survey signing key (csat_token_secret) and the five cron tokens went out in plain text. With the survey key someone could submit ratings on a customer's behalf; with a cron token, start that scheduled job over the web. Passwords and AI keys were always masked. - Four Tickets screens had no access check of their own β CSAT, Dashboard, the Widget Library and the Triage queue. The shared header checks sign-in, but only after the page has started sending, so its redirect could not work and the screens rendered β empty β to somebody not signed in.
When module access was enforced (#30, July 2026, before 1.0.0), the design deliberately guarded pages and writes and left reads open:
"Guard the module's write endpoints. Do not blanket-guard reads that other modules/flows depend on β e.g.
get_analysts,get_tenants, branding, the login page'sget_sso_providers."
That reasoning was sound for shared reads β the analyst picker is used in six modules, and guarding it with one of them breaks the other five. But it was applied to all reads, including the great majority used by only one module. The developer guide taught the same rule, so every endpoint written since followed it.
All 188 unguarded analyst reads were classified by their real callers β not by their folder: grep the file name, check which API_BASE each hit uses, and for a shared assets/js/ file find which pages load it. Four agents worked through them in parallel and every verdict was checked against the code before it was applied.
| Verdict | Endpoints | Guard |
|---|---|---|
| Used by one module | 144 | requireModuleAccessJson('<module>') |
| Shared by several | 12 |
requireAnyModuleAccessJson([...]) naming every module that calls it |
| Administrators only | 2 (+4 System reads) |
requireModuleAccessJson('system') β analystIsAdmin()
|
| Must stay open | 19 | Listed with the reason: token feeds, webhooks, your own account, the documents panel and global search (both filter each result themselves) |
| Nothing calls it | 6 | Deleted β including a debug page that printed the request and an attachment's file path |
Plus:
- Settings: a secret by name that is not on the mask list is no longer sent at all, and the request needs Assets, Software, Tickets or System (its four callers).
- Companies: the full list (every company and its email domains) needs System or Tickets; everybody else already asks for the companies I can access.
- Saved table views check the module the table belongs to.
-
The four Tickets pages got
requireModuleAccess('tickets').
Writes were checked too: every write was already guarded or exempt for a stated reason (sign-in, own account, documents check their parent record, the generic settings writer checks each key).
tests/module-access-coverage.php walks every file under api/ and fails on any endpoint with neither a guard nor an entry in its allow-list β and each entry carries its reason. It also fails if an allow-listed file has since gained a guard, or no longer exists. Run against 2.10.1 it fails on 167 endpoints and the four pages.
| File | Change |
|---|---|
157 files under api/
|
One guard line each, after the sign-in check |
api/settings/get_system_settings.php, includes/encryption.php
|
Unmasked secrets not sent; isSecretSettingName()
|
api/system/get_tenants.php |
The full list needs System or Tickets |
api/table-views/list.php, save.php
|
Guarded by the table's module |
tickets/csat/, tickets/dashboard/ (+ library.php), tickets/triage/
|
Page guard |
| 6 dead endpoints | Deleted |
tests/module-access-coverage.php |
New |
Every changed request was driven over HTTP as an analyst who has Assets, LMS and Tickets only:
- 158 / 158 answered as their guard says β allowed where it names Tickets or Assets, refused everywhere else, no PHP errors.
- Signed out, every one refused.
- The settings request no longer returned the six secrets, and still returned its other 188 settings.
- The control: the same run against 2.10.1 let that analyst into 91 endpoints for modules he does not have, sent him the survey key and cron tokens, and served the Triage page to a signed-out request.
- Upgrade. Every release from 1.0.0 to 2.10.1 is affected.
-
If an analyst you do not trust could have signed in before you upgraded, replace the secrets: delete the rows
csat_token_secret,sla_cron_token,webhook_cron_token,workflow_cron_token,domain_cron_tokenandintegration_cron_tokenfromsystem_settingsand run System β Database Verification, which creates new ones. Survey links already emailed stop working, and a scheduled job started by web address needs its new address (php scripts/cron_token.php --url). - If an analyst now sees "You do not have access to this feature" somewhere they used to work, their team is missing that module β give it in System β Teams β or tell me, if they should not need it for that screen.
- Module Access β Developer Guide β the rule, corrected
- Module Access Control
- Bugs resolved
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96