[ 05 ] Security

Built for vendor money.
Audited at every layer.

We hold your inventory and we hold your money. The platform is engineered like that's true — with append-only ledgers at the database level, mandatory multi-factor auth, tenant isolation enforced four ways, and a published threat model.

[ 06 ] Four pillars

Defence in depth, not theatre.

[ 01 ]

Tenant isolation (IDOR)

Vendor A cannot read or mutate Vendor B's data. Period.

  • Every vendor-scoped query filters by vendor_id at the service layer
  • TenantGuard enforces the JWT's vendor_id matches the URL parameter on every controller
  • 404 (NotFound) on cross-tenant access — never 403 — so we don't confirm existence
  • DB-level trigger refuses any order_lines insert/update where the line's vendor_id ≠ the parent order's
  • IDOR test suite runs in CI and blocks merge on any failure
[ 02 ]

Append-only audit trail

Every privileged action lands in an immutable row that even the application user cannot delete.

  • audit_log_entries: PG trigger raises EXCEPTION on UPDATE or DELETE
  • ledger_entries: same trigger + sign-consistency CHECK (DEPOSIT > 0, charges < 0)
  • order_events: same trigger; carrier + admin + cron events all captured
  • wallets.balance_cents = sum(ledger_entries.amount_cents) — reconciled nightly
  • Audit log viewer in the admin console emits its own audit row when accessed
[ 03 ]

Encryption + secrets

AES-256-GCM for sensitive fields. Argon2id for passwords. Hashed-at-rest for tokens.

  • MFA secrets encrypted at rest with AES-256-GCM (32-byte master key, never logged)
  • Argon2id password hashing tuned for ~250ms verify on production hardware
  • Refresh tokens, password reset tokens, recovery codes, invitation tokens — all sha256-hashed at rest
  • JWT signed with a 256-bit secret; rotation procedure documented in the runbook
  • HIBP k-anonymity check on every signup + password reset (fail-open with logged warning)
[ 04 ]

Authentication

Mandatory two-factor. Refresh-token rotation with reuse detection.

  • TOTP (RFC 6238) MFA required on every account; 10 single-use recovery codes generated at enrol
  • Refresh tokens rotate on every use; presenting a previously-rotated hash revokes every session in the user's family
  • Account lockout after 5 failures with exponential back-off (10s → 5min)
  • Step-up re-auth required for charges over a configurable threshold (default $500)
  • Session theft detection wired to a token.service.spec test that runs in CI
[ 07 ] Threat model

The attacks we actively defend against.

Threat
Cross-tenant access (IDOR)
Defence

Explicit vendor_id parameter on every query, TenantGuard, 404-on-miss, DB tenant-match trigger, IDOR test suite blocks merge.

Threat
Money replay
Defence

Idempotency-Key required on every state-changing money endpoint. Hash scoped to (vendor, endpoint). Replay returns the cached response.

Threat
Forged webhooks
Defence

HMAC verified for Stripe, KYC, EasyPost. Replay-safe via webhook_events unique(provider, event_id). 600/min rate limit.

Threat
Token theft
Defence

Refresh-token rotation. Presenting a previously-rotated token revokes every session for the user — the legitimate owner is forced to sign in again.

Threat
Privilege escalation
Defence

JWTs signed with a 256-bit secret, role read from the verified JWT only, RolesGuard global, sub-users scoped to read-or-write-not-edit.

Threat
PII exfiltration
Defence

Pino redact list, Sentry beforeSend scrub, recipient email masked in logs, Referrer-Policy strict-origin-when-cross-origin, single-origin CORS.

Threat
CSV / formula injection
Defence

Every export defangs cells starting with =, +, -, @, TAB, CR per OWASP. Streamed one row at a time.

Threat
Runaway query
Defence

Postgres statement_timeout = 10s, lock_timeout = 5s, idle_in_transaction = 30s. Set per-session at connect time.

[ 08 ] In the stack

Every defensive control, in one paragraph.

Helmet for security headers (CSP locked, HSTS preload, frame-ancestors none, strict-origin referrer). Throttler with stricter per-route limits on auth, money, and webhook endpoints. Zod validation pipe with `whitelist + forbidNonWhitelisted` on every body and query. Sentry-integrated exception filter that captures 5xx with PII-scrubbed request context. Daily ledger reconciliation cron that proves sum(ledger) === wallet.balance per vendor. Append-only DB triggers on audit, ledger, and order events. Postgres connection-pool query timeouts. CodeQL + `pnpm audit --production` blocking Critical CVEs in CI.

Password hash
Argon2id
m=19MiB, t=2, p=1
MFA
TOTP RFC 6238
+ 10 single-use recovery codes
Field encryption
AES-256-GCM
32-byte master key
Refresh token TTL
30 days
Rotated on every use
Access token TTL
15 minutes
Step-up re-auth above $500
Audit retention
7 years
Financial compliance
Backup retention
30 / 90 / 365 days
Nightly / weekly / monthly
DB query timeout
10 seconds
Per-session statement_timeout
HSTS
max-age 2 years
includeSubDomains; preload
[ 09 ] Compliance

What we hold, what we don't.

PCI: card data never touches our servers. Stripe Elements + PaymentIntents only. We're a Stripe customer of record.

KYC: handled by Stripe Identity. We store the verification status + metadata; we never receive your government-issued ID.

Customer PII: shipping addresses stored plain v1, behind tenant scoping + audit. Encryption-at-rest via the managed DB. Field-level encryption is on the v2 roadmap.

Audit retention: 7 years on financial events.

SOC 2: on the roadmap once we cross the volume threshold that justifies the auditor cost.

[ 10 ] Responsible disclosure

Found something? We want to hear from you.

Email security@usa-errands.com — we triage within one business day, acknowledge within three, and patch on the same severity-tiered SLA we use internally (P0 ≤ 24h, P1 ≤ 1 week). Bounty program coming with v2.

[ 11 ] Get started

Inventory in. Money locked down. Ship.

Two-factor auth, append-only audit, and a published threat model — from the first dollar.