Privacy & data
Where SiteCMD stores your data, what network requests it makes, and what those requests do and don't contain.
This page is the specifics behind Why local-first. What data lives where, what gets sent over the network, and what those requests actually contain.
What stays on your machine
Everything about your projects lives in a single per-user SQLite database, opened in WAL mode for safe concurrent reads and writes, inside SiteCMD’s app-data directory:
- macOS:
~/Library/Application Support/com.sitecmd.app/ - Windows:
%LOCALAPPDATA%\com.sitecmd.app\(falls back to%APPDATA%) - Linux:
$XDG_DATA_HOME/com.sitecmd.app/(defaults to~/.local/share/com.sitecmd.app/)
Inside that directory you’ll find:
sitecmd.db- the SQLite database (plussitecmd.db-walandsitecmd.db-shmwhile the app is running)audit.log- JSONL log of sensitive operationslogs/- rolling application logs
The database contains:
- Scan results. Every check that ran, what it found, severity and confidence, timestamps.
- Scan history. Every scan, retained per your prefs (default: 50 most recent per site).
- Source-audit findings. When Code Scan runs, results land here. The source files themselves stay where they are; only the findings are persisted.
- Project configuration. URLs, environments, linked source folder paths.
- Issue state. Dismissals, fixes, blocked-with-reason notes.
- Integration event data. When you connect an integration (analytics, search console, uptime), the events SiteCMD pulls are stored locally for correlation. The integration’s own data still lives in the integration; SiteCMD doesn’t mirror it wholesale.
What we never see
Your source code. The source audit runs entirely locally. The fix briefs your coding agent picks up are built locally and served over MCP, which also runs on your machine. No path, no file content, and no line number has anywhere to go, connected or not.
Your scan results, unless you connect that site. There is no sync running behind your back: connecting a site is a thing you set up in Settings, and an installation that never does it never contacts the connected service. Once you connect one, findings for that site do reach a SiteCMD server, and Connected sites below is the exact list.
Your project configuration, unless you connect that site. Project names, environment lists, and local history stay local. A connected site’s URL is registered with the service, because that is what the service is grading.
Optional local database inspection
Ordinary Code Scan does not read values from .env, .env.local, or other non-example dotenv files and does not open a project database. Source references and scrubbed example templates such as .env.example remain part of ordinary static analysis.
The scan form has a per-run Inspect local database schemas option, off by default. Enabling it permits SiteCMD to read local dotenv values only to discover a database target. It may open SQLite files inside the linked project read-only, or connect to Postgres only when every configured host and hostaddr is loopback or the connection uses a local Unix socket. Postgres catalog queries run in a read-only transaction. SiteCMD reads table, column, index, constraint, policy, and migration metadata, never application table rows. Remote and mixed-host targets are rejected. The option resets to off after the run and scheduled scans never enable it.
This inspection stays on the user’s machine. It is unrelated to SiteCMD’s own local sitecmd.db, does not require any globally configured database URL, and has no server-side or per-user setup step.
Network requests SiteCMD makes
A handful of outbound requests happen on purpose. Each is narrow and named.
Connected sites
Connecting a site is the one feature that sends findings to a SiteCMD server (connect.sitecmd.com), and it is off until you set it up. There is no background sync loop; the app talks to that host when you connect, verify, or sync, and at no other time.
Before each deploy of that server, the pipeline keeps a copy of its database for 30 days in the same Cloudflare account, so a migration that goes wrong can be undone. Nothing else holds a copy.
What it sends is a finding’s identity, not its evidence:
- The site. Its URL, and the alias you gave it.
- Each finding. The check that fired, its severity and confidence, and the route on your own site it fired on.
- Each code finding. The check, plus a keyed hash of where it fired, computed under a per-project key that stays on your device, plus how many times it fired in that location. The hash is what lets the service recognise the same finding in next week’s scan without ever holding anything that resolves to a file.
- The scan itself. Engine and check-registry versions, the capability manifest digest, which checks completed, the commit SHA the code side was observed against, the detected framework and version, and timing measurements with the route each was taken on.
- Your decisions, once. When you first connect, the dismissals and claimed fixes you already made locally, so a site you have been triaging for months does not arrive as a fresh list of problems.
Your source code, file paths, file contents, line numbers, page HTML, response bodies, screenshots, dependency lists, lockfiles, and integration credentials have no field in that payload. The app renders the exact object before it sends one, so what you read is the bytes rather than this description of them.
Connecting a site also lets SiteCMD scan it from our own infrastructure, on a schedule and with the app closed. Those requests leave Cloudflare’s network rather than your machine. There is no fixed SiteCMD address to allowlist and none to publish: outbound requests from Cloudflare Workers leave the anycast network, and the source address varies by the location that ran the scan. They carry the same SiteCMD/<version> (+https://sitecmd.com/scanner) identity a desktop scan sends, and the scanner page explains what is and is not provable about that.
A hosted scan also contacts cloudflare-dns.com, Cloudflare’s public DNS-over-HTTPS resolver. It receives each hostname the transport scanner or isolated browser is about to fetch as an A and AAAA lookup before the request, including public subresource hosts named by the page, so an address in a blocked range is refused. For the connected site’s registrable domain, it also receives the names used to query A, AAAA, SPF, DMARC, common DKIM selectors, MX, DNSKEY, CAA, and the www CNAME/address posture. It is not told which account caused the query.
Hosted scans send recognizable front-end library names and exact versions found in public script URLs to api.osv.dev, then retrieve details for any advisory IDs OSV returns. They send the registrable domain to rdap.org, which redirects to the TLD registry’s public RDAP endpoint for the registration-expiry check. Neither request includes page HTML, source files, credentials, scan results, or the connected account identity.
The browser-analysis half loads each selected route in a fresh isolated browser context. Like any browser visit, it may request public scripts, stylesheets, images, fonts, and read-only fetches from hosts named by that page. SiteCMD intercepts those requests, fetches each bounded response through Cloudflare Workers public egress, and fulfills it into the browser. Target hosts may see the requested URL, request headers, and cookies the page set during that isolated load.
Scheduled scans can run this browser layer unattended while your laptop is closed, so the same subresource behavior applies even when nobody is watching the page load.
SiteCMD intercepts every browser request. It blocks unsafe request methods, service workers, WebSockets, media streams, downloads, popups, non-web schemes, and targets in blocked address ranges. The context contains none of your personal browser cookies or saved sessions and is destroyed after the route is evaluated.
Page bodies, rendered DOM, query strings, screenshots, and browser storage are never written to a SiteCMD store, log, trace, or error report. Only derived verdicts and timing measurements survive the hosted scan.
The CLI uses one site-scoped CI token for exactly four connected operations: reading the deployment ordering cursor, gate, deploy, and connected --submit. The ordering cursor contains only the site’s current deployment identity, whether that credential supports GitHub OIDC attestation, and, when the credential owns the site’s selected publish authority, that authority’s kind, id, epoch, activation time, and current publish sequence. It contains no findings or other account data. gate sends a code-side candidate from the CI runner but persists nothing: it answers which findings are new against the site’s baseline and changes nothing about what the site is known to be. deploy records deployment history, while connected --submit records or converges the deployment and its privacy-preserving code findings.
What a connected site sends is aged out on a schedule, executed by a daily job on the service:
- 90 days: the site’s event stream, deployment history, and timing measurements.
- 30 days after you disconnect a site: everything the disconnect retained, deleted outright.
- 7 days: request receipts (the idempotency records that make retries safe).
- One year: the erasure receipt - the one record that outlives its site, naming the site identifier and domain and nothing else, kept so you can prove the erasure happened.
A connected site’s current findings and your decisions about them are kept for as long as the site stays connected: they are the baseline the service grades against, and deleting them on a timer would silently un-know what your site is.
Scans
The live-site engine fetches your URL plus a handful of related URLs (sitemap, /robots.txt, alternate URLs for security headers). Every URL SiteCMD requests passes through a network policy gate that blocks:
- Non-HTTP schemes (only
http://andhttps://are allowed) - Cloud metadata endpoints (
metadata.google.internaland similar) - Private RFC1918 IPs (
10.x,172.16-31.x,192.168.x) and other non-routable ranges, including IPv4-mapped IPv6 forms
Localhost and loopback addresses are allowed by default so you can scan your local dev server. The point of the gate is to stop a malicious site from redirecting SiteCMD into your internal network through a 3xx response.
Local browser analysis during desktop web scans
To measure Core Web Vitals, and to run axe-core when accessibility analysis is on, SiteCMD loads the page in a hidden webview. That window is private: it shares no cookies, storage, or cache with your browser or with earlier scans, it cannot open windows or start downloads, and every top-level navigation it attempts, including redirects and meta refreshes, is re-checked against the same policy gate, so a public page cannot redirect the analyzer to an internal address.
What that desktop gate does not cover is the page’s own subresources. Once the page loads, its images, scripts, stylesheets, fonts, and fetch calls are issued by the platform web engine to the hosts the page names. The desktop webview does not provide the same request interception as the hosted browser adapter, so those requests are in the same position they would be if you opened the page in your own browser. Nothing from them reaches SiteCMD; the measurements stay local. If that matters for a particular site, run its desktop browser scans manually rather than on a schedule.
Integrations
When you connect an integration, SiteCMD talks directly to that service’s API using credentials you provide:
- Analytics: GA4 (Google), Plausible
- Search: Google Search Console, Bing Webmaster
- Uptime / Hosting: UptimeRobot, Cloudflare
- Source / Deploys: GitHub
- Performance: PageSpeed Insights
- Issue tracking: GitHub Issues, Jira
These requests go from your machine to the integration’s endpoint. They don’t pass through SiteCMD servers.
Dependency lookups
The dependency update check looks up package versions in public registries depending on the ecosystems in your project: registry.npmjs.org (npm), pypi.org (Python), repo.packagist.org (PHP/Composer), crates.io (Rust), rubygems.org (Ruby), proxy.golang.org (Go), api.wordpress.org (WordPress plugins and core), and updates.drupal.org (Drupal modules). Each lookup sends only the package name in the request URL or as a query parameter. Your lockfiles and source files are never sent.
SiteCMD also queries the OSV vulnerability database (api.osv.dev) in a single batched POST request to check packages against known security advisories. The request contains package names and currently installed versions. Desktop and hosted web scans use the same database to check recognizable front-end libraries whose names and versions appear in public script URLs. Hosted requests leave SiteCMD’s Cloudflare infrastructure; desktop requests leave your machine. No source file content, lockfiles, or credentials are included.
PageSpeed Insights (web performance)
Web Vitals uses Google’s PageSpeed Insights service (www.googleapis.com). This call is made on demand from the desktop app - when you open the Web Vitals detail or refresh a project dashboard’s performance signals - and does not run on a background schedule. The request sends the URL being measured and the strategy (mobile or desktop). If you add an optional PageSpeed API key under Settings, it is stored in your OS keychain and appended to these requests to raise Google’s rate limit; without one the call is made without a key. No scan results, source file content, or project configuration are sent.
Domain checks during web scans
The web scan’s domain checks look up the scanned site’s public DNS records (SPF, DMARC, DKIM, MX, DNSKEY, CAA, and a CNAME/address lookup for the www subdomain). Desktop scans use your machine’s configured resolver. Hosted scans use Cloudflare’s public DNS-over-HTTPS resolver at cloudflare-dns.com. To read the public registration expiry date, both use rdap.org, which forwards to the TLD registry’s public RDAP endpoint; the request leaves the device for a desktop scan and SiteCMD’s Cloudflare infrastructure for a hosted scan. These requests contain only the relevant public DNS names or registrable domain.
License activation and validation
When you activate an existing license, SiteCMD sends your license key and a machine-scoped activation identifier to LemonSqueezy (api.lemonsqueezy.com). The activation identifier is a SHA-256 hash of your device hostname and username encoded as a 16-character hex string - your raw hostname and username are never transmitted. Ongoing validation requests send your license key and the per-activation instance ID returned at activation. We don’t send anything about your projects, scans, or source.
License validation runs at app startup and periodically thereafter. If your machine is offline, SiteCMD enters an offline grace period during which connected-service and maintained-catalog credentials remain valid. Beyond the grace period those credentials may pause until the next successful validation; local product capabilities do not.
Fix-guide catalog
Eligible connected-service and existing-license entitlements receive maintained guidance as a signed catalog the app downloads in the background. Activating such an entitlement also registers the installation with the catalog service (activation.sitecmd.com), sending your license key, the same per-activation instance ID described above, and a random single-use nonce. The exchange returns an opaque access token; your license key is never sent to the catalog after that. Deactivating the license sends the same identifiers to release the installation.
The app then checks catalog.sitecmd.com about once a day. That request carries only the opaque token, the app version, and the installed catalog version and channel - never your license key, and never anything about your projects, scans, or source.
Catalog packs are cryptographically signed (minisign) with a key separate from the update key, and the app verifies the signature before using a pack, so a compromised catalog server can’t alter the fix guidance you see.
Auto-updates
The desktop app polls releases.sitecmd.com for new versions and downloads the update bundle from there when one’s available. The polling request sends your current version and platform string. It doesn’t send anything about your projects.
Update bundles are cryptographically signed (minisign). The app verifies the signature before installing, so a compromised release server can’t push you a tampered binary.
Crash reports
SiteCMD does not send crash reports anywhere by default. If you opt into crash and error reports,
the app sends sanitized diagnostics to Sentry (o4511662343127040.ingest.us.sentry.io). Session replay,
autocapture, broad tracing, and default personal data collection are disabled. The payload is
scrubbed to remove URLs, paths, emails, tokens, license keys, source snippets, request bodies,
provider responses, and raw logs.
The visible preference is not the only control. The native app backend stores crash-report consent, defaults it to off, and requires native confirmation before enabling it. Renderer code cannot choose the destination or send an arbitrary Sentry body: it submits a typed diagnostic event, the backend checks consent again, sanitizes and validates the event, constructs the envelope, and uses only the baked SiteCMD Sentry ingest host.
Optional usage analytics
Usage analytics are also off by default and controlled separately from crash reports. If enabled,
SiteCMD sends anonymous workflow events to telemetry.sitecmd.com, a SiteCMD-controlled Cloudflare
Worker. These events help us answer product questions like which workflows are used, which scan
flows fail, and what app versions are active. Each event carries the app version, build channel,
operating system family, an anonymous install identifier, and your subscription tier as a plan name
only (“free” for the complete local workbench, or the name of an existing subscription’s legacy plan; never a license key or billing details). They do not include scan
targets, project names, source code, credentials, raw logs, page content, or integration data.
Usage consent is also stored and enforced by the native backend. The renderer can request only the documented registration, event, deletion, and crash-report operations; it cannot provide a network destination or arbitrary bytes. The backend validates the exact event and property allowlists and selects the fixed SiteCMD telemetry host. Turning consent off therefore disables the transport itself, not just the Settings control.
Where credentials live
Integration credentials (API keys, OAuth tokens) are stored in your operating system’s credential store, not in the SiteCMD database:
- macOS: Keychain
- Windows: Credential Manager
- Linux: GNOME Keyring, KWallet, or any
secret-tool-compatible store
The SQLite database stores references (which integration is connected, when it was last used), but never the secret itself. If someone gets your SiteCMD database file, they can’t extract your tokens from it.
The audit log
Every sensitive operation writes a line to audit.log in your SiteCMD storage directory. JSONL format, one line per operation. Recorded operations include:
- License validation calls
- OAuth flows (start, success, failure)
- Webhook deliveries
- Destructive data-admin operations (e.g. project deletion)
- Panic events from the Rust backend
The audit log redacts secrets before writing. License keys, OAuth tokens, and webhook secrets are reduced to a key prefix and a hash. Email addresses are reduced to a domain. Hostnames are kept; raw URLs with query strings are not.
You can read the audit log at any time. SiteCMD does not upload it.
Data export
Two ways to get your data out:
- CSV exports from the Issues page (filtered or full) for spreadsheet workflows.
- Report HTML exports for stakeholder-facing snapshots.
There’s no “export everything to one file” yet. Your SQLite database is the source of truth, and you can back it up directly by copying the storage directory.
Data deletion
To wipe a single project, delete it from Settings → Projects. SiteCMD removes the project’s scan history, findings, integration data, and any associated keychain entries.
To wipe everything, quit SiteCMD and delete the storage directory listed at the top of this page. Integration credentials in the OS keychain are separate; search for “SiteCMD” entries in Keychain Access (macOS), Credential Manager (Windows), or secret-tool (Linux) and remove them.
What we’d do if compelled
Since your data lives on your machine, we don’t have it to hand over. We can’t subpoena what we can’t see. License records (who paid us for what) we do have, and we’d respond to a legitimate request for those the same way any business does. Your scans aren’t part of that.
Reporting a security issue
If you find a security issue in SiteCMD itself, see Security disclosure.