Canaryflux
Security

Security

How we handle your data, what we encrypt, who hosts what, and how to reach us if you find something we missed.

Last updated · 11 June 2026
Encryption in transit TLS 1.3 Cloudflare-managed
Password hashing bcrypt cost factor 12
Session tokens JWT · HS256 14-day TTL
PII at LLM boundary Masked before every call

Data handling

We store what's needed to run device-profile scans, bill you, and keep the onboarding emails sane. Specifically:

  • Account data — email, bcrypt-hashed password, first/last name, email-verification status, and an account-creation timestamp. Stored in Postgres when DATABASE_URL is set; otherwise in JSON files inside the application's data directory.
  • Scan inputs — the URL you submit, the device profiles you select, and any per-run options.
  • Scan outputs — captured screenshots, masked DOM excerpts, console logs, and the findings we produce.
  • Usage records — counts of scans run per month for plan enforcement, plus your Stripe customer ID for billing reconciliation.
  • Operational records — hashed email-verification and password-reset tokens (with expiry), your active plan tier, first-scan timestamp, onboarding-email progress flags so we don't double-send nudges, and your nudge opt-out preference if you've set one.
  • Per-project scan-permission attestation — for each project, the consent text you agreed to (full text snapshot), the attested origin, timestamp, and the IP + User-Agent at grant time. Append-only history, capped at 50 entries per project.
We do not store your scanned site's source code, and we do not persist any cookies the scanned site issues back to the browser. On Free and Solo, the scanner runs as a fresh logged-out visitor on every run. On Pro and Studio, if you've configured authenticated scanning for a project, the scanner injects the encrypted cookie or Authorization header you supplied for that run only — see the Authenticated scanning section below.

Encryption

In transit. All traffic to canaryflux.com and the dashboard API is TLS 1.3, terminated at Cloudflare. All canaryflux.com hostnames are HTTPS-only at the Cloudflare edge.

At rest. Postgres and the application volume are Railway-managed and encrypted at rest under Railway's storage policy. Screenshots are written to the application volume; Postgres holds only the relative paths plus the rest of the application data.

Column-level for authenticated-scan secrets. Pro and Studio projects can store a session cookie or Authorization header so the scanner can reach pages behind a login. We never persist those values in plaintext — each one is individually Fernet-encrypted (AES-128-CBC + HMAC-SHA256) under a key derived via HKDF-SHA256 from the server-side master secret. The master secret lives only in the runtime environment; it is never written to disk, never logged, and never round-tripped through the API.

Signed asset URLs. Screenshot and console-capture URLs are HMAC-SHA256 signed with a 7-day TTL. The payload is domain-separated from JWT signing so a screenshot signature can't be replayed as a session token. The signing secret never leaves the server.

Scan-permission attestation audit trail. Every project records the consent text the owner agreed to (full text snapshot, not just a version string), the attested origin, the timestamp, and the IP + User-Agent at grant time. Older attestations are preserved on an append-only history list bounded at 50 entries per project so abuse investigations can be reconstructed months later.

Authentication

Passwords. bcrypt with a cost factor of 12. We never see, store, or log plaintext passwords.

Sessions. JWTs signed with HS256, 14-day TTL, reissued on every login. Tokens live in browser localStorage for the dashboard and are sent as bearer tokens to the API. JWTs are stateless (no server-side session table) — a token is valid until its exp claim or until the master secret is rotated.

Login lockout. Five consecutive failed attempts against the same email lock the account for 15 minutes.

Password resets. Single-use tokens with a 1-hour TTL. Requesting a new reset invalidates the previous one immediately.

Email verification. Required to run any scan from a signed-in account (free or paid). Tokens are single-use with a 24-hour TTL; requesting a new verification email invalidates the previous token.

Scanner safety

Before we point Playwright at any URL, the request runs through an SSRF guard (_validate_scan_url). We reject:

  • Non-http(s) schemes
  • Loopback (127.0.0.0/8) and link-local (169.254.0.0/16) addresses
  • RFC 1918 private ranges (10/8, 172.16/12, 192.168/16)
  • Cloud metadata endpoints (169.254.169.254 and friends)
  • URLs with embedded credentials (user:pass@host)

Every scan runs in a fresh, sandboxed Chromium browser context that is destroyed when the scan ends. No cookies, localStorage, or extensions carry over between runs. (On Pro/Studio authenticated scans, a customer-supplied session cookie or Authorization header is injected into that fresh context for the duration of the run only — see Authenticated scanning above.)

Device profiles — what they are

Honest disclosure, because the language elsewhere in this industry is loose: when Canaryflux scans your URL on iPhone 15 Pro, Pixel 8, iPad Pro, or any of the device names in our matrix, it is not running on physical hardware. We do not own or operate a device lab.

What we actually do is render your URL inside a Chromium browser context that Playwright has configured with the device’s real user-agent string, its viewport dimensions, its device-scale factor (DPR), its mobile touch-event flag, and its color-gamut metadata. That context behaves to the page the same way the device’s real browser would — serves the same media queries, triggers the same JS device-detection branches, fires the same touch events, downloads the same image variants.

What this catches: CSS layout bugs that only show at specific viewports, broken touch-event handlers, responsive images that load the wrong source, JS that mis-detects the user agent, modal traps on mobile, post-tap rendering issues, console errors that only fire on mobile UAs. The vast majority of cross-device bugs that ship in marketing-site code live in this category.

What this does NOT catch: hardware-specific failures — camera/microphone permission flows, real GPU rendering quirks, vendor-modified WebKit/Blink builds shipped by Samsung Internet or MIUI Browser, physical-device thermal throttling under load, accelerometer/gyroscope code paths, or the specific way OLED panels handle near-black gradients. If your bug only shows on a real customer’s Galaxy A14 running Samsung Internet 23, no emulation cloud will catch it — you need either a physical device or a real-device cloud service like BrowserStack or Sauce Labs.

We chose emulation over a device lab because it lets us deliver scans in minutes (vs. device labs that take ten times longer) at a price point indie hackers can sign up to without a procurement process. For the marketing-site, auth-flow, and visual-regression class of bugs Canaryflux is built to catch, the trade-off is honest.

Authenticated scanning — what we see

On Pro and Studio, you can paste a session cookie or Authorization header into a project so the scanner can reach pages behind a login. We want to be explicit about what that means in practice, because it’s the single biggest exposure decision a customer makes on the platform.

What we encrypt. The cookie or header value itself is individually Fernet-encrypted (AES-128-CBC + HMAC-SHA256) using a key derived via HKDF-SHA256 from the server’s master secret before it ever hits the database. You looking at the Postgres row see only ciphertext. We can’t recover the value from a backup, a logs dump, or a stolen read-replica without the master secret.

What encryption does NOT do. The encryption protects the cookie at rest. It does not redact the data the cookie unlocks. When the scanner loads a page using your session, everything that page renders — customer names, billing details, internal dashboards, PII in form fields — ends up in:

  • The full-page screenshot we capture on each device profile.
  • The masked DOM excerpt we capture (text PII like emails and phone numbers is replaced with placeholder tokens; structured customer data like names and addresses is not).
  • The findings our AI extracts from the above.

The screenshot is sent to Google (Gemini API) for vision-based finding extraction on the primary path, and to Anthropic on the fallback path when Gemini is unavailable, under each provider's commercial API terms. Canaryflux operates exclusively on the paid Gemini API tier (whose commercial terms prohibit training on customer inputs and outputs) and on Anthropic's commercial API (whose terms likewise prohibit training on customer inputs and outputs). It’s also written to disk on our Railway server and served back to your dashboard via HMAC-signed URLs with a 7-day TTL. The full screenshot file is deleted from disk on our retention sweep — which runs on app startup and at least every 24 hours thereafter — once it ages past your tier’s history window (7 / 30 / 90 / 365 days). DOM-text PII masking (Privacy at the LLM boundary below) covers emails, phones, API keys / tokens (JWTs, Bearer headers, Stripe / AWS / GitHub / Slack token patterns), credit cards, and SSNs in the masked text channel; structured customer data that isn’t in those patterns (names, addresses, balances) is not redacted and is visible to the vision models (Gemini primary, Claude fallback) in screenshots.

Internal access. A small number of Canaryflux engineers can open stored screenshots and DOM excerpts when strictly needed for customer-support tickets you open or abuse-of-service investigations. Access is logged. We do not browse stored scan content for any other purpose; we do not use it for marketing, training, or analytics.

Controller vs processor. When you paste a session that exposes your own end-customers’ personal data, GDPR and LFPDPPP treat you as the controller of that data. Canaryflux becomes a processor of it the moment scanning begins, and our AI vision sub-processors (Google for Gemini on the primary path, Anthropic for Claude on the fallback path) handle it under our agreements with each of them. You need a lawful basis for the processing and, for EU/UK personal data, a Data Processing Agreement with us before the scan runs. Email dpa@canaryflux.com — we ship a standard DPA with SCCs on request.

Our recommendation. Use a low-privilege test-account session when you can. Avoid pasting admin, root, or owner-level sessions for production tenants. If your product has a per-customer dashboard, scan with a seeded test customer’s session, not a real one. The fewer privileges your scanning session has, the less customer data ends up in screenshots and the lower the blast radius if you ever get a takedown request, a DSAR, or a security review.

Special-category data. Do not paste sessions that reveal health, financial-account, biometric, government-ID, or other special-category data (GDPR Art 9 / LFPDPPP “datos sensibles”) unless you have express consent from those data subjects and a written processor agreement with us covering that category. Mexican LFPDPPP penalties for sensitive-data mishandling are materially higher than ordinary-data penalties; the conservative default is “don’t paste it.”

Revoking. The "Authenticated scan…" item in each project’s kebab menu has a Clear saved auth button that wipes the encrypted cookie + header for that project. The next scan goes back to the logged-out view. The DELETE /api/projects/{id}/consent endpoint additionally clears the project’s scan-permission attestation (immutable history of what was attested and when is preserved on the audit row).

Privacy at the LLM boundary

Every piece of DOM-derived TEXT we send to our vision LLMs (Google Gemini on the primary path, Anthropic Claude on the fallback path) passes through mask_pii first. The actual replacement tokens that ship to the model:

  • Email addresses → [EMAIL_REDACTED]
  • Phone numbers (E.164 / US / common formats) → [PHONE_REDACTED]
  • JSON Web Tokens (three dot-separated base64url segments) → [JWT_REDACTED]
  • Bearer / Token / Basic Authorization header values → [TOKEN_REDACTED]
  • Stripe live/test keys (sk_, pk_, rk_) → [STRIPE_KEY_REDACTED]
  • AWS access keys (AKIA, ASIA) → [AWS_KEY_REDACTED]
  • GitHub personal-access-token / fine-grained / app patterns → [GH_TOKEN_REDACTED]
  • Slack tokens (xoxb-, xoxp-, xapp-, etc.) → [SLACK_TOKEN_REDACTED]
  • Generic token= / api_key= / password= / secret= values → key=[TOKEN_REDACTED]
  • Brand-matched + Luhn-validated credit-card numbers (Visa, MC, Amex, Discover, Diners, JCB, UnionPay) → [CC_REDACTED]
  • US Social Security Numbers (NNN-NN-NNNN) → [SSN_REDACTED]

The mask runs on the dom_excerpt, runtime signals, and any candidate title/element/evidence values before they hit the prompt or the cache key. Screenshots are a separate channel — they're the vision model's primary signal, so we send them unmodified. If your page visibly renders PII (a logged-in customer's email in the header, for example), assume those pixels reach our AI vision sub-processors as image bytes — Google Gemini on the primary path and Anthropic Claude on the fallback path — under each provider's commercial API privacy terms. Scan pages where that's acceptable, or sign out first.

Hosting & isolation

Dashboard. Vercel (US-East). Static HTML and Vite-built React shell, no server runtime.

API + scanner workers. Railway, US region. Each scan runs in a fresh, sandboxed Chromium browser context, isolated from other in-flight scans. (Application-level Python state — rate-limit buckets, in-memory caches — does live in the worker process for the lifetime of the process; the per-scan isolation is at the browser-context level, not the Python level.)

Database. Railway-managed Postgres, encrypted at rest under Railway's storage policy.

DNS & edge. Cloudflare proxied with TLS termination.

Sub-processors

These are the only third parties that ever touch your data:

VendorPurposeRegion
Google (Gemini API)Primary vision LLM (Gemini 2.5 Flash-Lite) for finding detection & verification, used for all tiers including Free. Canaryflux operates exclusively on the paid Gemini API tier, whose commercial terms prohibit training on customer inputs / outputs. Processing under Standard Contractual Clauses where EU/UK personal data is involved.US
AnthropicFallback vision LLM (Claude Sonnet 4.6) for finding detection & verification when the primary provider is unavailable. Commercial API terms prohibit training on customer inputs / outputs.US
RailwayAPI server, scanner workers, PostgresUS
VercelMarketing site + dashboard static hostingUS (global edge)
Vercel AnalyticsAnonymous page-view telemetry across both marketing and dashboard pages (no cross-site tracking)US
CloudflareDNS + TLS terminationGlobal
StripeSubscription billing & payment processingUS
ResendTransactional email (verification, alerts)US
SentryServer- and client-side error monitoring (stack traces, URL path, browser type — emails / cookies / Authorization headers stripped before send)US

Retention & deletion

Findings and screenshots are retained for the window defined by your plan:

  • Free — 7 days
  • Solo — 30 days
  • Pro — 90 days
  • Studio — 365 days

Account deletion. From the dashboard's Account settings, you can delete your account. We immediately wipe your user record, scan history, findings, screenshots, and any pending email-verification or password-reset tokens. Stored screenshots are removed in a background task right after the user record is gone. Because JWTs are stateless, any existing browser session becomes unusable as soon as the user record is gone — every API call rejects with 401. Stripe's billing-history retention is governed by their own policies.

Shareable finding links use 24-byte cryptographically random URL-safe tokens with a 90-day TTL. The server stores only the SHA-256 hash of each token. You can revoke any link from the finding's detail view; revocation is immediate.

Responsible disclosure

If you find a vulnerability, please email us before making it public. We commit to:

  • Acknowledge your report as quickly as we can — typically within a business day
  • Share a fix plan or initial triage within a week of acknowledging
  • Credit you publicly once a fix ships, if you'd like
  • Never pursue legal action against good-faith research
Send your report to security@canaryflux.com. Include reproduction steps, impact assessment, and your preferred contact method. Report a vulnerability