Trust
The tools run in your browser, and what you open in them is never sent anywhere. This page is how to check that for yourself, and an exact account of what the server does see: every route it answers, every record it keeps, and every outside service it talks to.
The tables below are written into this page by the build, from the files they describe: the security policy from _headers, the routes and records from the server's code, the count's columns from its schema. If the code changes and this page has not been told, the build stops.
Check it yourself
You do not need to believe any of this page. Three checks, each under a minute:
- The badge. Every page carries a Private · verify badge. Press it and it lists everything that page has contacted since you opened it, grouped by where it went, and how many bytes of your content each request carried. Open a file in a tool and watch the list: it does not grow.
- Your browser's network panel. The badge is this site's own code, so check it with something that is not. Verify walks through it: open the panel, use a tool with a large file, and see that nothing the size of your file goes up. It also hashes the code your browser was served, so you can compare it with what somebody else received.
- Pull the plug. Load a tool, switch off your connection, and use it. It works, because everything it needs is already in the tab.
What you will see in the network panel: requests to obscuraos.com, including this site's page count (/api/hit, three words, described below), and Cloudflare Web Analytics, which Cloudflare adds to each page as it is served. Nothing else is permitted, by the policy in the next section.
The policy your browser enforces
Every page on this site is sent with this header. It is quoted here by the build from _headers, the file Cloudflare reads, so it cannot say one thing here and another on the wire:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' blob: https://static.cloudflareinsights.com; worker-src 'self' blob:; child-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' blob: data: https://cloudflareinsights.com; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'
| Directive | Value | What it means |
|---|---|---|
default-src | 'self' | Anything not named below may come from this site only. |
script-src | 'self' 'unsafe-inline' 'wasm-unsafe-eval' blob: https://static.cloudflareinsights.com | Code may come from this site, from the page itself, and from workers and WebAssembly the tools build as they run. The one outside address is Cloudflare Web Analytics' script, which Cloudflare adds at the edge (see below). 'unsafe-inline' is there because the pages still carry inline script; it is a weakness against script injection, not a way for a file to leave. |
worker-src | 'self' blob: | Background workers may only be this site's own files, or ones a tool builds in memory. |
child-src | 'self' blob: | The same, for older browsers that read this instead. |
style-src | 'self' 'unsafe-inline' | Styles from this site and the page itself. |
img-src | 'self' data: blob: | Pictures from this site, or made in the page. No outside image can be used as a beacon. |
media-src | 'self' data: blob: | The same, for sound and video. |
font-src | 'self' data: | Fonts are this site's own files; no font service is asked. |
connect-src | 'self' blob: data: https://cloudflareinsights.com | Where a page may send a request. This is the directive that makes “nothing uploaded” something the browser enforces: a page can talk to this site and to Cloudflare Web Analytics' collector, and the browser refuses anything else, whatever the code tries. |
manifest-src | 'self' | The install manifest is this site's. |
object-src | 'none' | No plug-ins, ever. |
base-uri | 'self' | An injected tag cannot make relative links point elsewhere. |
form-action | 'self' | A form can only post to this site. |
frame-ancestors | 'self' | No other site can put these pages in a frame. |
Devices a page may use, from the same block:
Permissions-Policy: camera=(self), microphone=(self), geolocation=(self), payment=(), usb=(), serial=(), bluetooth=(), interest-cohort=()
Replies from the server's own routes under /api/ carry a stricter policy, since none of them is a page: default-src 'none'; frame-ancestors 'none'.
6 files run under a policy of their own, because the library inside has to build code as it starts. None of them is a page, and none is given a wider connect-src than the page has:
/pro-code/cad-solve-worker.js:default-src 'none'; script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval'; connect-src 'self'; worker-src 'self'/pro-code/eng-brep-worker.js:default-src 'none'; script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval'; connect-src 'self'; worker-src 'self'/vendor/libraw-wasm/1.6.0/worker.js:default-src 'none'; script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval'; connect-src 'self'; worker-src 'self'/vendor/libraw-wasm/1.6.0/libraw.js:default-src 'none'; script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval'; connect-src 'self'; worker-src 'self'/lib/occt-import-js/*: no policy at all. A worker is held to its own script's policy, and this one (the Engineering Suite's STEP reader) could not start under the page's. Nothing on the site loads a page from there./lib/replicad-opencascadejs/*: no policy at all. A worker is held to its own script's policy, and this one (the Engineering Suite's STEP reader) could not start under the page's. Nothing on the site loads a page from there.
Two things this does not cover, stated plainly. It permits requests back to this site, which is why the page count and the routes below are listed in full. And it cannot stop a change to the site's own code; what stops that is the code being small enough to read, the build's checks, and hashing what you were served.
Who else handles data
Three companies process data for this site. None of them ever receives a file you open in a tool, because no tool sends one.
| Who | For what | What they handle |
|---|---|---|
| Cloudflare | Hosting: the pages, the server routes, the store of seats and waitlist records (Workers KV), the page count (a D1 database), and Cloudflare Web Analytics. | Every request reaches Cloudflare's edge, with your IP address, as it would for any website. The records and counts described below, in this site's own Cloudflare account. Web Analytics reports that a page was viewed; it sets no cookie and keeps no identifier. |
| Stripe | Payments, on Stripe's own checkout page. | Your email address and payment details, under Stripe's own policy. This site never sees a card number. The server asks Stripe about a checkout or subscription by its reference, to make and renew your key. |
| Resend | Email, and only once email is switched on for this site. | The address and the message: a key and its sign-in link, a seat invitation, or a waitlist confirmation. Whether email is on is shown here when this page can ask the server. |
The server also asks a few public services for public data, on your behalf, so that your browser never contacts them. Every outside address in the server's code is here:
| Address | Who | What it is sent |
|---|---|---|
api.stripe.com | Stripe | Checkout, subscription, invoice and customer references, to make and renew keys, credit an invitation and read the owner's revenue figures. Only ever with this site's own key. |
billing.stripe.com | Stripe's billing page | A link written into the key email and the Pro page. The server never fetches it. |
api.resend.com | Resend | Only when email is switched on (the RESEND_API_KEY secret): the buyer's address and their key, the sign-in link, a seat invitation, or a waitlist confirmation. |
api.osv.dev | OSV.dev, Google's open source vulnerability database | When you press the button in the Sovereign Suite's dependency check: each package's name, version and ecosystem, and nothing else. |
api.pwnedpasswords.com | Have I Been Pwned | When you use PWNCHECK: the first five characters of the password's SHA-1 hash, with padding asked for. Never the password or the rest of the hash. |
www.cisa.gov | CISA | Its whole Known Exploited Vulnerabilities list, fetched at most once every six hours per data centre. Nothing about you or your findings. |
epss.cyentia.com | FIRST's EPSS scores | The whole daily file, the same way. Nothing about you or your findings. |
cdn.jsdelivr.net | jsDelivr | The library files on the shelf (/vendor/), fetched once by the edge, checked against a pinned size or hash, and kept. Your browser never contacts it. |
cdnjs.cloudflare.com | cdnjs | The same, for the libraries published there. |
huggingface.co | Hugging Face | The speech model SCRIBE uses, fetched once by the edge in the same way. |
tfhub.dev | TensorFlow Hub | The face detection model VEIL uses, the same way. |
github.com | GitHub releases | One pinned model file for the Photo Editor's subject selection, fetched once by the edge and checked against its hash. |
gist.githubusercontent.com | A GitHub gist | The record /status is drawn from, read by /api/status. Nothing is sent to it. |
The terms for Team and Business are in the data processing agreement.
What the server sees
Every route the server answers, found from the files that answer them. A route not in this table does not exist.
| Route | Who calls it | What arrives | What is kept |
|---|---|---|---|
/api/hit | Every page, once as it opens, and a tool when a file is opened, a save meets the paywall, a checkout button is pressed and the like. | Three words: the page's address, what happened, and where the visit came from as a class (a search engine's name, “internal”, “other”, “none”). Nothing is sent when Global Privacy Control or Do Not Track is on. | One is added to a count for that day and those three words (the table below). The address and user agent are not kept; the user agent is read only to leave robots out. |
/api/licence | The Pro page, when a checkout finishes, a key is renewed, a gift or seat link is opened, or someone asks for their key by email. | A Stripe checkout reference, a key, an invitation or seat token, or an email address. | Nothing of its own but a note that a checkout's key email has gone (pro-mail:) and when a gift pass was first opened (gift-start:). Stripe is the record of who paid. |
/stripe-webhook | Stripe's servers, when a checkout completes, an invoice is paid, or a subscription changes or ends. | An event naming a checkout, invoice or subscription. Only the reference is believed: the object is fetched from Stripe with this site's key. | The key email's note, an invitation pairing (ref:), and on Team and Business a line in the seat record when the plan changes. |
/api/seats | The seats page, for the holder of a Team or Business subscription's own key. | That key, and a person's name and email address when a seat is added. | The seats and their record of changes (seat:, seatlog:, seattoken:, seatplan:, seatgen:, seatseen:), and a Business organisation's name and logo (seatorg:). |
/pro-code/* | The Sovereign Suite, the Photo Editor and the Engineering Suite, for their code. | The Pro key, in a cookie sent to this path only. | Nothing. The key is checked; whether the subscription is paid is cached at the edge (below). |
/api/waitlist | The waitlist form, and the letter's sign-up at the end of each guide. | An email address and which products it is waiting for. | The address, the dates, and the products (waitlist:). No IP address, no browser details. |
/api/unsubscribe | The unsubscribe page. | An email address. | The record is marked as unsubscribed and its products emptied. |
/api/osv/* | The Sovereign Suite's dependency check, when you press the button. | Package names, versions and ecosystems. | Nothing: it passes the question to OSV.dev and the answer back. |
/api/pwned/* | PWNCHECK. | Five hexadecimal characters of a password's SHA-1 hash. | Nothing: it passes them to Have I Been Pwned and the answer back. |
/api/kev/* | The Sovereign Suite's vulnerability tracker, when you ask it to fetch the lists. | Nothing but which of the two public lists. | Nothing: CISA's and FIRST's lists are fetched and kept at the edge for six hours. |
/vendor/* | Tools that need a library or a model. | Which library file. | Nothing about you. The file is fetched once by the edge and kept. |
/api/status | /status. | Nothing. | Nothing: it reads the record the scheduled check keeps. |
/api/stats | The owner's /stats page, behind a key. | The key and a date range. | Nothing; it reads the counts. With STATS_PUBLIC on, anyone may see the daily totals and the thirty pages most often found from a search. |
/api/revenue | The owner's /revenue page, behind the same key. | The key. | Nothing; it reads totals from Stripe. |
/api/waitlist-export | The owner, behind a key of its own. | The key. | Nothing; it reads the waitlist. |
Every route under /api/ first passes a gate that refuses a write from another website, caps the size of what is sent, and limits how often one address can write. The gate keeps its counts in memory and writes nothing down.
The page count
One table, in a D1 database in this site's Cloudflare account. It has no column for an address, a browser, a cookie or any identifier, so none can be stored by mistake.
The table, from the schema the database was made with (.github/d1/hits.sql), with its primary key day, path, ev, src:
| Column | Declared as | What it holds |
|---|---|---|
day | day TEXT NOT NULL | The date, in UTC. |
path | path TEXT NOT NULL | The page's address, such as /seal: lower case, no query, no fragment. |
ev | ev TEXT NOT NULL | What happened, one of the words below. |
src | src TEXT NOT NULL | Where the visit came from, one of the classes below. |
n | n INTEGER NOT NULL DEFAULT 0 | How many times. The only number in the table. |
A beacon is refused unless each word is on a fixed list in functions/_lib/count.js. The events: view, 404, file, gate, unlock, handoff, share, waitlist, invite-land, paid; plan: and one of 8 plan names; via: and a guide's address; lang: and one of 12 languages or other, for a visit from a search engine; and lcp, inp, cls with a kind of page and good, ni or poor, never the measurement. The sources: google, bing, duckduckgo, ecosia, brave, yahoo, yandex, startpage, qwant, kagi, reddit, hn, github, mastodon, linkedin, x, share, other, internal, none.
Pages that never send one: /stats, /revenue, /seats.
The records
Everything the server keeps about people, in one Workers KV store. Found from the kinds of record the code writes; there is no other.
| Record | What it holds | How long |
|---|---|---|
waitlist:… | An email address, when it joined, and the products it is waiting for; marked when it leaves. | Until you ask for it to be deleted. |
seat:… | A person on a Team or Business plan: the name and email address the admin typed, the dates they were added, joined and removed, and which admin key did each. | While the plan has them; a removed seat for 400 days, so a returning key can be told it was taken back. |
seatlog:… | One line of the record of seat changes, for the admin's audit. | Kept; never rewritten. |
seattoken:… | An invitation to a seat, stored only as the SHA-256 hash of its token, with the seat it opens. | Fourteen days, then it expires by itself. |
seatplan:… | The plan and seat limit last seen, so a change of plan is recorded once. | While the subscription exists. |
seatgen:… | Which generation of a Team or Business admin key is current. | While the subscription exists. |
seatseen:… | When a seat's key was last renewed, as a date only, for the admin's “key last renewed” column. | 400 days after the last renewal. |
seatorg:… | On Business, the organisation's name and small logo the admin set, for the covers of its reports, with when and by which admin key. | Kept; the admin can change it, and there is not yet a way to remove it. |
pro-mail:… | That a checkout's key email has been sent, with Stripe's checkout reference and the time. No address. | 400 days. |
gift-start:… | When a gift pass's link was first opened, by checkout reference. | 400 days. |
ref:… | A friend's subscription paired with the inviter's subscription and customer references, and whether the month has been credited. No names or addresses. | Kept, so a month is credited once. |
Nothing about what anybody opens, types or makes is in any of it, because none of that is ever in a request.
What “Pro, locked on the server” means
The code for the Sovereign Suite, the Photo Editor and the Engineering Suite is not in the pages. It is served one file at a time from /pro-code/, and only to a request that carries a Pro key. The key is a short statement of the plan and the date it runs to, signed with an Ed25519 key that only the server holds; the page checks the signature with the public half, and the server checks it again before it hands over any code. A check made in the browser could be edited out of the browser. This one cannot: without a key, the code is simply not there to run.
What /pro-code/ answers when the key is missing or not good enough, read from the route itself. The reason is in the reply's X-Obscura-Pro header:
| Answer | Reason |
|---|---|
| 402 | no key |
| 403 | key not valid |
| 402 | key expired |
| 402 | subscription ended |
| 402 | seat removed |
| 402 | key replaced |
Whether a subscription is still paid is asked of Stripe and kept at the edge for 6 hours, so a cancelled or refunded subscription stops being served within 6 hours. On Team and Business, whether a seat has been taken back is read from the store (which the edge may answer from a copy up to 30 seconds old) and remembered by the server for 30 seconds; a store write takes about a minute to reach every data centre, so a seat taken back stops being served within about two minutes. If Stripe cannot be reached, a signed key that is in date is served: a paying customer is not locked out by somebody else's outage.
The key travels in a cookie that is sent to /pro-code/ and nowhere else, and it carries no name or email address. Status asks /pro-code/ for a real file with no key every thirty minutes, and records whether it was refused.
What has not been done
There has been no external audit. Nobody independent has reviewed this code or tested this site: there is no penetration test report, no SOC 2 and no ISO 27001. Everything on this page is evidence you can gather yourself, not a certificate. If your purchase needs one of those, we do not have it, and security says what we do have and how to report a problem.
The rest of the paperwork: the privacy policy, the terms, the data processing agreement, the threat model, tool by tool, and the accessibility statement.