✍️Design-level Stored Xss In Matomo I18n Rendering — Public Write-up
Summary
This write-up documents a design-level trust boundary issue identified in Matomo's internationalization (i18n) rendering pipeline. The issue was reported responsibly through Matomo's bug bounty program and later determined to be out of scope due to the absence of a directly exploitable attacker-controlled write path in a default installation.
Despite being out of bounty scope, the finding remains technically relevant as an example of a conditional Stored Cross-Site Scripting (XSS) risk caused by architectural trust assumptions, not by a missing access control or filter.
This document is published as a portfolio and learning artifact, not as a claim of an exploitable vulnerability in current Matomo releases.
Background
Matomo is a widely used open-source analytics platform with a rich Admin UI, SPA components, and Tag Manager functionality. Like many large web applications, Matomo relies heavily on an i18n translation system to render UI text dynamically across privileged interfaces.
The i18n system supports:
- HTML-enabled translation strings
- Dynamic placeholders (e.g.,
%s,%1$s) - Global rendering across Admin UI, Tag Manager, and SPA views
🧑💼High-Level Architecture
Semi-Trusted Persisted Data Sources
• Plugin metadata
• Marketplace imports
• Saved configuration
• Tag Manager UI content
• Historical database migrations
│
▼
Matomo i18n Translation Layer
• HTML-enabled strings
• Dynamic placeholders
• No mandatory contextual escaping
• Global-scope rendering
│
▼
Privileged Browser DOM Context
• Admin UI
• Tag Manager
• SPA / preview modesThe translation layer acts as a trust boundary, assuming that upstream data injected into placeholders is safe to render as HTML.
Core Observation
During runtime inspection of the latest official Matomo release, the following behaviors were confirmed:
- Translation strings are explicitly HTML-capable
- Placeholder values are injected into translations without enforced contextual escaping
- Rendered output is inserted into the DOM using HTML-capable sinks (e.g.,
innerHTML) - Translations are rendered in privileged administrative contexts
A simplified pseudocode illustration:
translations["Admin_Welcome_Message"] = "Welcome %1$s to the administration panel";
function renderTranslation(key, placeholderValue) {
const raw = translations[key];
const rendered = raw.replace("%1$s", placeholderValue);
adminContainer.innerHTML = rendered;
}The rendering layer does not enforce that placeholderValue is HTML-safe. Safety is instead assumed based on upstream processes.
Proof of Concept (Non-Weaponized)
To validate rendering behavior without introducing exploitation risk, a non-executing HTML payload was used:
<b style="color:red">[XSS-PoC]</b>When inserted via a supported persisted data source and rendered through an i18n placeholder, the payload appeared as rendered HTML, not escaped text, inside the Admin UI.
No JavaScript payloads were used. No security controls were bypassed. No custom code was injected.
This confirmed HTML rendering behavior, not exploitability.
Why This Is a Design-Level Issue
This behavior is not caused by:
- Missing RBAC
- Broken authentication
- A filter bypass
- An unreviewed user input path
Instead, it is caused by a design decision:
The translation layer trusts all placeholder values implicitly and delegates safety to upstream review and process controls.
As long as this trust holds, the system is safe. If that trust is violated (e.g., through a compromised plugin, supply-chain issue, or unsafe import path), the rendering layer provides no defense-in-depth.
Security Impact (Conditional)
If attacker-controlled data were ever allowed to reach this layer, the impact would include:
- Persistent XSS in Admin UI
- Session hijacking
- Administrative action forgery
- Tag Manager abuse
However, in a default Matomo installation, no such attacker-controlled write path exists.
Disclosure Outcome
Matomo maintainers confirmed that:
- This behavior is intentional and by design
- Translation content is reviewed and automatically tested
- Only directly exploitable issues without code or data manipulation are eligible for their bounty program
The report was therefore classified as out of scope, not as an exploitable vulnerability.
Lessons Learned
- Not all security-relevant behaviors are bounty-eligible vulnerabilities
- Design-level trust assumptions are often protected by process, not code
- Bug bounty programs prioritize exploitability over theoretical risk
- Clear scoping is as important as technical depth
Takeaway
This case serves as a real-world example of how secure-by-process designs can still represent architectural risk, and why distinguishing between exploitable vulnerabilities and design trust assumptions is critical in security research.
About This Write-up📘
This document is shared for educational and portfolio purposes only. It does not claim an active vulnerability in Matomo and respects responsible disclosure principles.