“Self-hosted” is not a binary. An editor can ship as files you serve from your own domain and still call out to a vendor endpoint to validate a licence, report usage, or fetch a remote asset. If a product is billed per editor load, something has to count those loads.
This is straightforward to check, and you should check it yourself rather than take anyone’s word for it — including ours. Here is a method that takes about ten minutes and produces evidence you can put in a security review.
Why it matters beyond licensing
- Air-gapped and regulated deployments.If the editor needs to reach a vendor host, it will fail — possibly silently, possibly in a degraded mode you only find in production.
- Data residency. Anything sent externally is a question you will be asked to answer, even if the payload is only a counter.
- Availability.A hard dependency on someone else’s uptime is a dependency you did not choose.
- Content Security Policy. You cannot write a tight CSP for a component whose outbound calls you have not enumerated.
The method: instrument before you load
The DevTools Network panel shows you resource loads, but it is easy to miss a single beacon fired during teardown, and hard to attach a call to the code that made it. A more reliable approach is to wrap the three outbound APIs before the editor script is parsed, so nothing can slip past.
The order matters: this block has to come before the editor’s <script> tag.
<script>
window.__net = [];
(function () {
var of = window.fetch;
if (of) window.fetch = function (u) {
window.__net.push({ how: 'fetch', url: String((u && u.url) || u) });
return of.apply(this, arguments);
};
var oo = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (m, u) {
window.__net.push({ how: 'xhr', method: m, url: String(u) });
return oo.apply(this, arguments);
};
var sb = navigator.sendBeacon && navigator.sendBeacon.bind(navigator);
if (sb) navigator.sendBeacon = function (u) {
window.__net.push({ how: 'beacon', url: String(u) });
return sb.apply(navigator, arguments);
};
})();
</script>
<!-- editor scripts go AFTER the block above -->
<script src="/path/to/editor.js"></script>sendBeacon is the one people forget. It exists specifically to send analytics during page unload, it is fire-and-forget, and it is easy to miss if you are watching a network panel while clicking around.
Exercise the editor, then read the log
Loading the page is not enough — some calls only fire on first edit, on focus, or on teardown. Do a realistic pass: set content, run a few commands, type, then read the log.
// after interacting with the editor
var calls = window.__net;
var external = calls.filter(function (c) {
return /^https?:\/\//i.test(c.url) && c.url.indexOf(location.origin) !== 0;
});
console.table(calls);
console.log('external calls:', external);Filtering on location.originis the important step. Same-origin requests are your own server — image uploads, autosave endpoints, your API. What you are looking for is anything leaving your origin that you did not configure.
Reading the results honestly
Not every external request is a problem. Sort what you find into three buckets:
- You configured it. An AI provider you supplied a key for, an upload service you chose, a collaboration server you run. Expected.
- A third-party asset. A CDN font, an optional library loaded on demand (PDF export libraries are commonly loaded this way). Usually benign, but it is a CSP entry and an offline failure mode, so write it down.
- The vendor’s own host, unprompted. Licence validation, telemetry, usage counters. This is the category that matters, and the one worth asking about directly.
What this produces for RichTextEditor
Running exactly the method above against a default self-hosted setup — instrument, load the editor, set content, run commands, dispatch input events — produces an empty log: no fetch, no XMLHttpRequest, no sendBeacon, and no external resource loads. The only requests are the local editor assets you are serving and inline data: URIs.
That is a consequence of the licensing model rather than a feature we bolted on: a perpetual, unmetered licence has nothing to count, so there is no counter to phone home. Optional integrations, once you configure them, obviously talk to the services you point them at.
Run it against whichever editors are on your shortlist. It is the same ten minutes for each, and the output is evidence rather than a marketing claim — which is the point.
RichTextEditor is a perpetual-licence JavaScript editor — one purchase, self-hosted, no metered editor loads. Download the evaluation or see how it compares.