Security
Authentication
Contract Lucidity uses JWT (JSON Web Token) bearer authentication with a refresh token flow.
Token Flow
Token Configuration
| Setting | Default | Recommendation |
|---|---|---|
JWT_ALGORITHM | HS256 | Sufficient for single-service deployments |
JWT_ACCESS_TOKEN_EXPIRE_MINUTES | 60 | Reduce to 15-30 for high-security environments |
JWT_REFRESH_TOKEN_EXPIRE_DAYS | 7 | Reduce to 1-3 for sensitive deployments |
The JWT_SECRET_KEY must be a strong, random value in production. Generate one with:
openssl rand -hex 64
The default value (change-me-to-a-random-secret-in-production) provides no security.
Password Security
- Passwords are hashed with bcrypt (via Passlib)
- The default admin account is created with
must_change_password=True - User roles:
admin(full access) anduser(project-scoped access)
File Upload Validation
When documents are uploaded, the backend validates:
- File size: Enforced by
MAX_UPLOAD_SIZE_MB(default: 100 MB) - File type: Restricted to configured types stored in
system_config(default:pdf, docx, png, jpg, jpeg, tiff) - Storage path: Files are written to the
cl-storagevolume at/data/storage, never to application directories
File type validation is enforced at both the frontend (UI-level) and backend (API-level). The accepted types are stored in the system_config table under the key supported_file_types and can be modified through the admin UI.
CORS Configuration
Cross-Origin Resource Sharing (CORS) is configured on the backend via the CORS_ORIGINS environment variable.
# Default configuration
CORS_ORIGINS=http://localhost:3000,https://contractlucidity.com
The backend applies CORS middleware with these settings:
- Allowed origins: Only origins listed in
CORS_ORIGINS - Credentials: Enabled (
allow_credentials=True) - Methods: All HTTP methods
- Headers: All headers
In production, restrict CORS_ORIGINS to only your deployment domain:
CORS_ORIGINS=https://contracts.yourcompany.com
Do not use wildcards (*) in production.
Network Isolation
Access Requirements by Service
| Service | Public Access | Internal Access | Notes |
|---|---|---|---|
| cl-frontend | Yes (via reverse proxy) | cl-backend | Only service that should face the internet |
| cl-backend | No | cl-frontend, cl-redis, cl-db | API not directly exposed; proxied through frontend |
| cl-worker | No | cl-redis, cl-db, AI APIs | No inbound connections needed |
| cl-redis | No | cl-backend, cl-worker | Internal task queue only |
| cl-db | No | cl-backend, cl-worker | Database traffic only |
The default docker-compose.yml publishes PostgreSQL (5432) and Redis (6379) ports to the host for development convenience. In production, remove or comment out these port mappings:
# Remove these lines in production:
# ports:
# - "5432:5432" # cl-postgres
# - "6379:6379" # cl-redis
AI API Key Security
AI provider API keys are:
- Stored encrypted in the
ai_providerstable (api_key_encryptedcolumn) - Never logged or included in API responses
- Configured via the UI (Settings > AI Capabilities), not in environment variables
- Scoped per capability — different keys can be used for different AI capabilities
Rotate AI provider API keys periodically. When you update a key in the UI, the old key is immediately replaced in the database.
Database Security
- PostgreSQL connections use the internal Docker network (
cl-network) — no traffic traverses public networks - Database credentials are set via environment variables, never hardcoded
- The
pgvectorextension is created automatically on first boot - Alembic migrations run on backend startup, ensuring the schema is always up to date
Recommended Production Settings
- Use a strong, unique
POSTGRES_PASSWORD(32+ characters) - Enable PostgreSQL SSL connections if the database is not co-located with application services
- Configure
pg_hba.confto restrict connections to the application subnet - Enable WAL archiving for point-in-time recovery
SSL/TLS
Contract Lucidity transmits JWT tokens and sensitive legal documents. TLS is mandatory for any non-development deployment.
Place a reverse proxy (Nginx, Caddy, Traefik, or a cloud load balancer) in front of cl-frontend to terminate TLS:
Internet → [TLS termination at :443] → cl-frontend:3000 → cl-backend:8000
All internal communication between services within the Docker network is unencrypted by default, which is acceptable when services share a private network. If services span multiple hosts, use mutual TLS or a service mesh.
Role-Based Access Control
Contract Lucidity enforces access control at two levels: user roles and project-level grants.
User Roles
| Role | Access |
|---|---|
| Admin | Full access to all projects, settings, user management, groups, SSO configuration, playbook, and AI providers |
| User | Can only access projects they have been explicitly granted access to (directly or via a group) |
Admin users bypass all project-level access checks. The user role is subject to project access filtering -- they will only see projects where a project_access record exists for their user ID or for a group they belong to.
Project-Level Access Control
Access to individual projects is managed through the project_access table, which supports three access levels:
| Access Level | Permissions |
|---|---|
viewer | Read-only access to the project and its documents |
editor | Upload documents, make clause decisions, run analyses |
admin | All editor permissions plus the ability to manage access for the project |
Access can be granted to individual users or to groups. A user's effective access level is the highest level across all their grants (direct + group-based).
For full details, see Groups & Access Control.
SSO Security Benefits
When Single Sign-On is enabled, authentication is delegated to your organization's identity provider. This provides:
- Centralized MFA enforcement -- multi-factor authentication is configured in the IdP, not managed per-user in CL
- Conditional access policies -- restrict login by device, location, or risk level (IdP-dependent)
- Centralized account lifecycle -- disabling a user in the IdP immediately prevents SSO login to CL
- No password storage -- SSO users have no password hash in the CL database
- Audit trail -- authentication events are logged in both the IdP and CL
For setup instructions, see Single Sign-On (SSO).
Permission Resolution Hierarchy
Contract Lucidity resolves access permissions in a strict 6-step order. The first matching rule wins:
- Ethical wall deny -- If the user is on an ethical wall that covers the project, access is denied unconditionally.
- User deny -- If the user has an explicit deny permission on the project, access is denied.
- Group deny -- If any of the user's groups has a deny permission on the project, access is denied.
- User allow -- If the user has a direct allow grant, access is allowed at the granted level.
- Group allow (highest) -- If any of the user's groups has an allow grant, access is allowed at the highest level across all groups.
- Default deny -- If no rule matched, access is denied.
This is a hybrid model: deny permissions always override allow permissions, and among allow grants, the most permissive level wins.
Permission Levels
| Level | Can View | Can Upload / Edit | Can Manage Access |
|---|---|---|---|
viewer | Yes | No | No |
editor | Yes | Yes | No |
admin | Yes | Yes | Yes |
Deny Overrides Allow
A single deny permission -- whether from an ethical wall, a direct user deny, or a group deny -- blocks all access to a project regardless of any allow grants. If a user has admin-level access through a group but also has a deny through any path, the result is deny.
Admin Role and Ethical Walls
Admin-role users bypass normal project-level access checks (they can see all projects). However, admin-role users do NOT bypass ethical walls. When an ethical wall screens an admin from a project, that admin cannot access the project. Only the seed admin (the break-glass recovery account) is exempt from ethical walls.
For full details on ethical walls, see Ethical Walls (Information Barriers).
Audit Logging
Key access control events are tracked in the audit log:
- Ethical wall enforcement: When a user's request is blocked by a wall (user ID, project ID, wall ID, timestamp)
- Permission changes: When access grants are created, modified, or revoked
- Wall lifecycle: When walls are created, modified, deactivated, reactivated, or deleted
- User login: Timestamp recorded on the user record (
last_login) - Document uploads: Tracked with
created_aton documents - Clause decisions: Tracked with
decided_atanddecided_byon clause_decisions
Audit logs for ethical wall events are available through the admin API at /api/admin/ethical-walls/audit-log and can be exported for compliance reporting.
Recommended Additional Measures
Firewall Rules
- Allow inbound only on port 443 (HTTPS)
- Allow outbound to AI provider APIs (443) and package registries
- Block all other inbound traffic
Rate Limiting
Apply rate limiting at the reverse proxy level:
- Login endpoint (
/api/auth/login): 5 requests per minute per IP - File upload (
/api/documents): 10 requests per minute per user - General API: 100 requests per minute per user
Web Application Firewall (WAF)
Consider a WAF (Cloudflare, AWS WAF, Azure Front Door) for:
- SQL injection protection (defense in depth — SQLAlchemy uses parameterized queries)
- Request body size limits
- Geographic access restrictions
- Bot detection
Audit Logging
For comprehensive audit logging beyond the built-in tracking described in the Audit Logging section above, configure your reverse proxy to log all requests with user identity headers.