Security and CSP
Content-Security-Policy
Section titled “Content-Security-Policy”If your page sends a CSP, you need to allow two sources for the widget: the loader script (the API origin) and the iframe (the site origin):
Content-Security-Policy: script-src 'self' https://api.mirac-ozdemir.com; frame-src https://mirac-ozdemir.com| Directive | Source | Why |
|---|---|---|
script-src |
https://api.mirac-ozdemir.com |
The widget.js loader. |
frame-src (or child-src) |
https://mirac-ozdemir.com |
The embed page where the calculator runs. |
connect-src is not needed: the calculation is done inside the iframe itself. The loader adds no inline script or style to your page; the iframe’s styles are set via the CSSOM, so unsafe-inline is not required.
Security model
Section titled “Security model”- Message trust. The loader accepts a message only if all of these conditions hold:
event.originis the site origin,data.sourceismirac-ozdemir-widget,data.versionis1,data.idis a known widget instance andevent.sourceis that instance’s iframe window. A frame cannot resize or impersonate another widget. - No data is inserted into the DOM. Data coming from the API and the iframe is handled with
textContent/DOM APIs and is not parsed as HTML; the payload is passed to you only as the eventdetail. - Isolation. The embed page is on a different origin from your page: it cannot read your page’s content, cookies or storage. The loader also grants the iframe no permissions (
allow). - Sandbox. In the iframe, scripts, forms and popups are enabled; top navigation, downloads, modal dialogs and pointer lock are disabled. The tool link opens normally in a new tab (
allow-popups-to-escape-sandbox). - Command trust (embed page). In the other direction, the embed page accepts a command from your page only if
event.source === window.parent, the envelope is the host envelope, theidequals the widget’s ownid(nevernull) and the payload is well-formed. Such commands can only change presentation (theme, accent colour, …), fill inputs and trigger a calculation; unknown types are ignored. The loader posts them with the site origin astargetOrigin. - Input validation.
data-*attributes,data-input-*values andupdate/setInputsarguments are validated against strict allow-lists (value lengths, field-name pattern, accent as 6 hex digits, a fixed set of radius/density/theme values) and URL parts are encoded; unknown fields and invalid values are ignored or the container is skipped with a warning. - The loader’s headers. The loader responses carry
Content-Type: application/javascript; charset=utf-8,Access-Control-Allow-Origin: *,Cross-Origin-Resource-Policy: cross-originandContent-Security-Policy: default-src 'none'(the response itself cannot load anything). Caching differs by address:/widget.jsand/widget/v1.jsuseCache-Control: public, max-age=3600; the versioned/widget/v<semver>.jsusespublic, max-age=31536000, immutable;/widget/manifest.jsonusesmax-age=300and is also readable cross-origin. - Subresource Integrity. The immutable loader URL can be pinned with a
sha384-…hash from the manifest, so a changed file is rejected by the browser: Versioning policy. - Console messages. The loader reports invalid options in English with
console.warn([MiracOzdemirWidget] …).
Privacy
Section titled “Privacy”- The widget page does not load the site shell or Matomo analytics.
- Calculation inputs are sent to the API only to perform the calculation; the widget passes them back to your page in the
mo:resultevent and does not send them anywhere else. - The widget does not read the visitor’s personal data from your page; origin isolation prevents this.
- In your site’s cookie/privacy (KVKK) text, it is good practice to state that the calculator is loaded from a third-party domain (
mirac-ozdemir.com).
Anonymous embed usage counting
Section titled “Anonymous embed usage counting”So that we can see which tools are embedded and how much, the embed page sends an anonymous beacon to our own API (POST /v1/public/tools/{toolId}/events) when a widget is shown (embed-view) and when a calculation is performed in it (embed-calculation). This applies to the loader and to plain iframes alike.
- What is sent: the event type and the host name of the embedding page (e.g.
example.com), taken from the referrer origin, lowercase. If no host is available the value is(direct). - What is stored: only counters per tool, host and day. No IP address, user agent, page path, cookie, calculation input or result is stored.
- Limits: hosts beyond 5,000 distinct values per day are collapsed into
(other). Counters are deleted after the same retention period as the other usage statistics. - Where it is shown: in aggregate, in the admin insights of the platform (“Embeds” view).
- Reliability: the host is self-reported by the browser (referrer), so it is analytics, not proof; it can be spoofed or missing.
For your own privacy text: the widget makes your visitors’ browsers contact mirac-ozdemir.com (the embed page) and api.mirac-ozdemir.com (loader, calculation data and the counter beacon); from the counter, only the domain of your page is kept, counted per day.
Disclaimer
Section titled “Disclaimer”The widget shows the tool’s disclaimer note inside it and does not let you remove it. Results are for information purposes only and do not replace tax, legal or financial advice.
Reporting a vulnerability
Section titled “Reporting a vulnerability”If you have noticed a security issue, please contact us before sharing it anywhere public.