fix: Track C audit remediation — security hardening#34
Conversation
P1-C1: Document CRM schema contract — Twenty uses workspace-scoped schemas
(workspace_<base36_uuid>) with singular table names (person, company), not
people/companies. No compatibility views needed.
P1-C2: Harden admin-token middleware — hash token at startup (SHA-256),
use crypto.timingSafeEqual for comparison, add sliding-window rate limiter
(10 attempts/min/IP), audit log on every accept/reject.
P1-C3: Stop leaking GoTrue error details — return generic "Authentication
failed" to clients, log full error server-side. Also harden admin-token
endpoint in gotrue-auth controller with timing-safe comparison.
P2-C1: Remove default Redis password — require explicit REDIS_PASSWORD
via ${REDIS_PASSWORD:?Set REDIS_PASSWORD} in all compose references.
P2-C2: Encrypt backup SQL dumps — add optional GPG symmetric encryption
using EXE_BACKUP_KEY env var (AES-256). Warns if key not set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 704c0f9971
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return; | ||
| } | ||
|
|
||
| this.rateLimiter.record(clientIp); |
There was a problem hiding this comment.
Limit only failed admin-token attempts
When a client uses the configured admin Bearer token for GraphQL/REST/MCP calls, every successful request is recorded before the token comparison, so the 11th valid request from the same IP within 60 seconds is treated as rate-limited and falls through to the normal auth middleware without req.adminTokenAuthenticated. Since the admin token is not a JWT/API key, legitimate integrations making more than 10 admin-token API calls per minute will start receiving auth failures; record attempts only after a failed comparison or exempt accepted tokens.
Useful? React with 👍 / 👎.
| | `companyId` | `uuid` | FK to `company.id` | | ||
| | `createdAt` | `timestamptz` | | | ||
| | `deletedAt` | `timestamptz` | Soft-delete | | ||
|
|
There was a problem hiding this comment.
Correct the documented company domain column
The schema contract tells gateway/ERP callers to query workspace_<id>.company."domainNameUrl", but the standard company metadata and duplicate criteria use the LINKS subfield column domainNamePrimaryLinkUrl (with related domainNamePrimaryLinkLabel/domainNameSecondaryLinks), not domainNameUrl. External systems following this new contract will select a non-existent column and fail when reading company domains.
Useful? React with 👍 / 👎.
Summary
docs/SCHEMA-CONTRACT.md. Twenty uses per-workspace schemas (workspace_<base36_uuid>) with singular table names (person,company). Nocrm.peopleorcrm.companiestables exist — external systems (gateway, ERP) must use the correct names.crypto.timingSafeEqualfor comparison, sliding-window rate limiter (10 attempts/min/IP), audit logging on every accept/reject.gotrue-auth.controller.tswith timing-safe comparison.exe-crm-redis-default) — now requires explicitREDIS_PASSWORDvia${REDIS_PASSWORD:?Set REDIS_PASSWORD}in all compose references.EXE_BACKUP_KEYenv var (AES-256 symmetric). Warns if key not set but does not block backups.Test plan
error_descriptionleak)docker compose configfails withoutREDIS_PASSWORDsetEXE_BACKUP_KEYis setEXE_BACKUP_KEYis unset🤖 Generated with Claude Code