Security architecture
Eight layers of security
Security is not a feature we added - it is the architecture we built around. Every layer is enforced independently so that a failure in one layer is caught by the next.
Row-Level Security (database layer)
Company data is isolated at the PostgreSQL level using Row-Level Security policies. Even if there were an application bug, the database would reject any cross-company query. Company A's data is physically invisible to Company B.
AES-256 encryption at rest + TLS 1.3 in transit
All data stored in the database is encrypted at rest using AES-256. All data transmitted between your browser and our servers uses TLS 1.3. Bank account numbers stored in vendor profiles are additionally encrypted at the application layer before being written to the database.
Short-lived JWT tokens with rotation
Access tokens expire after 15 minutes. Refresh tokens expire after 7 days and rotate on every use - a stolen refresh token can only be used once. Passwords are hashed using bcrypt at cost factor 12. Logging out destroys all active tokens immediately.
Role-based access control on every API route
Every API endpoint enforces three checks in sequence: (1) Is the user authenticated? (2) Does their role permit this action? (3) Does the requested data belong to their company? A user with the wrong role or accessing another company's data receives a 403 Forbidden - not filtered results, but an outright rejection.
Rate limiting across all sensitive endpoints
Login attempts are limited to 5 per 15 minutes per IP address - exceeding this triggers a temporary block. API calls are limited to 100 per minute per user token. Auction bid submissions are limited to 10 per minute per vendor to prevent automated bid manipulation.
Zod input validation on every endpoint
Every API endpoint validates the incoming request body against a Zod schema before any database query is executed. Invalid types, out-of-range values, and unexpected fields are rejected at the API boundary. SQL injection is architecturally impossible - we use a type-safe ORM that never constructs raw SQL from user input.
Immutable, append-only audit log
Every mutating action in the system is logged with: user ID, role, action type, entity affected, old and new values, IP address, and timestamp. The audit_logs table has database-level restrictions that prevent UPDATE and DELETE operations - not just application-level, but enforced by the database itself. Logs cannot be modified or removed by anyone.
OWASP security headers on every response
Every page response includes: X-Frame-Options: DENY (prevents clickjacking), X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Content-Security-Policy, and Strict-Transport-Security with a 1-year max-age. These headers are applied in middleware and cannot be bypassed.