Skip to main content

Security Model

SeptemCore is designed on the principle that everything is encrypted, always, everywhere — without exceptions. Security is not a feature; it is a structural property of the platform enforced at every layer.


1. Seven-Layer Encryption

Every data pathway has its own encryption guarantee. The layers are independent — compromising one does not compromise others.

LayerWhat is encryptedStandardImplementation
At restDatabases, files, backups, logsAES-256PostgreSQL TDE, S3 server-side encryption, ClickHouse encrypted volumes
In transit (external)All HTTP, WebSocket, gRPC (client-facing)TLS 1.3HTTPS-only, HSTS, certificate pinning
In transit (internal)Service-to-service communicationmTLSMutual TLS via Istio service mesh
Field-level (PII)Email, phone, IP address columnsAES-256-GCMColumn-level encryption in PostgreSQL (shared/crypto)
EnvelopeEncryption keys (DEK, KEK)RSA-2048+DEK → KEK → Master Key (HSM)
SecretsAPI keys, passwords, tokensHashiCorp Vault only — never in code, ENV, or git
BackupsAll backup archivesAES-256Encrypted before upload, independent key rotation

2. Key Hierarchy

All cryptographic material flows through a three-tier envelope encryption scheme:

Master Key (HSM — FIPS 140-3 Level 3)
└── KEK — Key Encryption Key (HashiCorp Vault)
└── DEK — Data Encryption Key (application layer)
  • Master Key lives in a Hardware Security Module certified to FIPS 140-3 Level 3. It never leaves the HSM in plaintext.
  • KEK is managed by HashiCorp Vault. Vault uses the HSM to unwrap it on startup and serve it to authorised services.
  • DEK is generated per data classification (e.g. one DEK per tenant PII store). The application holds the DEK in memory only — never on disk or in environment variables.

Rotation policy: Automatic 90-day rotation for all keys. Dual-key strategy: During rotation the old key remains valid for decryption while the new key signs all new writes. Zero downtime, zero manual steps.


3. JWT Signing Key Lifecycle

JWT access tokens are signed with ES256 (ECDSA P-256). The signing key is managed through Vault with a strict in-memory caching strategy:

PhaseBehaviour
INITIAM service fetches the JWT signing key from Vault and loads it into process memory. Startup fails if Vault is unreachable.
RuntimeEvery token signature uses the in-memory key — no per-request Vault round-trip.
RotationVault pushes a watch notification → IAM loads the new key into memory. Dual-key window: old key continues to validate existing tokens; new key signs all new tokens.
Vault runtime outageIAM continues with the current in-memory key. Rotation is blocked. An alert fires via system.health Notify channel.
Vault prolonged outageAfter KEY_MAX_AGE (default: 7 days) without successful rotation, IAM enters DEGRADED mode and fires a critical level alert.

Token characteristics:

PropertyValue
AlgorithmES256 (ECDSA P-256)
Access token TTL15 minutes
Refresh token TTL7 days
Max JWT size8 KB (HTTP header budget)
PII in claimsProhibited — JWT is base64-readable without the key

4. Password Security — Argon2id

Passwords are hashed with Argon2id (OWASP 2024 recommendation). bcrypt is prohibited for new implementations.

// services/iam/internal/service/auth_service.go
import "golang.org/x/crypto/argon2"

const (
ArgonTime = 1 // iterations
ArgonMemory = 64 * 1024 // 64 MB
ArgonThreads = 4
ArgonKeyLen = 32
)

func hashPassword(password string, salt []byte) []byte {
return argon2.IDKey(
[]byte(password), salt,
ArgonTime, ArgonMemory, ArgonThreads, ArgonKeyLen,
)
}
  • Salt: 16 bytes, cryptographically random (crypto/rand).
  • Timing-safe comparison (subtle.ConstantTimeCompare) — prevents timing side-channel attacks.
  • Anti-enumeration: login, register, and password-reset responses use identical timing and identical error messages for valid and invalid accounts.

5. mTLS — Service-to-Service Security

All internal gRPC traffic between services uses mutual TLS. Each service presents a client certificate; the server validates it before processing any RPC.

Client service ──── TLS handshake (both sides present cert) ──── Server service
↑ certificates managed by Istio / Vault SDS
PropertyValue
ProtocolmTLS (both client and server authenticate)
Certificate authorityInternal CA managed by HashiCorp Vault PKI
Certificate rotationAutomatic (Istio Citadel / Vault agent sidecar)
ImplementationIstio service mesh sidecar injection; shared/mtls helpers for non-sidecar scenarios

No service can call another without a valid certificate issued by the platform CA. An attacker who compromises the network cannot impersonate a service without the corresponding private key.


6. Module Sandbox — CSP and Isolation

Third-party module code runs inside the UI Shell under a strict Content Security Policy. The Shell enforces the following rules:

ThreatDefence
Malicious module injecting scriptsStrict CSP — script-src 'self' cdn.septemcore.com — external script sources are blocked
XSS between modules (cross-MFE)Sandbox isolation: each MFE runs in its own React tree; no direct DOM access across module boundaries
Supply chain attack via npmSCA scanning (Snyk) on every module bundle before it is accepted by the Module Registry
Unauthorised module loadingModule Registry validates a digital signature on every bundle before it is served to the Shell
Data exfiltration via fetchCSP connect-src restricts outgoing XHR/fetch to api.septemcore.com and the tenant's own domain

Module Signing Flow

1. Developer builds module (pnpm build)
2. CLI signs bundle: kernel-cli sign --key developer.pem remoteEntry.js
3. CLI uploads to Module Registry: POST /api/v1/modules/install
4. Module Registry verifies signature against developer's registered public key
5. Signature valid → bundle stored in S3 and CDN URL written to manifest
6. Signature invalid → 422 Unprocessable Entity — bundle rejected

Modules signed by verified publishers receive a verified badge in the Admin module catalogue. Unsigned modules can only be installed by Platform Owners (development workflow).


7. Row-Level Security — Tenant Isolation

Every table that stores tenant data has a PostgreSQL Row-Level Security policy enforced at the database engine level. No application code change can bypass it:

-- Applied to every module data table during migration
ALTER TABLE contacts ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON contacts
USING (tenant_id = current_setting('app.tenant_id')::uuid);

The application sets app.tenant_id from the JWT tenantId claim at the beginning of every database session. Even if a bug in application code constructs a malformed query, PostgreSQL silently filters out rows that do not belong to the current tenant.

ClickHouse double isolation: Because ClickHouse does not support RLS natively, the platform enforces isolation at two levels:

  1. ClickHouse Row PolicyCREATE ROW POLICY on each table.
  2. Application-layer WHERE clause — every ClickHouse query generated by the Data Layer service unconditionally appends WHERE tenant_id = ? using the tenant ID from the JWT context.

8. Secret Management — HashiCorp Vault

No secret ever appears in:

  • Source code
  • Environment variables in any deployment manifest
  • Docker images
  • Git history

All secrets are fetched at service startup via the Vault agent sidecar or the vault shared library (services/vault/):

// services/vault/vault.go (simplified)
type Client struct {
client *vault.Client
}

func (c *Client) GetSecret(ctx context.Context, path string) (string, error) {
secret, err := c.client.KVv2("secret").Get(ctx, path)
if err != nil {
return "", fmt.Errorf("vault get secret %s: %w", path, err)
}
return secret.Data["value"].(string), nil
}

Secrets used by the platform:

SecretVault pathConsumer
JWT signing key (ES256 private key)secret/iam/jwt-signing-keyIAM service
PostgreSQL credentialssecret/{service}/postgresEach Go service
Kafka credentialssecret/kafkaEvent Bus, Audit
AES-256-GCM DEKsecret/crypto/field-dekshared/crypto
TLS certificatespki/issue/{service}All services (mTLS)
Integration provider credentialssecret/integrations/{tenantId}/{providerId}Integration Hub

9. Transport Security

PropertyRequirement
External TLS versionTLS 1.3 (TLS 1.2 allowed for legacy clients; 1.0 and 1.1 prohibited)
HSTSmax-age=31536000; includeSubDomains; preload
Certificate authorityLet's Encrypt (ACME) for public endpoints; internal Vault PKI for services
SSL renewal30 days before expiry — automatic via ACME or Vault PKI
Certificate storagePrivate keys in HashiCorp Vault — never on disk or in K8s Secrets

Custom domains provisioned via the Domain Resolver service use the same Let's Encrypt ACME pipeline. Envoy Secret Discovery Service (SDS) fetches certificates on demand from Vault — Envoy never loads all certificates at startup (lazy fetch prevents memory bloat at scale).


10. Threat Model Summary

ThreatPrimary defenceSecondary defence
Malicious module codeCSP, module signingSCA scan (Snyk)
XSS between MFEsSandbox isolation, CORSReact error boundaries
Supply-chain attackSCA on every bundlePinned exact versions (no ^)
Cross-tenant data leakPostgreSQL RLSClickHouse Row Policy + app WHERE
JWT forgeryES256 signature (HSM-backed key)Vault key rotation every 90 days
Password brute forceArgon2id (64 MB / 1 iteration)Rate limit 5/min per IP+email
Secret exposureVault agent — never in ENVSealed Secrets in git (Kubeseal)
Service impersonationmTLS (both sides authenticated)Istio SPIFFE/SVID identity
DDoSCloudflare/Akamai edge + rate limitingToken-bucket per-tenant 1 000 RPS
Insider threat / API abuseFull audit trail, immutable ClickHouseGDPR anonymization, 7-year retention