Security overview · v1.1 · August 2026CardArc holds the things a card business cannot afford to lose: the money ledger, the inventory, and customers' personal details. This document explains the controls that protect them, how each one is enforced, and how we prove every day that it still is.
Every control on these pages exists on the running system. Each one is either probed automatically every 60 seconds or attested with a date and a name. A control that drifts turns red on our own dashboard by itself, and an independent reviewer checks the whole picture every week.
No single control is trusted alone. A request to anything sensitive crosses at least three independent layers: the edge (TLS and routing), identity (who are you), and the application (are you allowed to do this to this row).
Every app listens only on the machine's internal interface, admin identities are allowlisted, access is scoped by role and by workspace, and secrets are files readable by one system user and nobody else.
We check the effective state, not the config file. The dashboard reads what the server is actually doing right now, so a claim on this page cannot outlive the control behind it.
Cece, our AI security officer, audits every product weekly against a written charter, files a dated report and pushes findings into a tracked queue. The CTO fixes; the owner signs off on risk.
The stack is deliberately simple: a Next.js application, a SQLite database per instance, a Caddy edge that issues and renews TLS automatically, and systemd on one hardened Linux server. Simple systems have fewer places to hide a mistake, and every piece is something we can inspect directly.
Reaching an app or a database directly on its port from the internet. Every service is bound to the machine's internal interface. The only road in is through the edge above, and our dashboard probes that binding live, every minute.
Three columns: the gate (who gets in), the code (what the app does with what it is given) and the ground (the machine it all runs on). Each product's checklist maps to these nine squares on our dashboard.
Google SSO with allowlist for admin surfaces; session-based login with httpOnly cookies inside the app; role gates on every admin page and action.
Every field on every public and admin endpoint is typed, ranged and allowlisted on the server (grades, conditions, currencies, quantities).
Auto-renewed TLS on every domain; host firewall active; every app bound to the internal interface; SSH key-only with root login off.
Every query is scoped to the caller's workspace and branch. Every client-supplied ID is ownership-checked before it is used.
Keys live in files with owner-only permissions, outside the repository. Never committed, verified across full git history.
Service health probed every minute; daily automated report; who-did-what audit logs on prices, edits, write-offs and impersonation.
Storefront enquiries, bids, sign-ups and scheduled endpoints are rate-limited so nobody can hammer a store or enumerate data.
Dependency audit gate in CI; automated OS security updates on the host; nightly vulnerability sweep across all apps with an alert file if anything is found.
Nightly database snapshots, a daily off-site copy, whole-server daily snapshots at the host, and a pre-change backup before every risky migration.
CardArc runs many stores on one platform. Each store is a workspace; each workspace has one or more branches. The rule is simple and absolute: a request can only ever touch rows that belong to the workspace it is signed into. We enforce that rule in the application layer and test it as if we were an attacker.
Every inventory, sales, customer, consignment and pre-order query carries the caller's workspace and branch. There is no "list all" path that forgets the filter; helpers that resolve a workspace fail closed when they cannot.
Any ID a client sends (a product, a batch, a campaign, a display, a staff member) is checked for ownership before use. Guessing another store's ID returns "not found", not their data.
Public actions (enquiry, checkout, bids, pre-order registration, consignment search) derive the store from the hostname. Nothing in the request body can point them at another store.
Roles are walled upward: a manager can manage staff but can never alter, demote or remove an owner, and cannot mint other managers. Platform-level flags are separate from workspace roles, so one can never be mistaken for the other.
Support can view a workspace as its owner to help them, for a 30-minute window that is logged. Every non-read request in that mode is blocked at the proxy, before it reaches any code.
Card reference data is shared so every store benefits from one catalog. Everything a store adds on top (costs, prices, notes, photos, manual products) is owned by that store and invisible to others.
The suite creates a sandboxed second workspace and tries, from inside it, to read or change the first workspace's inventory, staff, products, batches, displays, pre-orders, write-offs, relinks and breakdowns. Each attempt must be refused. It is part of the deploy ritual: a release does not ship if the count drops. Alongside it run a 71-assertion isolation check on the private instance, a 50-scenario pre-order simulation and a 192-scenario ledger replay.
Lessons applied. In August 2026 an internal test found a manager able to edit an owner's account because a guard checked a platform flag instead of the workspace role. We ran a full authorisation sweep of every action, route and page, fixed the class of bug in four commits, wrote the rules above into the engineering guide, and added the cases to the suite. That is the loop working as designed.
Buys, sales, deposits, payouts and cost corrections are lines. A cancellation adds a reversal that references the original; both stay visible. Statistics exclude reversed lines automatically.
A sale that touches stock, ledger, consignor split and receipt happens in one database transaction. It either fully happens or not at all, so two cashiers acting at once cannot corrupt a count.
Price changes, product detail edits, write-offs and impersonation sessions each have their own audit log with the acting account and timestamp, and an undo path where it is safe.
Every change is committed and pushed to GitHub. Uncommitted drift on the server shows red on the dashboard.
Secret scanning (gitleaks), dependency audit, and update alerts. A leaked key or a known vulnerability fails the build.
Type check, build, isolation suite, pre-order and ledger simulations, staging smoke test on a separate instance.
systemd-managed services with atomic restarts. Rollback is one revert and one restart. A pre-change backup is taken before risky migrations.
Service health, TLS expiry, git sync, secret file permissions, loopback binding, firewall. Drift shows red without anyone looking.
Cece reviews every product against a written charter and files a dated report. Findings enter a tracked queue and are closed by the CTO.
Databases, configuration, secrets and service definitions, with dated restore points retained.
Database snapshots leave the server every day. The hosting provider also keeps a daily whole-server snapshot that covers images.
An automated reconciler cross-checks inventory against the financial ledger and flags any drift.
Restore from any retained point; a pre-change backup is taken before every risky migration.
We publish gaps as well as passes. A security page that only lists green ticks is not one you should trust.
| Control | State | Status |
|---|---|---|
| Apps bound to internal interface | All services, probed live | Live probe |
| HTTPS on every domain | Auto-issued, auto-renewed, expiry probed | Live probe |
| Browser security headers and CORS | HSTS, anti-framing, nosniff on all domains; CORS closed except two landing-page endpoints pinned to cardarc.app | Live probe |
| Host firewall and SSH hardening | Firewall active; key-only SSH, root login off, verified at runtime | Live probe |
| Secrets outside the repo, owner-only | Never in git history; permissions probed; gitleaks in CI | Live probe |
| Tenant isolation | 102/102 suite; full authorisation sweep closed 23 Aug 2026 | Verified |
| Parameterised SQL, server-side validation | Attested per release by the CTO | Attested |
| Known vulnerabilities | npm audit 0 across all apps; OS auto-updates active | Verified |
| Privacy, terms, delete-my-data | Published and probed; self-service deletion live | Live probe |
| Backups and off-site copy | Nightly plus daily off-site and host snapshots | Verified |
| Auth 30-second test | Automated half passes after August auth changes; manual browser half scheduled | In progress |
| Key rotation runbook | Zero-downtime rotation procedure to be written | Planned |
| Application error tracking | Logs, probes and daily reports today; crash-level alerting to add | Planned |
| Single-server resilience | Accepted at current scale; recovery path tested; revisit with growth | Accepted risk |
Want the detail? Ask for the latest weekly review, the deploy ritual, or a walkthrough of the isolation suite. We will show you the live dashboard rather than a slide.