Repository navigation
Developer Tests Knowledge
Part of Developer Tests. These are the suites where a wrong answer looks like a right one, so nearly every assertion here is paired with a positive control.
That is the theme of the whole page and worth stating once: a permission filter that quietly does nothing returns a page full of plausible results, and a query that matches nothing looks identical to an honest "not in your tickets". So every "they could not see it" is followed by "β¦and this person, who should, still can". Without the second half the first proves nothing.
| Test | Needs |
|---|---|
knowledge-visibility/run.php |
Database, some parts local HTTP |
knowledge-gaps/run.php |
Database (rolled back) |
search/run.php |
Database |
search-extract/run.php |
Nothing |
document-permissions.php |
Database (rolled back) |
document-search-permissions.php |
Database |
Whether one company's knowledge can reach another company's people. It is a runner: it executes the seven numbered harnesses beside it in order and reports which failed.
| Part | The question it asks |
|---|---|
01_webchat_scope.php |
Can one company's anonymous website visitor be answered out of another company's knowledge base? |
02_write_path.php |
Does KnowledgeService validate company and audience when writing? |
03_readers.php |
Does every analyst-facing surface respect the company the analyst has switched to? |
04_rest_api.php |
Does an API key scoped to one company see only that company's articles β but still the shared ones? |
05_lms.php |
Can the LMS build a lesson from an article it should not have? |
06_gap_analysis.php |
Does the assistant judge one company's tickets "already covered" by another's articles? |
07_acl.php |
Folders, the access list, and the two permission models. |
03_readers.php drives the real endpoints over HTTP with a forged session, as
a restricted analyst. That is the point β a scoped query proves nothing if the
endpoint in front of it never applies the scope.
04_rest_api.php has a trap worth knowing about. The second half is the hard
part: the generic apiKeyTenantFilter treats NULL as "the Default company's",
which would hide every shared article from a non-Default key. So the test
asserts both directions β not another company's, but yes to the shared ones.
05_lms.php makes the same argument the Forms audience test does: the picker
hiding a title is not enough, because it posts an id, and ai_author.php is gated
on LMS_MANAGE rather than on Knowledge. The real test is whether a guessed id
still yields a body.
06_gap_analysis.php documents a mismatch precisely: gapWindowSql() always
company-scoped the ticket side, while the article side had no tenant filter
at all β the two halves of one comparison disagreeing about whose data was in
scope.
07_acl.php runs inside one transaction that is always rolled back, so it
is safe against a live database.
_bootstrap.php beside them is a shared helper, not a harness β it is included by
each part and has no assertions of its own.
php tests/knowledge-visibility/run.php
Or one part on its own, e.g. php tests/knowledge-visibility/07_acl.php.
All Knowledge visibility harnesses passed. and exit 0. Otherwise it prints
FAILED: and the harness names, and exits 1.
Any leak here is a live cross-company data leak. Fix it at the query, not at the surface that happened to be tested β the reason there are seven harnesses is that there are seven ways in.
The knowledge gap assistant's clustering. The interesting behaviour is emergent: does a pile of real tickets actually collapse into the right questions, and do the one-offs stay out? You cannot answer that by reading the code β only by feeding it a pile whose right answer you already know.
Everything runs inside one transaction which is always rolled back, so it is safe against a live database.
It runs in "wording" mode by default, which needs no OpenAI key and spends nothing. The clustering logic under test is identical; only the similarity function differs.
php tests/knowledge-gaps/run.php
If it reports the write-up schema is not ready, run System β Database Verification.
Clustering has changed shape. Check the control assertions first: if those failed, nothing clustered and the tuning is broken rather than merely different.
Three things, all of which can be wrong while looking right:
- Query translation. What a person types is not what MySQL is asked. Terms too short to be indexed must be dropped, because requiring one in boolean mode makes the whole query match nothing β the difference between "search works" and "search mysteriously returns nothing for that phrase".
- Scope goes into the query. Every "it was excluded" assertion has a twin showing the same row is returned once the predicate is relaxed. Without that twin, a filter matching nothing at all would pass.
- The result shape. Hits collapse to their ticket and report which parts matched, because a user thinks in tickets β one ticket with the term in four replies must not flood the page.
Writes a handful of rows with a reserved source_type and deletes them in a
finally block. MATCH ... AGAINST.
php tests/search/run.php
- Translation β a search phrase now returns nothing. Look at the minimum indexed word length and what the translator does with short terms.
- A scope twin β if the exclusion passes but its twin fails, the filter is excluding everything, not just what it should.
Attachment text extraction: reading words out of an uploaded file so they can be searched.
Builds real files in a temporary directory and reads them back β including a
minimal but genuinely valid .docx, which is a zip with word/document.xml
inside. No database, no HTTP: it exercises includes/search/extract.php alone,
because the risky part of attachment indexing is the file handling, not the
SQL.
π The hostile cases matter most. An attachment arrives from anyone who can email the service desk, so "a zip bomb is refused" is a more useful assertion than "a Word document is read".
php tests/search-extract/run.php
25 assertions, no fixtures needed β this is a good first test to run on a fresh checkout.
A refused-hostile-file assertion going red means the extractor will now try to process something it should reject. That is a denial-of-service risk on a queue anyone can post to.
A document is visible if and only if you can see something it is attached to.
π The same question is asked twice by the product, and the two must always agree:
-
documentVisibilityClause()β the set, used by search -
documentCanView()β the row, used by download
A document you cannot find but can download is the whole bug this design exists to prevent.
Writes to scratch records inside a transaction and rolls back. Every "cannot see" is followed by the same analyst being granted the parent and then seeing the document.
php tests/document-permissions.php
If the set and the row disagree, say so explicitly in your fix β decide which is right and change the other. Do not fix only the one the test named.
That search does not return a document you cannot see.
Searches the real corpus as a real analyst. Every denial is paired with a positive control: the same analyst, the same document, the module granted. A test that only proves absence passes just as happily when the index is empty, the word is below the full-text minimum, or the query is broken.
php tests/document-search-permissions.php
A denial going red is a leak through search β someone can see the existence and title of a document they have no route to. Treat as blocking. A control going red means documents have stopped being indexed at all.
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: Projects
- β³ π 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
- π Projects
-
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
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ 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