This page documents how Astralium handles user data: where the data travels, who holds each segment, and how the controls are verified. The settings and figures below can be checked against this site's public HTTP response headers and the database migrations in the repository.
The final section states what this page does not cover.
Charts are computed in the browser
All six engines — Zi Wei Dou Shu, BaZi, Western astrology, Vedic astrology, Liu Yao and Qi Men — are bundled with the front end. Star placements, transformations, stem-branch relations, houses and aspects are computed in the user's browser and do not pass through our servers.
Chart history is local
History is stored in the browser's localStorage as a fixed-size ring buffer, partitioned by account. While signed out, it exists only on that device.
Geocoding goes through the server
A place name must be converted to coordinates and a timezone before true solar time can be computed. This runs through the site's own endpoint, /api/resolve-birth-place: the server holds the third-party maps key and caches results, so the key is never shipped to the browser and a repeated place name is not looked up twice.
When data is written to the database
Used signed out, none of the above produces a database write. Data reaches Supabase only when a user saves a birth profile, or a practitioner creates a client, a case, or a packet version.
Response headers currently in effect
Five security headers are served site-wide. The CSP reads in full:
- Content-Security-Policy — restricts which sources may execute script; see below
- Strict-Transport-Security — enforces HTTPS, no downgrade
- X-Frame-Options: DENY with
frame-ancestors 'none'— no origin may embed the site in an iframe (clickjacking) - Referrer-Policy: strict-origin-when-cross-origin — sends only the origin across sites, never the full URL
- Permissions-Policy — disables camera, microphone, geolocation, the payment interface and USB
The set is rated A+ by securityheaders.com.

script-src without unsafe-inline
This site's script-src carries neither 'unsafe-inline' nor 'unsafe-eval'. That is the strongest form of protection a CSP can provide. In effect, none of the following execute:
- injected inline scripts
- event-handler attributes such as
onerror javascript:URLsevaland string-formsetTimeout- external scripts from unlisted origins
This build contains zero inline executable scripts, checked across all 329 prerendered HTML files; its 397 JavaScript chunks contain no eval, no new Function, and no WebAssembly compilation call.
By contrast, 'unsafe-inline' permits the page to execute any inline script; a script-src carrying it offers close to no protection against cross-site scripting, because an injected inline script still runs. It is common in deployed policies not because the configuration is difficult, but because removing it is not a setting — it is a constraint on how the front end is written: the HTML served to the browser must contain no inline executable script. For a site already using inline script, removing it generally costs a front-end rewrite.
script-src, so whether it carries 'unsafe-inline' is visible at a glance. A site with no content-security-policy header at all has no CSP.connect.facebook.net in the allowlist
The script-src allowlist includes connect.facebook.net, but this site does not load that script. Meta's in-app browsers (Threads, Instagram, Facebook) inject it into every page they render, for advertising conversion attribution; it is listed so that attribution continues to work for traffic arriving from those sources.
The cost is stated alongside it: an origin in the allowlist can execute script on this site's origin, which ties this site's security posture to that origin's. It is the same trade every site running Meta conversion tracking makes. The entry is unrelated to functionality — removing it affects no operation, and only disables advertising attribution through that channel.
default-src is omitted
The policy sets no default-src. Directives that are not listed therefore remain unrestricted, so adding a font, an image source or an embedded page cannot break the site through CSP.
This trades coverage for maintenance surface. With default-src omitted, the situations requiring a policy change reduce to one: adding an external script source to the front end. The reason is survival of the policy itself — a policy that regularly breaks routine development gets loosened during some debugging session and is not restored afterwards.
Who holds each segment
- Payments · Stripe — checkout is a full-page redirect to a Stripe-hosted page. The front end does not install
@stripe/stripe-js. Card details pass through neither our pages nor our servers; the server receives subscription state returned by Stripe. - Database and authentication · Supabase — the boundary between accounts is drawn by Row Level Security policies, detailed below.
- Transport and deployment · Vercel — TLS certificates, edge network and the deployment pipeline.
- Error monitoring · Sentry — configured with
sendDefaultPii: false, session replay and tracing disabled. It collects error type, stack and browser environment; no default personal identifiers, no session recording.
Cross-account isolation is enforced by the database
Row-level security is enabled on 18 tables, in two shapes:
- 13 carry user-scoped policies — 40 in total, each constraining
auth.uid()to match the row's owner. - 5 carry no policies at all — the geocoding cache, feedback submissions, the referral abuse log and two Stripe idempotency tables. Row-level security denies by default, so these are unreadable with any user credential; only the server-side key reaches them.
The read policy on birth profiles, for instance:
The rule sits in the database rather than the application. Querying directly with a user's credentials, bypassing the application, still returns no rows belonging to anyone else.
These policies are written in version-controlled migration files rather than configured through a dashboard. Every change carries a commit record and passes review.
Isolation is tested automatically
Correct configuration and correct behaviour have to be confirmed separately. A verifier in the repository creates two temporary accounts, attempts to read one account's rows using the other's identity, asserts the result is empty, and deletes both accounts.
The observation period before enforcement
A policy that blocks requests fails, when misconfigured, by silently breaking page functionality. The policy was therefore deployed first as Content-Security-Policy-Report-Only — recording without blocking — and observed against real traffic for seven days.
None of the violations recorded during that window originated in this site's own code; all came from tracking scripts injected by applications' built-in browsers. Only then was the policy switched to enforcing. On the day of that change, controlled test scripts were injected on production to confirm blocking was actually in effect.
How far this page goes
Risk classes covered here
- cross-site script execution
- clickjacking
- connection downgrade
- cross-account reads
- handling of payment details
Not covered here
- providers' own practices and compliance (Supabase / Vercel / Stripe each publish their own)
- the strength of a user's password
- whether a device is shared
These measures address specific classes of risk. They are not equivalent to "secure" as an overall state.
Practitioners who need to explain data handling to clients may cite this page directly. For anything not covered, please write viaContact.
Casting requires no account.
Signed out, no database write occurs. Sign in when you want to save profiles.
Start charting →