Security Architecture
Status: DRAFT
Owner: Engineering
Last Review: 2026-02-22
Applicable Standards: SOC 2 (CC6, CC7) / GDPR (Art. 32) / SEC (data protection)
1. Purpose
This document describes the security controls protecting the Equa platform — the technical mechanisms that safeguard user credentials, financial data, equity records, and organizational documents. It covers authentication, encryption, session management, network hardening, input validation, and infrastructure security.
2. Scope
| Component | In Scope | Notes |
|---|
| equa-server | Yes | Backend API, authentication, authorization, data persistence |
| equa-web | Yes | Frontend SPA, input validation, file upload controls |
| PostgreSQL | Yes | Session store, credential storage, audit logs |
| AWS S3 | Yes | Document storage, access control |
| Google Cloud Run | Yes | Production deployment, container isolation |
| Railway | Yes | Secondary deployment target |
| equabot-gateway | Partial | Gateway HTTP authentication and CORS posture are in scope; broader gateway security is documented separately |
3. Authentication
For the full technical implementation, see Authentication and Permissions.
3.1 Authentication Methods
The platform supports three authentication methods, all resulting in a server-side session stored in PostgreSQL.
| Method | Source File | Key Properties |
|---|
| Password Login | equa-server/modules/auth/src/authentication.ts | bcryptjs 10 salt rounds, email verification required, 2FA support |
| Google OAuth | equa-server/modules/auth/src/google-auth.ts, equa-server/modules/api/src/server.ts | ID token + access token verification, audience (aud) validation, CSRF via g_csrf_token cookie/body check, auto-registration |
| Magic Links | equa-server/modules/auth/src/magic-link.ts | 32-byte random hex token, 15-minute expiry, anti-enumeration (generates token even for nonexistent emails); implementation exists but schema integration is incomplete |
3.2 Two-Factor Authentication
Source: equa-server/modules/auth/src/two-factor.ts
| Property | Value |
|---|
| Algorithm | TOTP (Time-based One-Time Password) |
| Secret Storage | AES-256-CTR encrypted (see Encryption) |
| User Fields | Users.twoFactorSecret (encrypted), Users.twoFactorEnabled (boolean) |
| Status | Optional — users can enable/disable |
3.3 Email Verification
Email verification is required before a user can log in. The EmailVerifications entity stores a verification code linked to the user’s UUID.
Source: equa-server/modules/persistence/src/schema.ts (EmailVerifications entity)
3.4 Anti-Abuse Controls
| Control | Implementation | Source |
|---|
| Registration rate limiting | REGISTRATION_IP_LIMIT env var (default: 20 registrations per IP) | equa-server/modules/referral/src/referral.ts |
| Domain blacklist | domain_blacklists table blocks registration from specified email domains | equa-server/modules/persistence/src/schema.ts |
| Email blacklist | email_blacklists table blocks specific email addresses | equa-server/modules/persistence/src/schema.ts |
| reCAPTCHA | Required on signup form using frontend site key (CAPTCHA_SITE_KEY) | equa-web/src/modules/auth/components/sign-up-form.tsx |
| Temporary passwords | TempPasswords entity with bcrypt-hashed temporary passwords | equa-server/modules/persistence/src/schema.ts |
reCAPTCHA validation is currently frontend-only. No server-side verification endpoint was found in equa-server for validating reCAPTCHA tokens with Google.Recommendation: Add backend token verification for signup to prevent direct API bypass of frontend checks.
4. Encryption
4.1 Current Implementation
Source: equa-server/modules/auth/src/lib/encryption.ts
| Property | Value |
|---|
| Algorithm | AES-256-CTR |
| Key Source | TWO_FACTOR_PRIVATE_KEY env var (32-byte hex string) |
| IV | 16 random bytes per encryption operation |
| Storage Format | iv:encryptedContent (hex-encoded) |
| Class | AesKey with encrypt() and decrypt() methods |
Encrypted fields:
| Entity | Field | Purpose |
|---|
| Users | twoFactorSecret | TOTP shared secret for 2FA |
4.2 Password Hashing
| Property | Value |
|---|
| Algorithm | bcryptjs |
| Salt Rounds | 10 |
| Stored In | Users.passwordHash, TempPasswords.passwordHash |
4.3 Encryption Gaps
The following sensitive fields are stored in plaintext in PostgreSQL:| Entity | Field | Risk |
|---|
| GoogleDriveConnections | accessToken | OAuth token enables Google Drive access |
| GoogleDriveConnections | refreshToken | Long-lived token enables persistent access |
| BankAccounts | accountNumber | Financial account identifier |
| TaxIds | value | Contains EIN or SSN |
Recommendation: Extend the AesKey encryption to these fields using a dedicated encryption key separate from the 2FA key.
5. Session Management
Source: equa-server/modules/auth/src/sessions.ts
| Property | Value |
|---|
| Library | express-session |
| Store | Custom TypeORMSessionStore (persisted in sessions table) |
| Secret | API_SESSION_SECRET env var |
| Max Age | API_SESSION_MAX_AGE (runtime default in sessions.ts: 7 days; current service bootstrap config: 2,520,000ms / ~42 minutes) |
| Rolling | true — session expiry resets on each request |
| Cookie Secure | Enabled when API_SSL is set |
| Proxy Trust | trust proxy: 1 |
| Resave | false |
| Save Uninitialized | false |
5.1 Session Storage Schema
Source: equa-server/modules/persistence/src/schema.ts (Sessions entity)
| Column | Type | Notes |
|---|
id | string (PK) | Session identifier |
expires | Date (indexed) | Expiration timestamp |
user | UUID (indexed, nullable) | Linked user |
json | text | Serialized session data |
5.2 Session Cleanup
Source: equa-server/modules/auth/src/lib/session-cleaning.ts
A cron job periodically removes expired session records from the database based on the expires column.
6. Network and Infrastructure Security
6.1 Deployment
| Environment | Platform | Configuration |
|---|
| Production | Google Cloud Run | equa-server/cloudbuild.yaml — Docker image built and deployed |
| Secondary | Railway | equa-server/railway.toml — nixpacks builder with health check |
| Health Check | Both | GET /health returns status and timestamp |
6.2 SSL/TLS
| Property | Value |
|---|
| Toggle | API_SSL env var |
| Key Path | SSL_PRIVATE_KEY_PATH |
| Cert Path | SSL_PUBLIC_KEY_PATH |
| Cookie Security | Secure flag set when SSL enabled |
6.3 CORS
CORS is enabled via the vineyard-lawn middleware in equa-server/modules/api/src/server.ts. The specific allowed origins are configured at the application level.
6.4 Gateway HTTP Authentication and CORS
Source: equabot-gateway/src/gateway/auth.ts, equabot-gateway/src/gateway/channels-http.ts, equabot-gateway/src/gateway/config-http.ts, equabot-gateway/src/gateway/task-stack-http.ts
| Control | Current State |
|---|
| Auth methods | Token/password modes with timing-safe comparison, trusted proxy handling, and optional Tailscale-aware behavior |
| CORS headers | Access-Control-Allow-Origin: * is set on gateway HTTP handlers |
| Endpoint rate limiter | RateLimiter utility exists in equabot-gateway/src/gateway/rate-limit.ts but is not wired to HTTP handlers |
No security headers middleware (such as helmet) is configured. The following headers are not set:
Strict-Transport-Security (HSTS)
X-Content-Type-Options
X-Frame-Options
Content-Security-Policy
X-XSS-Protection
Referrer-Policy
Recommendation: Add helmet middleware to equa-server/modules/api/src/server.ts with production-appropriate defaults.
6.6 Rate Limiting Gap
There is no API-wide rate limiting. The only rate limiting exists at the agent level:| Limit | Default | Env Var |
|---|
| Tool calls per minute | 30 | AGENT_MAX_TOOL_CALLS_PER_MINUTE |
| Write operations per minute | 10 | AGENT_MAX_WRITE_OPS_PER_MINUTE |
| Destructive operations per hour | 5 | AGENT_MAX_DESTRUCTIVE_PER_HOUR |
Source: equa-server/modules/agent/src/security/guardrails.tsRecommendation: Implement API-wide rate limiting (e.g., express-rate-limit) on authentication endpoints and write operations.
7.1 Backend Validation
Source: equa-server primarily uses class-validator decorators for backend request DTO validation. vineyard-lawn is used for endpoint wiring and request flow.
| Field | Validation Rule |
|---|
| Email | IsEmail (class-validator) |
| Password | 4-128 characters (Length(4, 128)) with regex guard ^[\\w\\-_@.\~!#` |
| Username | 2-32 characters, [a-z0-9-_]+ |
| UUID | IsUUID for UUID-typed fields |
| 2FA token | Numeric TOTP verification in two-factor.ts |
Validation for file names, URLs, phone numbers, and Ethereum addresses is also present in frontend validators (equa-web/src/shared/helpers/field-validators.ts) and should not be treated as equivalent to backend enforcement without endpoint-specific confirmation.
7.2 Frontend Validation
Source: equa-web/src/shared/helpers/field-validators.ts
Frontend validation mirrors backend rules using the same validator library and custom regex patterns. React’s default JSX escaping provides baseline XSS protection.
7.3 File Upload Validation
| Control | Value | Source |
|---|
| Allowed extensions | .jpg, .jpeg, .png, .gif, .webp, .pdf, .doc, .docx, .xls, .xlsx, .csv, .txt, .md | equa-web/src/modules/landing/pages/landing-page.tsx |
| Max file size | 10 MB (AWS_S3_UPLOAD_SIZE_LIMIT_MB) | equa-server env config |
| Max files per upload | 5 | Frontend validation |
| Upload method | multipart/form-data via axios with withCredentials: true | equa-web/src/service/lib/http-client.ts |
8. Secret Scanning
| Repository | Secret Scanning | Tool |
|---|
| equabot-gateway | Yes | detect-secrets in CI (.github/workflows/ci.yml) |
| equa-server | No | Not configured |
| equa-web | No | Not configured |
Recommendation: Add detect-secrets scanning to equa-server and equa-web CI pipelines, matching the equabot-gateway configuration.
9. Environment Variables (Security-Relevant)
| Variable | Purpose | Required |
|---|
API_SESSION_SECRET | Session cookie signing | Yes |
TWO_FACTOR_PRIVATE_KEY | AES-256-CTR key for 2FA secrets | Yes (if 2FA enabled) |
GOOGLE_OAUTH_CLIENT_ID | Google OAuth audience validation | Yes (if Google auth enabled) |
API_SSL | Enable SSL/TLS | Production |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY | S3 file storage credentials | Yes |
MS_AUTH_CLIENT_ID / MS_AUTH_SECRET | Microsoft integration OAuth | If Microsoft enabled |
GOOGLE_DRIVE_CLIENT_ID / GOOGLE_DRIVE_CLIENT_SECRET | Google Drive integration | If Drive enabled |
CAPTCHA_SITE_KEY | reCAPTCHA validation | Yes |
REGISTRATION_IP_LIMIT | Max registrations per IP (default: 20) | Optional |
10. Regulatory References
| Standard | Requirement | Current Status |
|---|
| SOC 2 CC6.1 | Logical access security over protected information | Partially implemented (RBAC exists, gaps in rate limiting) |
| SOC 2 CC6.6 | Security measures against threats outside system boundaries | Gap (no security headers, no API rate limiting) |
| SOC 2 CC7.2 | Monitoring of system components for anomalies | Gap (no APM/alerting beyond health check) |
| GDPR Art. 32 | Appropriate technical and organizational security measures | Partially implemented (encryption limited to 2FA secrets) |
11. Revision History
| Date | Version | Author | Changes |
|---|
| 2026-02-21 | 0.1 | Agent (Phase 5 Session A) | Initial draft from code analysis |
| 2026-02-22 | 0.2 | Agent | Accuracy pass: corrected validation/session/auth references, clarified gateway scope, added reCAPTCHA backend gap note |