Security & allowlist
ShowKit is built around a simple principle: changelogs are public, so the widget is built for public distribution. Anything you publish is meant to be indexed, embedded, and shared — but only you can write to it, and only your allowlisted domains can embed it.
Public by design
The content the widget loads — your changelog — is the same content that lives at showkit.dev/c/<slug>, gets indexed by Google, ships as RSS / Atom / JSON, and goes out to your email subscribers. There is no hidden tier of "private changelog entries" behind the embed. If you can see it in the widget, you can see it on the public page.
This is intentional. We want your launches to spread. We want search engines to find them. We want feed readers to subscribe. The widget is one of several distribution surfaces over the same public dataset.
The workspace slug is an identifier, not a credential
data-workspace="acme" is just an identifier — like a Twitter handle, a Substack URL, or a Plausible data-domain. It tells the widget which workspace's changelog to display. It does not grant any kind of access. Treat it the way you'd treat your public username: visible to everyone, useless to attackers on its own.
Writes are gated by auth + row-level security
Publishing entries, editing the roadmap, importing testimonials, and changing workspace settings all happen through the dashboard, behind a magic-link sign-in. Every database write is protected by Postgres row-level security policies keyed to the workspace owner — even if someone had your slug, they could not write to your workspace without your auth cookie.
Domain allowlist
Because the slug is public and the data is public, nothing structurally stops a third party from putting data-workspace="acme" on their site to display your changelog as if it were theirs. The same is true of any embeddable content (someone could embed your tweets, your Substack RSS, your YouTube videos). It is not a data breach — every byte they show is already public — but it is a brand-trust problem.
The domain allowlist is our defense. When configured, the widget's data-fetch endpoint checks the embedding page's Origin header against your allowed-domains list and refuses to serve to origins outside it.
Configure your allowed domains
Open your dashboard → Settings → Allowed domains. Add the hostnames where your widget is allowed to load. Wildcards on the leftmost label are supported.
# exact hosts
acme.com
app.acme.com
# wildcard subdomains
*.staging.acme.com
# localhost is always allowed for dev
localhost
127.0.0.1Wildcards match one label only: *.acme.com matches app.acme.com and staging.acme.com, but not acme.com.evil.com (that would be a different domain entirely).
Save with an empty list and the widget serves to any origin (the default for new workspaces). This is fine while you're trying things out — add domains the moment your embed is on a public page.
What the allowlist does not restrict
- The public page at
showkit.dev/c/<slug>— Google and Bing index it /changelog.rss,.atom,.json— feed readers and integrations- The widget script file itself — needed to bootstrap on any allowed page
What an outside embed sees
When an origin is not on your list, the data endpoint returns a 403 with { error: "origin_not_allowed" }. The widget logs one clear console warning and renders nothing. No partial state, no broken bell.
Other anti-impersonation defenses
The allowlist is the primary defense. We pair it with several smaller signals:
- Footer attribution. Every widget on the Free tier links the popover footer back to
showkit.dev/c/<slug>. Visitors can verify the real source with one click. - Embedding-origins audit log. Your dashboard's Embed analytics tab shows the top origins fetching your widget. Unexpected hosts surface fast — usually before they're a real problem.
- Edge rate limiting. A single misbehaving origin can't burn through your bandwidth quota — we cap per-origin request rates at the CDN.
- Verified-domain badge (planned). Optionally verify domain ownership via a DNS
TXTrecord. Verified domains show a small "Verified" indicator in the popover footer — Stripe and Apple Pay use the same pattern.
Customer-side CSP
The widget runs inside a Shadow DOM with its own scoped stylesheet, so it cannot leak styles into — or read styles from — your site. It makes outbound requests only to https://showkit.dev. See Troubleshooting → CSP for the exact headers.
Reporting a vulnerability
If you find a security issue, please email security@showkit.dev. We respond within one business day and credit reporters in the changelog (with permission).