Security & Data Handling
Last updated: 30 July 2026. Effective immediately.
In short: tenant data is isolated at the database level, every request is authenticated, every privileged action is logged, and access keys are hashed. The platform admin layer is a separate identity with a separate signing key and an IP allowlist.
1. Security principles
We design the platform around these principles:
- Isolation first. A bug in one tenant must not leak data to another tenant. We enforce this at the database, not just the application layer.
- Least privilege. The platform admin layer can read across tenants by design, but every cross-tenant read is recorded.
- Defence in depth. TLS, hashed credentials, signed tokens, row-level security, audit logs, rate limits, and IP allowlists all work together.
- Reversibility. Every state change (plan edit, suspension, password reset, impersonation) is reversible and recorded.
- No secrets in code. All secrets live in environment variables. The repository has no production credentials.
2. Tenant isolation
Nishan runs on PostgreSQL with PostGIS. Every operational table includes
a tenant_id column and is protected by a row-level security
(RLS) policy. The application connects as a role that has RLS enforced;
the only way to read across tenants is to connect as a separate role that
bypasses RLS, which is reserved for the platform admin layer and is itself
audited.
Tests prove isolation: 32 of our backend tests are dedicated to cross-tenant leakage scenarios and they all pass on every release.
3. Encryption
- In transit: TLS 1.2+ on all public endpoints. HTTP requests are redirected to HTTPS.
- At rest: managed disk encryption on database and object storage volumes.
- Passwords: argon2id with parameters tuned for current hardware. Stored as a self-describing PHC string.
- API keys: SHA-256 hash stored; raw key is shown once at creation or rotation.
- Refresh tokens: SHA-256 hash stored in the platform_admin_refresh_tokens table.
- JWT signing: HS256 with a 32+ byte secret. Separate secrets for tenant and platform-admin tokens.
4. Authentication & authorisation
- Tenant users authenticate with email + password
(argon2id) and receive a short-lived JWT (default 30 min) plus a
30-day refresh token cookie (httpOnly, SameSite=Strict, Secure in
production, path-scoped to
/). - API access uses a per-tenant API key passed in the
Authorization: Bearerheader. - Roles:
owner,admin,dispatcher,viewer. Each role has explicit allow / deny lists for every endpoint. - Platform admins authenticate on a separate
/admin/auth/loginroute with a separate JWT secret. The JWT carriestype: 'platform_admin'and the platform-admin middleware rejects any token that does not. - Failed login attempts are rate-limited per email and per IP.
5. API hardening
- HTTPS-only; HTTP requests get a 301 to HTTPS.
- Strict
Content-Security-Policy,X-Frame-Options,X-Content-Type-Options,Referrer-Policy, andPermissions-Policyheaders on every response. - Request bodies are size-limited (default 1 MB) and parsed with strict Zod schemas; unknown fields are rejected.
- All input strings are normalised, length-capped, and (for free-text fields) HTML-escaped before logging.
- Per-tenant and per-API-key rate limits are enforced at the edge.
- Geocoder calls go through a provider router that includes a circuit breaker and cost cap per provider.
6. Platform admin controls
- Separate
platform_adminstable, separate JWT secret (ADMIN_JWT_SECRET), separate refresh-token table. - Refresh cookie is path-scoped to
/admin, httpOnly, SameSite=Strict. - IP allowlist on the admin console, enforced at the load balancer.
- Impersonation tokens are capped at 10 minutes regardless of the tenant JWT TTL.
- Owner password reset returns a one-time password in the response and rotates all active refresh tokens for the affected tenant.
- Every cross-tenant read or write is recorded in
platform_admin_auditwith actor, action, target, IP and user agent.
7. Audit & monitoring
- Append-only audit log per tenant for owner / admin / dispatcher actions, plus a separate platform_admin_audit for the admin layer.
- Server access logs retained 30 days. Logs are redacted of PII (phone, email, full name, key material) at write time.
- Health endpoints, request counters, and provider-cost metrics exported for monitoring.
- Anomalous patterns (sudden spikes in failed logins, geocoder cost overruns, repeated cross-tenant denied reads) raise alerts.
8. Secure development
- Pull requests require code review and a green CI build.
- Static analysis and dependency audit run on every commit.
- Secrets are loaded from environment variables; the repo is scanned for accidental secret commits.
- Database migrations are versioned, reviewed, and reversible.
9. Backups & recovery
- Database: encrypted daily backups, 35-day retention, restore tested monthly.
- Object storage (verification photos): versioned, encrypted, 13-month retention.
- Recovery time objective (RTO): 4 hours. Recovery point objective (RPO): 24 hours.
- Backups are stored in a separate region from the primary database.
10. Incident response
- Confirmed security incidents are notified to affected tenant owners within 72 hours of detection, in line with applicable data-protection requirements.
- Notifications include what we know, what we are doing, and what you can do.
- Post-incident reports summarise root cause and remediation, and are available to affected tenants on request.
11. Responsible disclosure
If you have found a security issue, please email security@nishanhq.com with a description and reproduction steps. We will acknowledge within one business day and work with you on coordinated disclosure. We do not pursue legal action against good-faith security research.
12. Contact
Security: security@nishanhq.com
Privacy: privacy@nishanhq.com
General: hello@nishanhq.com