Sophie Well calculations are static and client-side. There are no user accounts or telemetry. The sole hosted write path is an isolated, user-initiated tool-report Worker backed by D1. Cloudflare Turnstile loads only after a user opens the report dialog.
Please report security issues privately. Do not open a public GitHub
issue. Email hi@claygood.com with:
- A clear description of the vulnerability and its impact.
- Step-by-step reproduction instructions.
- The affected commit or deployed URL where you observed the behavior.
You will receive an acknowledgement within 72 hours. Confirmed issues will
be patched on main and deployed to sophiewell.com within 7 days for
high-severity findings, 30 days for medium-severity, and 90 days otherwise.
Security fixes are noted in CHANGELOG.md under a
### Security heading.
The deployed site at https://sophiewell.com always tracks main. Older
commits are not maintained; security fixes are applied to the current
main only.
Documented in docs/threat-model.md. High-level summary:
- Normal tool and asset traffic is static. The report Worker is API-only, separately routed, rate-limited, and has no asset binding.
- A strict Content Security Policy is set both via
<meta>and via real HTTP response headers in_headers(Cloudflare Static Assets) andscripts/serve.mjs(local dev). The deployed CSP allows only same-origin resources pluschallenges.cloudflare.cominscript-srcandframe-srcfor the user-opened Turnstile widget. The'wasm-unsafe-eval'token permits only same-origin WebAssembly compilation for the vendored on-device OCR engine (lib/pa/ocr.js); it does not permiteval,Function, or inline scripts. - All data shards under
data/are integrity-verified at build time byscripts/verify-integrity.mjsagainst SHA-256 hashes recorded in each dataset'smanifest.json. - No
localStorage,sessionStorage,IndexedDB, or cookies are used. Calculator state is encoded in the URL fragment (location.hash). connect-src 'self'prevents arbitrary off-origin data exfiltration. The report POST is same-origin and the Worker validates its exact origin, body, tool ID, URL, Turnstile token, and size before storage.
- One bounded runtime exception. Cloudflare Turnstile is fetched only after a user opens the report dialog. There are no analytics, trackers, fonts, or other external scripts.
- Pinned dev dependencies. All entries in
package.jsondevDependenciesuse exact versions (no^or~), including ESLint, Playwright, OpenLore, and Wrangler. - Pinned runtime engine.
engines.nodeis constrained to>=22.23.2 <23;.nvmrcrecords the exact patch for local and CI builds. - Reproducible SBOM.
scripts/build-sbom.mjsemits a CycloneDX 1.5sbom.jsonand a human-readablesbom.md. They include a valid deterministic UUID, hashes for browser and Worker entry assets, everylib/,views/, MCP, and vendored source file, both complete lockfiles, and the on-demand Turnstile component. Generated pages and data shards are covered by their build and integrity manifests. Runnpm run sbomto regenerate. - Data integrity.
npm run data:verifyre-hashes every shard underdata/and compares against the value in its dataset manifest. CI fails the build on any mismatch.
- HTTPS enforced site-wide; HSTS preloaded
(
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload). X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: no-referrer,Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Embedder-Policy: require-corp,Cross-Origin-Resource-Policy: same-origin.Permissions-Policydenies camera, microphone, geolocation, payment, USB, and accelerometer.- A service worker (
sw.js) caches the bundle for offline use; cache keys include theBUILD_HASHso a new deploy invalidates old caches.
- Calculation is local. A report is sent only after the user opens the dialog, is told which context categories will be attached, completes Turnstile, and chooses Send report.
- Reports contain a canonical tool URL and an optional note. An unchecked, explicit option can add bounded input rows and results. No report URL contains query parameters or URL state. Sensitive tools and patient-document generators also attach no form fields or generated output.
- Raw IP addresses, user agents, identities, email addresses, and Turnstile tokens are not stored by the application in D1. A secret-keyed daily HMAC supports rate limiting and cannot be used to track a reporter across days.
- A daily Worker schedule deletes rate counters after 14 days, resolved or
wont_fixreports after 90 days, and every report after 180 days. - The footer links to
claygood.comandgithub.comonly when the user explicitly clicks. Ordinary tool use makes no automatic external requests; opening the report dialog deliberately loads Cloudflare Turnstile.
See CHANGELOG.md entries marked ### Security.