Swift DMS — Security & Compliance
Last reviewed: 2026-05-21
Independent dealers handle three classes of sensitive data: customer
personally identifiable information (PII), financial records (purchase /
sale prices, banking), and vehicle history reports (VIN, accident
history). This document is the high-level overview of how Swift DMS
protects each.
Data classes + handling
| Class | Examples | Storage | At-rest encryption | In-transit encryption |
|---|---|---|---|---|
| Customer PII | Name, address, phone, DOB, DL | Dealer-scoped garage_register.json, customers.json |
Render disk encrypted at rest by Render | TLS 1.2+ on every API call |
| Driver's licence images | DL photos | Dealer's own Google Drive (per-vehicle folder) | Google Drive's at-rest encryption | TLS 1.2+ to Drive |
| Banking / financial | Purchase price, sale price, financing terms | Dealer-scoped garage_register.json |
Render disk encrypted | TLS 1.2+ |
| Vehicle history (Carfax) | VHR PDFs | Dealer's own Google Drive | Drive at-rest | TLS to Carfax + Drive |
| OAuth refresh tokens | Google, QuickBooks Online | Dealer-scoped config.json |
Render disk encrypted; tokens rotate; revocable via Connections sheet | TLS to Intuit / Google |
| Passwords | Dealer login | config.users[].password hashed with PBKDF2-SHA256 (600k iterations) |
Hash, not plaintext | TLS 1.2+ |
Tenancy model
Swift DMS is multi-tenant by Render-deployed Flask app + per-dealer
directory isolation (dealers/<dealer_id>/). Tenant scoping happens at
every request via session["dealer_id"]; no API call is allowed to
read another tenant's data. Multi-rooftop dealer groups
(groups/<group_id>/config.json) opt in to cross-rooftop reads through
the explicit location switcher; the user must be a member of the group
to switch.
Authentication & authorization
- Email + password login with PBKDF2 hashing (600k iterations,
per-user salt). Brute-force protection via flask-limiter (10/min on
login, 5/hr on registration).
- Session cookies —
HttpOnly,SameSite=Lax,Secureflag set
when served over HTTPS (always true on Render production).
- CSRF protection on state-changing endpoints via session-bound
nonces in OAuth flows (Google, QuickBooks) and Flask's request-scoped
session.
- Password reset via single-use tokens with 1-hour TTL, atomic
consume-once semantics (replay attempts log to security_audit.log),
PBKDF2-hashed token store at rest.
- Admin actions require both
SWIFT_ADMIN_KEYenv var match AND
password match. Production refuses to serve admin routes if the
default credentials from the public repo are still in place.
OAuth integrations
Per-dealer OAuth means each dealer connects their own Google + Intuit
accounts. Swift DMS never stores access tokens (always derived from
refresh tokens at request time); refresh tokens are stored encrypted at
rest by Render's disk encryption and are revocable by the dealer from
the Connections sheet.
- Google — Drive + Sheets scopes only. Used for per-vehicle folder
storage, wholesale tracking sheet sync. Tokens rotate every ~6
months; Swift re-prompts on expiry.
- Intuit (QuickBooks Online) —
com.intuit.quickbooks.accounting
scope only. Used for Customer + Item + Invoice + Attachable sync on
BOS generation. Refresh tokens rotate every ~100 days; Swift detects
rotations on every refresh and persists the new token.
Compliance
- PIPEDA — Personal Information Protection and Electronic Documents
Act (Canada federal). Compliance via per-dealer data isolation,
Principle 9 access right (/api/data/export returns full dealer
data as a JSON zip), retention controlled by the dealer's own
schedule.
- Quebec Bill 96 — French-language consumer documents required for
Quebec sales. Swift DMS renders BOS PDFs in French (Pro+ tier) for QC
dealers. The dashboard UI is bilingual on the Pro+ tier.
- OMVIC — Ontario regulator. Swift's BOS generator pulls verbatim
language for the four MVDA-mandated statements (Sales Final,
Compensation Fund, As-Is, CAMVAP) from bos_template/province_rules.py
and the regulator-issued reference documents. Tests pin canonical
wording so accidental rewordings fail CI.
Backup & restore
- Render disk —
dealers/lives on a Render persistent disk with
daily snapshots.
- Per-dealer Google Sheets mirror — wholesale records also sync to
the dealer's own Sheet (Sheets-as-backup pattern), so a Render disk
loss doesn't lose the dealer's deal history.
- Dealer self-export —
/api/data/exportlets the dealer download
their full data at any time.
- Tested restore path —
tests/test_restore_backup.pyexercises a
sheet-driven restore against an empty dealer dir; fails CI if the
restore-from-sheets round-trip breaks.
Audit logging
Security-relevant events write to a JSONL append-only log at
<DATA_ROOT>/security_audit.log:
- Admin password reset attempts (allowed / wrong-key / token-replay)
- Push-notification quick-action endpoint invocations (token-only flow)
- OAuth callback grants + revocations (planned — currently logs to
application log)
Incident response
- Sentry SDK (Flask + standalone monitor.py) captures exceptions with
send_default_pii=False + a before_send PII redactor that strips
email, phone, dl_number, dob from breadcrumbs.
- Push notifications alert the dealer on chronic outages (Carfax
session expired, push delivery health amber/red).
- Crisp.chat live chat widget on the landing page for support escalation.
What Swift DMS does not do
- Sell your data. Swift's business model is per-dealer subscription
revenue. No third-party data sales, no marketing analytics that
identify your customers.
- Train AI on your data. Anthropic and Google APIs are called with
no-training data-use headers where supported. AI inbox replies are
drafted from each prospect's individual message + your replies, not
pooled across dealers.
- Share data across rooftops. Multi-location groups have explicit
membership controls; one rooftop's data is never visible to another
without the dealer-group owner's consent.
Reporting a vulnerability
Email security@swiftdms.ca with the issue. We acknowledge inside one
business day and aim for a fix within 7 days for critical issues
(authentication bypass, data leak across tenants, remote code
execution). Coordinated disclosure preferred; we credit reporters in
release notes unless asked not to.
Open audit findings
The Phase J audit (AUDIT.md at the repo root) tracks remaining
hardening work. As of this writing, the largest open items are:
- Audit-log coverage expansion (OAuth events to JSONL)
- Encryption-at-rest for OAuth refresh tokens (currently relying on
Render disk encryption; planning per-token AES envelope encryption)
- Sentry breadcrumb scrubbing surface review