We take the security of Meridian seriously. If you discover a security vulnerability, please report it privately through one of the following channels:
Report vulnerabilities using GitHub's private vulnerability reporting:
- Navigate to the Security Advisories page
- Click "Report a vulnerability"
- Provide detailed information about the vulnerability
When reporting a vulnerability, please include:
- Description of the vulnerability
- Steps to reproduce the issue
- Potential impact and severity assessment
- Any suggested fixes or mitigations (if applicable)
- Your contact information for follow-up questions
- Acknowledgment: Within 48 hours of submission
- Initial Assessment: Within 5 business days
- Status Updates: Regular updates as investigation progresses
- Resolution: Timeline depends on severity and complexity
- Do not publicly disclose vulnerabilities until a fix has been released
- We will work with you to understand and address the issue
- Credit will be given to researchers who report vulnerabilities responsibly (if desired)
Meridian is a transaction integrity engine handling financial, energy, and infrastructure assets. The threat model addresses risks specific to multi-tenant financial platforms.
Unauthorized modification of ledger entries, account balances, or transaction records. Meridian mitigates this through:
- Immutable audit trails: All entity mutations (INSERT, UPDATE, DELETE) are captured
via GORM hooks and written to per-service
audit_logtables through a transactional outbox pattern (see ADR-0009). - Dual-path audit delivery: Primary path via Kafka, fallback to outbox table. Audit intent is committed atomically with the business transaction, so no mutation can occur without a corresponding audit record.
- Saga orchestration: Distributed transactions use saga state machines with automatic compensation. Each step is recorded, and partial failures trigger deterministic rollback.
Cross-tenant data access where one tenant reads or modifies another tenant's data. See Multi-Tenancy Security Boundaries below for detailed controls.
Re-submission of previously valid requests to duplicate transactions or bypass authorization. Mitigations include:
- Idempotency keys: Critical operations (payment orders, ledger postings) require idempotency keys. Duplicate submissions return the original result without re-execution.
- Audit outbox deduplication: The outbox worker processes entries idempotently; retries do not create duplicate audit records.
Accessing operations or data without proper permissions. Controls include:
- JWT-based authentication: Production deployments require valid JWT tokens with
embedded tenant claims. The
auth.Interceptorvalidates that thex-tenant-idheader matches the JWTtenant_idclaim. - gRPC interceptor chain: The
TenantExtractionInterceptor(development only) andauth.Interceptor(production) are mutually exclusive. Production must useauth.Interceptorwhich enforces JWT validation. - Starlark sandboxing: Tenant-defined business logic runs in Starlark, which is intentionally not Turing-complete. No while loops, no recursion, guaranteed termination. This prevents tenants from executing arbitrary code.
- CEL expression evaluation: Validation rules use CEL (Common Expression Language), which is pure-function and read-only. CEL expressions cannot modify state.
Exploiting bi-temporal data (valid-time and transaction-time) to manipulate historical records or create backdated entries. Mitigations:
- Server-side timestamps:
created_atandchanged_atfields use server-generatednow()defaults, not client-supplied values. - Audit trail immutability: Audit records are append-only. Historical entries cannot be modified or deleted through application code.
| Classification | Examples | Handling |
|---|---|---|
| Restricted | DB credentials, API keys, JWT signing keys | Env vars only. Never in source control. Rotate regularly. |
| Confidential | PII, financial amounts, account balances | Encrypted at rest. Tenant-scoped access. Audited. |
| Internal | Tenant config, Starlark sagas, CEL rules | Authenticated operator access. Changes audited. |
| Audit | Audit logs, outbox records, Kafka events | Append-only. Tampering detection. Regulatory retention. |
| Public | Protobuf schemas, docs, open-source code | No special handling required. |
- Tenant IDs are hashed before logging using SHA-256 truncated to 16 hex characters
(
hashTenantIDinshared/platform/db/gorm_tenant_scope.go). This allows log correlation without exposing tenant identity. - Application logs must not contain PII, financial amounts, or credentials. Use structured logging with parameterized fields.
Meridian uses schema-based multi-tenancy on CockroachDB. Each tenant's data is isolated in a dedicated database schema.
- Schema naming convention: Each tenant's data lives in a schema named
org_{tenant_id}(see ADR-0016). - Tenant ID validation: IDs are validated against
^[a-zA-Z0-9_]{1,50}$at construction time (shared/platform/tenant/tenant_id.go). Invalid IDs are rejected before any database operation. - SQL injection prevention: Schema names are quoted using
pq.QuoteIdentifier()before inclusion in SQL statements (shared/platform/db/gorm_tenant_scope.go).
Tenant identity flows through the entire request lifecycle:
- API Gateway: The
TenantResolverMiddlewareextracts tenant from the request subdomain and sets thex-tenant-idheader. - gRPC Metadata: The
x-tenant-idkey (shared/platform/tenant/keys.go) propagates tenant identity across service boundaries via gRPC metadata. - Context Injection:
tenant.WithTenant(ctx, tenantID)injects the tenant into Go context. All downstream operations extract the tenant from context. - Database Scoping:
WithGormTenantScopeexecutesSET LOCAL search_path TO <schema>, publicwithin each transaction.SET LOCALis transaction-scoped and automatically reverts on commit or rollback. - Kafka Headers: Audit events carry tenant identity in Kafka message headers for cross-service audit correlation.
- Fail-fast on missing tenant:
RequireFromContextreturnsErrMissingTenantContextif tenant context is absent.MustFromContextpanics, treating missing context as a programming error. No database operation can proceed without tenant context. - JWT-tenant validation: In production (
AUTH_ENABLED=true),auth.Interceptorvalidates that thex-tenant-idgRPC metadata matches the JWT'stenant_idclaim. This prevents a caller from specifying a different tenant than their credentials authorize. - No cross-tenant database access: Each service connects to its own database with its own credentials. The centralized audit worker pattern was explicitly rejected to maintain bounded context isolation (see ADR-0020).
Audit records are designed to be append-only:
- Business operations write audit intent to an
audit_outboxtable atomically within the same database transaction. This guarantees that every committed business operation has a corresponding audit record. - The audit worker moves entries from
audit_outboxtoaudit_log. Processing is idempotent; retries do not create duplicates. - Application code does not provide UPDATE or DELETE operations on
audit_logtables.
The audit system uses two delivery paths for resilience:
- Primary (Kafka): Audit events are published to service-specific Kafka topics
(e.g.,
audit.events.current-account). Dedicated audit consumer deployments write toaudit_log. - Fallback (Outbox): When Kafka is unavailable (timeout: 5 seconds), audit entries
remain in the
audit_outboxtable. A centralizedaudit-workerservice polls and processes these entries.
This dual-path design ensures no audit records are lost during infrastructure failures.
Prometheus metrics for audit system health:
| Metric | Type | Purpose |
|---|---|---|
meridian_audit_kafka_events_published_total |
Counter | Primary path volume |
meridian_audit_kafka_fallback_used_total |
Counter | Detect Kafka failures |
meridian_audit_worker_outbox_depth |
Gauge | Audit processing lag |
meridian_audit_kafka_events_consumed_total |
Counter | Consumer throughput |
Alert if outbox depth exceeds 1,000 entries for more than 5 minutes, which may indicate audit processing failures.
Audit log retention policies should be configured based on regulatory requirements for the deployment context (e.g., financial services may require 7+ years). Operators are responsible for configuring retention and archival to immutable storage (e.g., S3 Glacier) for their specific compliance needs.
- At rest: CockroachDB supports encryption at rest for the storage layer. Enable this in production deployments.
- In transit: All database connections should use TLS. Configure CockroachDB with valid certificates and require TLS for client connections.
- Backup encryption: Database backups must be encrypted before storage. Use CockroachDB's built-in encrypted backup or encrypt at the storage layer.
- Store database connection strings in environment variables or a secrets manager (e.g., Kubernetes Secrets, HashiCorp Vault). Never commit connection strings to source control.
- Use separate database credentials per service. Each microservice connects only to its own schema (enforced by ADR-0020 which rejected cross-service database access).
- Rotate database credentials regularly. Use short-lived credentials where possible.
- Database migrations are managed through versioned SQL files. Each migration is immutable once applied.
- Migration files must not contain secrets (credentials, API keys, encryption keys). Use parameterized values or environment variable references for sensitive configuration.
- Review all migration files for SQL injection vectors before applying to production.
When contributing to or deploying Meridian:
- Keep dependencies up to date
- Follow secure coding practices outlined in our contribution guidelines
- Use environment variables for sensitive configuration
- Enable authentication and authorization in production deployments
(
AUTH_ENABLED=true) - Regular security audits of your deployment configuration
- Use
pq.QuoteIdentifier()for all dynamic SQL identifiers - All database access must use GORM through tenant-scoped transactions; avoid raw SQL queries that bypass audit hooks
- Validate tenant IDs at system boundaries using
tenant.NewTenantID()
This security policy applies to:
- The core Meridian platform and all services
- Official container images and deployment configurations
- Documentation that affects security posture
The following are generally not considered security vulnerabilities:
- Issues requiring unauthorized physical access to infrastructure
- Social engineering attacks against users
- Vulnerabilities in third-party dependencies (report to upstream projects)
- Issues already publicly disclosed or known
For questions about this security policy, open a GitHub Discussion or contact the project maintainers.