Repository navigation
Split DNS Developer Guide
Issue: #249, the last part of #243 (internal monitoring, slice 3) Β· Module: Domains Β· From: the release after 4.10.0 (changelog #2744)
This page explains split DNS from the beginning - what it is, why anyone wants it, what was decided and why - and then every rule in the code and where it lives. If you have never heard of split DNS, start at the top; if you only need the code, jump to section 4.
DNS is the internet's phone book. You ask it for a name - example.com - and it gives you a number - an IP address - that your computer then connects to.
Normally there is one phone book, and everybody gets the same answer.
Split DNS means there are two phone books. People outside your organisation ask the public phone book and get one answer. People inside your network ask your own, internal phone book and get a different answer for the same name.
A typical example:
Who asks for portal.example.com
|
Which phone book | Gets | Lands on |
|---|---|---|---|
| A customer at home | The public one | 203.0.113.10 |
The public website, on the internet |
| A member of staff in the office | Your internal one | 10.0.5.20 |
An internal server, inside the network |
Organisations do this on purpose: staff reach a faster internal copy, or an internal-only version of a system, without having to remember a different name.
Why it matters for monitoring: the two sides can break independently. The public website can be fine while the internal server staff are sent to has an expired certificate - or the other way round. If FreeITSM only watches one side, it misses half the picture.
Domains already understood the two kinds of name, through a setting called Scope (#203, 4.5.0):
| Scope | What FreeITSM does | What it never does |
|---|---|---|
| Public | Looks the name up the way the world does - public DNS, the domain registry, certificate logs - and gives it a security grade (A+ to F) | - |
| Internal | Asks this server's own DNS - the internal phone book - and gives it a health (Healthy, Problem...) | Sends the name anywhere outside the network |
The second column is the important one. An internal name is never sent to a public service, because that would reveal it - that is what Internal scope is for. The single function every outbound path asks is domainMayLeaveNetwork($domain) in includes/domains/scope.php, and it answers from the record's own Scope.
What was missing: a name could only be in the register once. So portal.example.com could be watched as Public or as Internal - never both. That is the gap #249 closes.
Option A - one record, two perspectives (the original design, written before any of this was built). One portal.example.com record holding both a public result and an internal result side by side. Every place that stores results - findings, history, certificates - would gain a "perspective" label, so each row says whether it came from outside or inside.
Option B - two records, linked (what was built). portal.example.com is in the register twice: one record with Scope = Public, one with Scope = Internal. The page shows each one the link to the other.
Think of the two as two folders for the same name.
- Each record already keeps its own everything. Its findings, its history, its certificates, its grade or health, its settings - all stored against that record. Two records therefore give two completely separate perspectives for free. Nothing is shared, so nothing can overwrite anything.
- "Permission is never inherited" is automatic. Each record's Scope decides on its own whether anything may leave the network. The Internal record never asks the outside world, whatever its Public twin does. There is no shared setting to get wrong.
- No storage change, so nothing to migrate. Option A meant changing how results are stored for every domain, including the public ones everybody already has, and deciding how to label years of existing history. Option B adds no table and no column. Existing data is untouched.
- Reports and the REST API already understand it. They already show and filter by Scope, so they tell the two apart without change.
- It is the reversible choice. If one record with two views is ever genuinely better, merging each pair is a one-off job that loses nothing. Undoing Option A would have meant unpicking a storage change across every domain.
The trade-off, stated plainly: owner, notes, tags and links belong to each record, so the two views can be owned by different people. That is usually right - the web team looks after the public site, the infrastructure team the internal server - but it does mean the two are kept separately. "Add an internal view" copies the owner across to start with, so the new record is not flagged as unowned.
The decision was posted on the issue before building (#249), as FreeITSM does for any change to an agreed design.
The rule: at most one record per name, per company, per scope.
So a name can be in the register once as Public and once as Internal - and never twice in the same scope. A name therefore has at most one twin.
"Per company" matters on multi-company installs: portal.example.com in company A and portal.example.com in company B are not twins - they are two separate companies' domains, as they always were.
The rule is checked everywhere a record could become a duplicate:
| Where | What could go wrong | What happens | Code |
|---|---|---|---|
| Adding a domain (New, Paste list, Import CSV, REST) | A second Public (or Internal) record of a name | Refused: "... is already in the register as a Public domain (id 12)" |
DomainsService::createDomain() β domainSameScopeId()
|
| Renaming a domain | Renaming onto a name that already has a record of the same scope | Refused | DomainsService::updateDomain() |
| Changing Scope (Edit, bulk edit, REST) | Switching the Internal twin to Public while the Public one exists | Refused: "... one record per name and scope. Edit that one instead, or delete it first." | DomainsService::updateDomain() |
| Both at once | Renaming and changing scope onto a taken pair | Refused - the check uses the name and scope the record would have |
updateDomain(), $nextName / $nextScope
|
| Add an internal view | A second internal view | Refused (it goes through createDomain()) |
DomainsService::addInternalView() |
Paste list and Import CSV go through createDomain() too, so a list containing portal.example.com with Scope = Internal now adds the internal twin instead of skipping it as a duplicate - and a genuine duplicate is still skipped. Two changes in api/domains/bulk_add.php make that work:
-
"Listed twice" means the same name and the same scope. A CSV can now hold
portal.example.comonce as Public and once as Internal; before, the second row was skipped as a repeat. -
A CSV's Scope is tidied like its Purpose -
Internalbecomesinternal- so a spreadsheet does not have to match the internal spelling. Before, a capital letter was refused.
Before Database Verification has added the scope column, every record is public, and domainSameScopeId() falls back to checking the name alone - exactly the rule before #249.
domainTwin($conn, $id, $name, $storeTenant) // the same name's OTHER record, or null
domainSameScopeId($conn, $name, $storeTenant, $scope) // the record with this name AND scope, or nullThere is deliberately no new column linking the twins. They are linked by being the same name in the same company - so a twin can never point at the wrong record, and there is no link to keep in step when one is renamed or deleted.
Since 3.16.0 a subdomain (intra.example.com) links to its parent (example.com) when the parent is in the register. With split DNS the parent's name can be there twice, so which does a subdomain link to?
The one of its own scope. An internal subdomain links to the internal parent; a public subdomain to the public parent.
-
When a subdomain is added:
domainParentCandidates(..., $preferScope)inincludes/domains/subdomains.phplists possible parents closest name first, and - between two records of the same name - the same scope first. The closest name still wins over scope; scope only breaks the tie between twins. The Edit dialog's Subdomain of list uses the same order. -
When the second twin arrives later: say
intra.example.com(Internal) was added when only the Publicexample.comexisted, so it was linked to that. Then somebody adds the internal view ofexample.com. The internal subdomain moves to the new internal parent, and the public subdomains stay put. Each move is written in the subdomain's history:parent_domain_id: example.com (public) β example.com (internal). Code: the end ofcreateDomain().
Why move it? So the rule is the same whichever order things were added in. Without the move, the result would depend on history - which is exactly the kind of thing nobody can work out later.
On a Public domain's page, under the name, there is a link: Add an internal view. It asks first, in plain words, then:
- creates an Internal record with the same name (
DomainsService::addInternalView()), - in the same company as the public record - not the company the analyst happens to have selected, because a twin in another company is not a twin,
- with the public record's owner, so it is not flagged as unowned,
- runs its first check straight away - DNS through this server only, which never leaves the network - and
- opens the new record.
Endpoint: api/domains/add_internal_view.php (POST {id}), the same access as adding a domain.
Once both exist, each page says so under the name, with a link across:
- on the Public one: "Split DNS - also watched from inside the network: the internal view"
- on the Internal one: "Split DNS - also watched from outside: the public view"
The page gets the twin from api/domains/get.php (twin: {id, scope}), and draws the line in twinLine() in assets/js/domains-view.js.
TRAP: one way only - Public β Internal. There is no "Add a public view" on an internal domain, on purpose. Creating a public record for an internal name would send that name to public DNS, the registries and the certificate logs - the very leak Internal scope exists to prevent. Somebody who really means it (the name is public too) adds it deliberately with New.
addInternalView()refuses an internal record, and the test checks it.
- The checks themselves. A Public record is checked exactly as before; an Internal record exactly as before. Split DNS is two ordinary records, so there is nothing new to check.
-
The gate.
domainMayLeaveNetwork()is untouched - it was already per record. - Storage. No table, no column, nothing for Database Verification to do.
- Reports, the dashboard, Watchtower. They count records, and already split public from internal by Scope. A name with twins appears once in the public counts and once in the internal ones, which is correct: it is being watched twice.
-
The REST API.
GET /domains?domain_name=portal.example.comreturns both twins; add&scope=internalor&scope=publicfor one.POST /domainswith the name and"scope": "internal"creates the internal twin (it used to be refused as a duplicate). See REST API: Domains.
| File | Change |
|---|---|
includes/domains/scope.php |
domainTwin(), domainSameScopeId()
|
includes/services/domains.php |
the per-scope duplicate rule in createDomain() and updateDomain(); same-scope parent; moving subdomains to a new twin; addInternalView()
|
includes/domains/subdomains.php |
domainParentCandidates() takes $preferScope; domainSubdomains() returns each subdomain's scope |
api/domains/add_internal_view.php |
New - the Add an internal view action |
api/domains/bulk_add.php |
Paste list / CSV: "listed twice" is per name and scope; a CSV's Scope is lower-cased |
api/domains/get.php |
twin and scope_ready for the page |
assets/js/domains-view.js (?v=15) |
the twin line and the Add link |
lang/en/domains.php |
the page strings, and help paragraph internal.p9
|
includes/feature_bingo/cards/domains.php |
card One name, watched from both sides |
tests/domains-split-dns.php |
New - below |
php tests/domains-split-dns.php
It runs inside a database transaction that is always rolled back - nothing it adds survives, and its last check proves it. It never runs a check, so no name is looked up anywhere. 29 checks:
- Once per scope - a Public and an Internal record of a name both exist; a second of either is refused, saying which scope.
- Twins and permission - each finds the other; the Public may leave the network and the Internal may not (nothing inherited); a name in once has no twin; the Feature Bingo card ticks while a twin exists.
- No third record - switching either twin's scope is refused; a record without a twin changes scope freely; renaming onto a taken name is refused, also when combined with a scope change; and the refused edits changed nothing.
- Subdomains - internal to internal parent, public to public parent; the Edit dialog lists the same scope first.
- Add an internal view - same name, Internal, same company, same owner; the internal subdomain moves to it, the public one stays; the move is in the history, from which record to which; a second is refused; and one way only.
- Per company - the same name in two companies is not a twin.
Control: run against the code from before #249, it fails at the very first step - the old rule refuses the internal twin. So a pass means the new rule is really there.
The existing Domains tests still pass: domain-subdomains-certs.php (49), domains-internal-scope.php (18), domains-expected-records.php (12).
Checked in the browser: a public domain's page shows Add an internal view, with no script errors.
Not covered by an automated test: the Paste list / CSV endpoint's own "listed twice" check and Scope tidying (they live in the web endpoint, which the test does not call - checked by reading); and the twin line on screen, because the install it was built on had no internal domain to show it with, and clicking Add there would have made a real record.
-
One record per name, per company, per scope. Not per name. Any new way of creating or editing a domain must go through
createDomain()/updateDomain(), or it can make a third record. - Twins are linked by name, not by a column. Never add a "twin_id": it would have to be kept in step on every rename and delete, and could point at the wrong record.
- Public β Internal only, in one click. Never offer Internal β Public: it would leak an internal name.
- The twin belongs to the public record's company, not the analyst's selected one.
- Subdomains go to the parent of their own scope - also when the second twin arrives after them.
-
www.is stripped when a name is added (the "paste a web address" tidy-up), sowww.example.comisexample.com. The test usesweb.for that reason.
- Domains - Internal domains - for users, including split DNS
- Internal Domain Monitoring - Developer Guide - slice 3's other parts
- Internal monitoring in Domains - design - the plan this finishes
- Domains - Developer Guide
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
-
Architecture
- β³ π§Ή Root folder tidy (URL routing)
- β³ π Database Integrity (FKs & constraints)
- β³ π οΈ Database Verification β Developer Guide
- β³ π§© Service Layer (UI + API share code)
- β³ πͺ€ Code traps (the TRAP: comments)
- β³ π Commenting for the Engineering wiki
- β³ π οΈ Engineering wiki scanner - Developer Guide
- β³ π Service-Layer progress
- π€ Contributing (read before a pull request)
- π§ͺ Developer tests
- π PDF exports moved to PDFKit (#254)
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- π± Currencies
- π§ The sidebar (optional module rail)
- π§© Preference templates
- π Display order (modules, Watchtower cards) β Developer Guide
- 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: Files
- β³ π 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
- β³ π Switching modules off
- β³ π οΈ Module availability β Developer Guide
- β³ π Module availability β design (#217)
- 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
-
π¦ Microsoft Entra ID
- β³ π₯ Importing people and groups
- β³ π» Intune: assets to their primary user
- β³ π’ Organisation chart
- β³ π οΈ Entra directory sync β Developer Guide
- β³ π οΈ Entra import - review (#235)
- β³ π οΈ Entra follow-ups - review (#237-#240)
- β³ π οΈ Intune primary user - review (#236)
- β³ π οΈ Organisation chart - reviews (#242, #265)
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π Proposal: custom fields, inventory import and change evidence (#194)
- β³ π οΈ Custom fields, evidence + inventory - review (#195-#197)
- β³ π οΈ Long forms in a task's Work view - review (#198)
- β³ π οΈ Empty Procedures fold - review (#247)
- β³ π« 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: Chart of accounts
- β³ π REST API: Projects
- β³ π‘οΈ API threat protection
- β³ π§βπΌ REST API: Analysts
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Live updates
- β³ π οΈ Watching a ticket - Developer Guide (1 of 2)
- β³ π οΈ CSAT delay and reminder - Developer Guide
- β³ π οΈ Ticket checklist step types - Developer Guide (1 of 2)
- β³ π οΈ Right-click Attach checklist - Developer Guide
- β³ π οΈ Checklists in the REST API - Developer Guide
- β³ π οΈ Inbox.js map β Developer Guide
- β³ π οΈ Floating windows & the file viewer β Developer Guide
- β³ π οΈ Tickets settings β Developer Guide
- β³ π Simpler ticket and task forms - design (#218)
- β³ ποΈ The ticket workspace
- β³ π Forms on tickets
- β³ π The ticket table
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π§Ύ Custom ticket fields
- β³ π·οΈ Ticket categories
- β³ π§Ύ A type for every new ticket
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ π Ticket dashboard β Developer Guide
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ π Attachment size limits and large emails
-
β³ Basic IMAP mailboxes
- β³ π Basic IMAP without the PHP extension (#257)
- β³ π οΈ Basic IMAP client β Developer Guide
- β³ π§ͺ Basic IMAP client β Testing and verifying
- β³ π οΈ Basic IMAP - Sent copy - Developer Guide (#270)
- β³ π οΈ Verifying a mail folder β Developer Guide
- β³ π οΈ Mailbox providers β Developer Guide
- β³ π οΈ How Gmail mail collection works β Developer Guide
- β³ 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
- β³ π Case reports - a ticket and its work as one PDF
- β³ π Scheduled work in your own calendar
- Problem Management
-
Tasks
- β³ π οΈ Task board and subtasks β Developer Guide
- β³ π§βπ€βπ§ People on a task ("Involved")
- β³ π Repeating tasks
- β³ π’ Moving a task between companies
- π Projects
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Leases
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π§© Asset type layouts
- β³ π Inventory freshness
- β³ π Linked configuration item
- β³ π Connected assets
- β³ π Asset review
-
β³ π·οΈ QR asset labels
- β³ π οΈ QR asset labels β Developer Guide
- β³ π Asset label designer - design (#229)
- β³ π· Asset scanning β Developer Guide
- β³ π οΈ Asset tag numbering β Developer Guide
- β³ π οΈ Asset reconciliation β Developer Guide
- β³ π οΈ Reconciliation, tags + labels - review (#164)
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ πͺ Hyper-V and Failover Clusters
- β³ π 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
- Starters & leavers
- Domains
- ποΈ Files
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
-
CMDB
- β³ π οΈ CMDB β Developer Guide
- β³ π οΈ Impact analysis β Developer Guide
- β³ π οΈ Data quality β Developer Guide
- β³ π Linked assets
- β³ π Linking an asset to its CI - design (#201)
- β³ π Discovery and redundancy-aware impact - design (#199)
- β³ π οΈ Relationship wording - review (#297)
- Network Mapper
-
Workflows
- β³ π οΈ Workflow engine β Developer Guide
- β³ π οΈ Workflows for tasks β Developer Guide
- β³ π οΈ Workflow notes on a ticket β Developer Guide
- β³ π οΈ Workflow category routing β Developer Guide
- β³ β° Time-Based Triggers
- β³ πͺ Webhooks
- β³ π¬ Webhook Message Formats
- β³ π HTTPS Certificates & CA Bundles
- β³ π οΈ SSL Verification β the Global Setting
- β³ π Workflow & Webhook Pitfalls
- Issue trackers (Jira, Azure DevOps)
- π‘ Zabbix (monitoring)
- π¬ Microsoft Teams alerts
- π 3CX (phone system)
-
System
- β³ π οΈ Preferences β Developer Guide
- β³ π§Ή Maintenance
- β³ π Finding people in the Analysts list
- β³ πͺ When an analyst leaves
- β³ π οΈ System β Branding β Developer Guide
- β³ π©Ί Page health (D019) β Developer Guide
- β³ β Feature Bingo
- β³ π·οΈ Cost centres
- β³ π Chart of accounts
- β³ π· Photo Album
- β³ Demo Data
- β³ π οΈ Demo Data β Developer Guide
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ One company supporting another
- β³ π’ 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
- β³ π Small fixes in 5.1.0
- β³ π CMDB relationships read backwards (5.1.0)
- β³ π #301 CSAT delay was never applied
- β³ π #281 Comments imported from Jira never arrived
- β³ π #288 A change failed to open with Enforce approvals on
- β³ π Long non-Latin articles never got an embedding
- β³ π« #280 Email lookups scanned the whole table
- β³ β #266 Dragging a task told nobody
- β³ π #255 Article previews showed HTML codes
- β³ π #252 A link to a Knowledge article did not open
- β³ π #254 PDFs could not print Polish
- β³ π #250 Clicking a folder left the open article on screen
- β³ π« #260 The dashboard cog did nothing
- β³ π« #248 A department rule never matched on four routes
- β³ π A directory account could be matched to someone else
- β³ π« #244 Reply went to the assigned analyst
- β³ π Mailbox sign-in was not checked
- β³ π Four weaknesses around signing in
- β³ π A lookup answer could name any record
- β³ π« #227 A mailbox could not be deleted
- β³ π« #226 A picture on a note could not be closed
- β³ π» #222 The Assets page never loaded in 4.3.0
- β³ βοΈ #214 A "contains" condition did not match capitals
- β³ βοΈ #216 A workflow email from a chosen mailbox always failed
- β³ π³ The Docker image was missing PHP extensions
- β³ π A failed DNS lookup read as a missing record
- β³ π A link could change data
- β³ π Access outlived its removal
- β³ π Knowledge articles could run scripts
- β³ π Knowledge writes ignored permissions
- β³ π Company boundaries (Oct 2026)
- β³ π Smaller gaps (Oct 2026)
- β³ π Escaped text could leave its attribute
- β³ π Task descriptions were not cleaned
- β³ π Task documents ignored company scope
- β³ π Repeating tasks ignored company scope
- β³ π¬ #183 Mailbox checks overlapped
- β³ π #188 Transfer lock shown off when unknown
- β³ β #184 Subtask names and Overdue
- β³ π§© #186 Teams: the buttons did nothing
- β³ π Text in a change could run script
- β³ π AI settings changed without the AI permission
- β³ πͺ #178 Deleting an analyst failed
- β³ π« A team with no departments showed 0 tickets
- β³ π Projects: adding a phase did nothing on Save
- β³ π Leavers could still sign in
- β³ πΌοΈ 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