Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/platform/config/config.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/platform/config/config.go |
| Original SHA-256 | 75b5797fed30cdd3792075eda2ce1e8d58817fe1f000ee2ae2c0829340cbf58f |
| Git blob SHA | 8f86f8bcc09355e6081d31c76c3f947cab416cae |
| Reading-copy SHA-256 | 75b5797fed30cdd3792075eda2ce1e8d58817fe1f000ee2ae2c0829340cbf58f |
Recovered source transcript
Download complete source text ↓625 lines · 31.4 KB
// Package config loads and validates the application's runtime configuration.
//
// Cloud selection is CONFIGURATION, not code (the cloud-portability mandate): the same binary runs
// on AWS GovCloud or Azure Government by flipping MM_CLOUD and the provider settings. Nothing here
// imports a cloud SDK, adapters live behind interfaces in their own packages and are wired in
// cmd/api from this config.
package config
import (
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
)
// Env is the deployment environment.
type Env string
const (
EnvDevelopment Env = "development"
EnvProduction Env = "production"
)
// Cloud is the target government cloud. Local is for dev (MinIO, self-hosted embeddings, no managed services).
type Cloud string
const (
CloudLocal Cloud = "local"
CloudAWSGovCloud Cloud = "aws_govcloud"
CloudAzureGov Cloud = "azure_gov"
)
// EmbeddingProvider selects the embedding adapter. "fake" is a deterministic offline provider for
// dev/tests; "openai" is any OpenAI compatible endpoint (self hosted vLLM, the production CUI path,
// or OpenAI); "bedrock"/"azure_openai" are the managed gov cloud adapters (wired at deploy time).
type EmbeddingProvider string
const (
ProviderFake EmbeddingProvider = "fake"
ProviderOpenAI EmbeddingProvider = "openai"
ProviderBedrock EmbeddingProvider = "bedrock"
ProviderAzureOpenAI EmbeddingProvider = "azure_openai"
// ProviderMegamind routes embeddings through the stateless MEGAMIND inference plane (the typed
// inference client to the gateway), the production self hosted path for cleared CUI data.
ProviderMegamind EmbeddingProvider = "megamind"
)
// Embeddings configures the embedding provider. Dimension MUST match the vector(N) column width in
// the schema (default 384); changing the provider's native dimension is a re-embed migration, not a
// config flip (STACK_DECISIONS.md §6).
type Embeddings struct {
Provider EmbeddingProvider
Endpoint string // base URL for the OpenAI-compatible / Azure endpoint
Model string // e.g. "text-embedding-3-large", "amazon.titan-embed-text-v2:0", a local model id
Dimension int
APIKey string // bearer/api-key; for Bedrock/Azure-Entra the adapter uses cloud credentials instead
MegamindURL string // base URL of the MEGAMIND gateway when Provider is "megamind"
}
// Inference configures the typed client to the stateless MEGAMIND inference plane (embeddings,
// reranking, generation, faithfulness). The heavy models run out of process on the Apple Silicon
// cluster; this is the only AI compute the FIPS API binary reaches, over HTTP. URL empty means the AI
// layer is unconfigured and its routes degrade rather than fail the boot.
type Inference struct {
URL string // MEGAMIND gateway base, reached over Tailscale
EmbedModel string // 384 dim embedder (all-minilm or bge-small-en-v1.5)
RerankModel string // cross encoder (bge-reranker-v2-m3) or the cosine baseline
GenerateModel string // local instruct model (e.g. llama3.1:8b)
Dimension int // must match the vector(N) column width
TimeoutSeconds int // per request
MaxRetries int // additional attempts on a transient failure
BreakerTrips int // consecutive failures before the breaker opens
BreakerCoolSec int // how long the breaker stays open
TeachingEnabled bool // federated craft signal transmission; disabled until multiple live clients exist
}
// ExtractorProvider selects the Mission Check structured extraction adapter (resume or job description
// text to proposed fields). "deterministic" is the offline rules plus ontology extractor and the
// default; an LLM backed provider (self hosted vLLM for CUI in production) is a future addition behind
// the same port. Extraction runs server side only, so a key here never reaches the browser bundle.
type ExtractorProvider string
const (
ExtractorDeterministic ExtractorProvider = "deterministic"
)
// Extraction configures the Mission Check structured extraction provider. Endpoint, Model, and APIKey
// are reserved for a future provider backed extractor and are unused by the deterministic one.
type Extraction struct {
Provider ExtractorProvider
Endpoint string
Model string
APIKey string
}
// Auth holds the server side secrets and tunables for the authentication module. Every secret here
// loads from the environment (the root owned secrets file), never the repo or the browser bundle.
type Auth struct {
PepperVersion int // active credential pepper version stored on new credentials
Pepper []byte // active credential pepper key (HMAC applied before PBKDF2)
PepperPrevious []byte // optional prior pepper key (version PepperVersion minus 1) for rotation
TOTPKEKVersion int // active TOTP secret KEK version
TOTPKEK []byte // 32 byte AES 256 key encrypting TOTP secrets at rest
TOTPKEKPrevious []byte // optional prior TOTP KEK (version TOTPKEKVersion minus 1) so a rotated KEK still decrypts older secrets
// OrgSecretKEK seals per organization credentials in org_secrets: a customer's own SMTP password today,
// a source connector credential when in place federation lands. It is a SEPARATE key from the TOTP KEK
// on purpose. Both are 32 byte AES 256 keys handled by the same primitive, but they protect different
// populations of secret with different rotation pressure, and one key covering both means one disclosure
// covers both. When it is unset, storing an organization secret fails closed and says so.
OrgSecretKEKVersion int
OrgSecretKEK []byte
OrgSecretKEKPrevious []byte
CSRFKey []byte // HMAC key binding a CSRF token to its session
RecoveryLookupKey []byte // HMAC key for recovery code lookup tags
TOTPIssuer string // issuer shown in the authenticator app
SessionIdleSeconds int // idle timeout (NIST 800-171 3.1.11 default 900)
SessionAbsoluteSeconds int // absolute session cap (default 43200, 12 hours)
PendingAbsoluteSeconds int // auth_pending (pre MFA) session cap (default 300)
InviteExpirySeconds int // invite and activation link lifetime (default 259200, 72 hours)
CookieSecure bool // Secure attribute on the session cookie (true except plain http dev)
MFARequired bool // when false, login mints an active session after password with no TOTP step (pilot only, forbidden in production)
// OperatorMFARequired governs the operator seat alone. Amendment 6 decision 1 makes a second factor
// mandatory for operator from the day the role exists, independent of MM_MFA_REQUIRED, and that stays
// the DEFAULT here so the rule holds unless a deployment deliberately stands it down.
//
// It is a setting rather than a constant because Joseph explicitly directed, on 28 July 2026, that his
// production operator account not be prompted for MFA at this time. Making that a configuration value
// keeps his decision in one auditable place instead of deleting the rule from the code, keeps the
// staging gate proving the rule on its default, and turns re arming it into an env change rather than a
// release. Like MM_MFA_REQUIRED it cannot be stood down in a production MM_ENV.
OperatorMFARequired bool
}
// Config is the fully-resolved, validated application configuration.
type Config struct {
Env Env
HTTPAddr string
// AppDBRole is the database role the app pool MUST authenticate as, asserted at boot. It is
// configuration rather than a constant so an environment can run under its OWN isolated role: staging
// and production share one Postgres cluster, so staging running as the production app role means the
// staging process can reach the production database. Naming the expected role per environment is what
// lets staging hold a role with zero privileges on production while the assertion stays exactly as
// strict, still failing the boot closed on a mismatch and still refusing a role with BYPASSRLS.
AppDBRole string
AppDatabaseURL string // NOBYPASSRLS app role DSN
MigratorDatabaseURL string // BYPASSRLS migrator role DSN (used by goose only)
ComplianceDatabaseURL string // compliance role DSN, the only path that reads the eeo schema; empty disables adverse impact reporting (503)
AioeDatabaseURL string // the dedicated missionmatch_aioe role DSN for the optimization engine batch updater; empty leaves the updater off while outcome capture keeps recording
AioeShadowMode bool // when true the updater proposes candidate weight profiles a human must promote, never auto activating (the safe pilot posture)
SecretsFile string // path of the root owned KEY=VALUE secret file (0600); empty means env only (local dev)
DecisionRetentionDays int // retention window for identifiable screening artifacts after a decision (default 90)
Cloud Cloud
Embeddings Embeddings
Inference Inference
Extraction Extraction
RequireFIPS bool // when true, cmd/api aborts unless crypto/fips140.Enabled()
AllowDevHeaderAuth bool // dev only X-Org-ID resolver toggle; MUST be false in production
// AllowSelfHostedProduction lets a production deployment run with MM_CLOUD=local. It exists so a self
// hosted federation can enforce every OTHER production control without lying about which cloud it is on.
AllowSelfHostedProduction bool
CurationEnforceSeparation bool // MM_CURATION_SEPARATION_OF_DUTIES: require the curation verifier to differ from the approver (default true); a single operator pilot may set it false to self verify, an explicit audited choice
BidOutcomesEnabled bool // MM_BID_OUTCOMES_ENABLED: gate the M7 bid outcomes recording routes (default false); ships DORMANT so the ledger accrues win and loss labels only once an operator turns it on, no learned model consumes it yet
AllowedOrigins []string // CSRF Origin allowlist for unsafe verbs; empty disables the check (dev)
TrustedProxyHeader string // header carrying the real client IP (set by a trusted reverse proxy); empty uses the TCP peer
// AppBaseURL is the public address of the web application, used to build the activation link inside an
// invitation email. It DEFAULTS to the first allowed origin rather than to a guess, because that value is
// already the address the browser really talks to, and a credential bearing link to the wrong hostname is
// worse than no link at all.
AppBaseURL string
Auth Auth
Federated Federated
Tenancy Tenancy
}
// TenancyMode selects how tenant isolation is achieved physically.
type TenancyMode string
const (
// TenancyShared is the pool model: one database, every tenant's rows in the same tables, isolation
// enforced by forced Row Level Security and the per request org GUC. This is the default and the
// posture the product has shipped with.
TenancyShared TenancyMode = "shared"
// TenancyPerTenant is the silo model: each organization's business data lives in its OWN database,
// resolved per request through the tenant directory. RLS stays forced inside every tenant database,
// so the two mechanisms stack rather than one replacing the other.
//
// The reason this mode exists is not only isolation. Because the directory records a host and port
// per tenant, tenants can live on DIFFERENT MACHINES, which is what lets the deployment spread load
// across the federation without ever needing multi master writes: every tenant still has exactly one
// primary, so the row locks that enforce seat caps, stage transitions, and ownership claims keep
// working unchanged.
TenancyPerTenant TenancyMode = "per_tenant"
)
// Tenancy configures the database per tenant routing layer. It is INERT in shared mode: none of the
// pool, directory, or secret resolver machinery is constructed unless Mode is per_tenant.
type Tenancy struct {
Mode TenancyMode
// SecretsBackend resolves a tenant's credential REFERENCE from the directory into a real DSN.
// "env" reads a root owned environment file (development and staging); the government cloud stores
// are wired at deploy so no cloud SDK enters the core build.
SecretsBackend string
// SecretTTL bounds how long a resolved tenant DSN is cached before the backend is consulted again.
// A rotated credential is additionally picked up immediately on an authentication failure, because
// the pool manager forgets the reference and retries once.
SecretTTL time.Duration
// MaxPools caps how many tenant pools are live at once, least recently used evicted beyond it. The
// product of MaxPools and MaxConnsPerPool must stay under the server's max_connections, which is why
// both are configuration rather than constants.
MaxPools int
// MaxConnsPerPool keeps each tenant pool small, since many exist concurrently.
MaxConnsPerPool int
// IdleEvict closes a whole tenant pool after this long with no use, freeing the pool object itself
// rather than merely shrinking it the way pgx per connection idle close does.
IdleEvict time.Duration
}
// Federated configures the cross tenant differential privacy aggregation path (WS8). It is INERT by
// default: the whole federated package is dead code unless Enabled is true and a real cohort of five or
// more live non test tenants exists. Epsilon and Delta are the privacy budget, expressed as
// configuration and never as code constants so a privacy officer can sign off and tune them without a
// rebuild. In tenant LIVE keeps this off, only the in tenant producer and the human approved curation
// pipeline ship enabled.
type Federated struct {
DPEnabled bool // MM_FEDERATED_DP_ENABLED: master switch for the cross tenant DP release path (default false)
DPEpsilon float64 // MM_FEDERATED_DP_EPSILON: the (epsilon, delta) DP budget epsilon target (default 10)
DPDelta float64 // MM_FEDERATED_DP_DELTA: the (epsilon, delta) DP budget delta target (default 1e-7)
}
// Load reads configuration from the environment, applies defaults, and validates it.
func Load() (Config, error) {
// Secret key decode errors are collected and surfaced alongside validation errors, so a
// misconfigured deploy sees every problem at once instead of one boot failure at a time.
var secretErrs []error
getB := func(key string) []byte {
b, err := getBytes(key)
if err != nil {
secretErrs = append(secretErrs, err)
}
return b
}
c := Config{
Env: Env(getDefault("MM_ENV", string(EnvDevelopment))),
HTTPAddr: getDefault("MM_HTTP_ADDR", ":8080"),
AppDBRole: getDefault("MM_APP_DB_ROLE", "missionmatch_app"),
AppDatabaseURL: os.Getenv("MM_DATABASE_URL"),
MigratorDatabaseURL: os.Getenv("MM_MIGRATOR_DATABASE_URL"),
ComplianceDatabaseURL: os.Getenv("MM_COMPLIANCE_DATABASE_URL"),
AioeDatabaseURL: os.Getenv("MM_AIOE_DATABASE_URL"),
AioeShadowMode: os.Getenv("MM_AIOE_SHADOW") == "true",
SecretsFile: os.Getenv("MM_SECRETS_FILE"),
DecisionRetentionDays: getInt("MM_DECISION_RETENTION_DAYS", 90),
Tenancy: Tenancy{
// Shared is the default, so an existing deployment that sets none of these keeps its current
// behaviour exactly. Per tenant routing is opt in, per environment.
Mode: TenancyMode(getDefault("MM_TENANCY_MODE", string(TenancyShared))),
SecretsBackend: getDefault("MM_TENANT_SECRETS_BACKEND", "env"),
SecretTTL: getDuration("MM_TENANT_SECRET_TTL", 15*time.Minute),
MaxPools: getInt("MM_TENANT_MAX_POOLS", 32),
MaxConnsPerPool: getInt("MM_TENANT_MAX_CONNS_PER_POOL", 4),
IdleEvict: getDuration("MM_TENANT_IDLE_EVICT", 15*time.Minute),
},
Cloud: Cloud(getDefault("MM_CLOUD", string(CloudLocal))),
Embeddings: Embeddings{
Provider: EmbeddingProvider(getDefault("MM_EMBED_PROVIDER", string(ProviderFake))),
Endpoint: os.Getenv("MM_EMBED_ENDPOINT"),
Model: os.Getenv("MM_EMBED_MODEL"),
Dimension: getInt("MM_EMBED_DIMENSION", 384),
APIKey: os.Getenv("MM_EMBED_API_KEY"),
MegamindURL: os.Getenv("MM_MEGAMIND_URL"),
},
Inference: Inference{
URL: os.Getenv("MM_MEGAMIND_URL"),
// bge-small-en-v1.5 (384 dim) is the production embedder, served on Ollama as the qllama handle.
// Stored and query vectors MUST use the same model: switching the model requires a FULL re
// backfill (existing vectors from another model are incompatible), see DEPLOY_PLAN.md.
EmbedModel: getDefault("MM_MEGAMIND_EMBED_MODEL", "qllama/bge-small-en-v1.5"),
RerankModel: getDefault("MM_MEGAMIND_RERANK_MODEL", "bge-reranker-v2-m3"),
GenerateModel: getDefault("MM_MEGAMIND_GENERATE_MODEL", "llama3.1:8b"),
Dimension: getInt("MM_EMBED_DIMENSION", 384),
TimeoutSeconds: getInt("MM_MEGAMIND_TIMEOUT_SECONDS", 30),
MaxRetries: getInt("MM_MEGAMIND_MAX_RETRIES", 2),
BreakerTrips: getInt("MM_MEGAMIND_BREAKER_TRIPS", 5),
BreakerCoolSec: getInt("MM_MEGAMIND_BREAKER_COOL_SECONDS", 30),
TeachingEnabled: getBool("MM_MEGAMIND_TEACHING_ENABLED", false),
},
Extraction: Extraction{
Provider: ExtractorProvider(getDefault("MM_EXTRACT_PROVIDER", string(ExtractorDeterministic))),
Endpoint: os.Getenv("MM_EXTRACT_ENDPOINT"),
Model: os.Getenv("MM_EXTRACT_MODEL"),
APIKey: os.Getenv("MM_EXTRACT_API_KEY"),
},
RequireFIPS: getBool("MM_REQUIRE_FIPS", false),
AllowDevHeaderAuth: getBool("MM_ALLOW_DEV_HEADER_AUTH", false),
AllowSelfHostedProduction: getBool("MM_ALLOW_SELF_HOSTED_PRODUCTION", false),
CurationEnforceSeparation: getBool("MM_CURATION_SEPARATION_OF_DUTIES", true),
BidOutcomesEnabled: getBool("MM_BID_OUTCOMES_ENABLED", false),
AllowedOrigins: splitCSV(os.Getenv("MM_ALLOWED_ORIGINS")),
TrustedProxyHeader: os.Getenv("MM_TRUSTED_PROXY_HEADER"),
AppBaseURL: strings.TrimRight(strings.TrimSpace(os.Getenv("MM_APP_BASE_URL")), "/"),
Auth: Auth{
PepperVersion: getInt("MM_CREDENTIAL_PEPPER_VERSION", 1),
Pepper: getB("MM_CREDENTIAL_PEPPER"),
PepperPrevious: getB("MM_CREDENTIAL_PEPPER_PREVIOUS"),
TOTPKEKVersion: getInt("MM_TOTP_KEK_VERSION", 1),
TOTPKEK: getB("MM_TOTP_KEK"),
TOTPKEKPrevious: getB("MM_TOTP_KEK_PREVIOUS"),
OrgSecretKEKVersion: getInt("MM_ORG_SECRET_KEK_VERSION", 1),
OrgSecretKEK: getB("MM_ORG_SECRET_KEK"),
OrgSecretKEKPrevious: getB("MM_ORG_SECRET_KEK_PREVIOUS"),
CSRFKey: getB("MM_CSRF_KEY"),
RecoveryLookupKey: getB("MM_RECOVERY_LOOKUP_KEY"),
TOTPIssuer: getDefault("MM_TOTP_ISSUER", "missionMATCH"),
SessionIdleSeconds: getInt("MM_SESSION_IDLE_SECONDS", 900),
SessionAbsoluteSeconds: getInt("MM_SESSION_ABSOLUTE_SECONDS", 43200),
PendingAbsoluteSeconds: getInt("MM_SESSION_PENDING_SECONDS", 300),
InviteExpirySeconds: getInt("MM_INVITE_EXPIRY_SECONDS", 259200),
CookieSecure: getBool("MM_COOKIE_SECURE", true),
MFARequired: getBool("MM_MFA_REQUIRED", true),
OperatorMFARequired: getBool("MM_OPERATOR_MFA_REQUIRED", true),
},
Federated: Federated{
DPEnabled: getBool("MM_FEDERATED_DP_ENABLED", false),
DPEpsilon: getFloat("MM_FEDERATED_DP_EPSILON", 10),
DPDelta: getFloat("MM_FEDERATED_DP_DELTA", 1e-7),
},
}
if err := c.Validate(); err != nil {
secretErrs = append(secretErrs, err)
}
if len(secretErrs) > 0 {
return Config{}, errors.Join(secretErrs...)
}
return c, nil
}
// Validate enforces invariants and environment-specific requirements. Errors are joined so a
// misconfigured deploy surfaces every problem at once.
func (c Config) Validate() error {
var errs []error
switch c.Env {
case EnvDevelopment, EnvProduction:
default:
errs = append(errs, fmt.Errorf("MM_ENV %q invalid (want development|production)", c.Env))
}
switch c.Cloud {
case CloudLocal, CloudAWSGovCloud, CloudAzureGov:
default:
errs = append(errs, fmt.Errorf("MM_CLOUD %q invalid (want local|aws_govcloud|azure_gov)", c.Cloud))
}
// The activation link host falls back to the first allowed origin, which is already the exact address the
// browser is permitted to talk to. Deriving it means one fewer setting an operator can get wrong, and the
// derived value cannot point somewhere the CSRF allowlist would have refused anyway.
if c.AppBaseURL == "" && len(c.AllowedOrigins) > 0 {
c.AppBaseURL = strings.TrimRight(strings.TrimSpace(c.AllowedOrigins[0]), "/")
}
switch c.Embeddings.Provider {
case ProviderFake, ProviderOpenAI, ProviderBedrock, ProviderAzureOpenAI, ProviderMegamind:
default:
errs = append(errs, fmt.Errorf("MM_EMBED_PROVIDER %q invalid", c.Embeddings.Provider))
}
if c.Embeddings.Dimension <= 0 {
errs = append(errs, errors.New("MM_EMBED_DIMENSION must be > 0"))
}
switch c.Tenancy.Mode {
// Empty is shared. A Config built programmatically rather than through Load leaves Tenancy zero
// valued, and the zero value must mean the default posture rather than a validation failure.
case TenancyShared, "":
case TenancyPerTenant:
// In per tenant mode MM_DATABASE_URL stops being the data plane and becomes the CONTROL PLANE
// pool: it is what the router reads the tenant directory through. Without it there is no way to
// discover any tenant database, so the service could not serve a single request.
if c.AppDatabaseURL == "" {
errs = append(errs, errors.New("MM_DATABASE_URL (the control plane pool) required when MM_TENANCY_MODE=per_tenant"))
}
if c.Tenancy.MaxPools < 1 {
errs = append(errs, errors.New("MM_TENANT_MAX_POOLS must be >= 1"))
}
if c.Tenancy.MaxConnsPerPool < 1 {
errs = append(errs, errors.New("MM_TENANT_MAX_CONNS_PER_POOL must be >= 1"))
}
switch c.Tenancy.SecretsBackend {
case "env", "aws_govcloud", "azure_gov":
default:
errs = append(errs, fmt.Errorf("MM_TENANT_SECRETS_BACKEND %q invalid (want env|aws_govcloud|azure_gov)", c.Tenancy.SecretsBackend))
}
default:
errs = append(errs, fmt.Errorf("MM_TENANCY_MODE %q invalid (want shared|per_tenant)", c.Tenancy.Mode))
}
// Empty selects the deterministic extractor (the same default extraction.New applies), so a Config
// built without an explicit provider validates.
switch c.Extraction.Provider {
case ExtractorDeterministic, "":
default:
errs = append(errs, fmt.Errorf("MM_EXTRACT_PROVIDER %q invalid (want deterministic)", c.Extraction.Provider))
}
// OpenAI-compatible providers need an endpoint + model.
if c.Embeddings.Provider == ProviderOpenAI || c.Embeddings.Provider == ProviderAzureOpenAI {
if c.Embeddings.Endpoint == "" {
errs = append(errs, fmt.Errorf("MM_EMBED_ENDPOINT required for provider %q", c.Embeddings.Provider))
}
if c.Embeddings.Model == "" {
errs = append(errs, fmt.Errorf("MM_EMBED_MODEL required for provider %q", c.Embeddings.Provider))
}
}
// The MEGAMIND provider needs the gateway URL and the embed model name.
if c.Embeddings.Provider == ProviderMegamind {
if c.Embeddings.MegamindURL == "" {
errs = append(errs, errors.New("MM_MEGAMIND_URL required for provider \"megamind\""))
}
if c.Embeddings.Model == "" && c.Inference.EmbedModel == "" {
errs = append(errs, errors.New("MM_EMBED_MODEL or MM_MEGAMIND_EMBED_MODEL required for provider \"megamind\""))
}
}
// TOTP secrets are AES 256, so the KEK must be exactly 32 bytes whenever it is set, in any env.
if len(c.Auth.TOTPKEK) != 0 && len(c.Auth.TOTPKEK) != 32 {
errs = append(errs, errors.New("MM_TOTP_KEK must decode to exactly 32 bytes (AES 256)"))
}
if len(c.Auth.TOTPKEKPrevious) != 0 && len(c.Auth.TOTPKEKPrevious) != 32 {
errs = append(errs, errors.New("MM_TOTP_KEK_PREVIOUS must decode to exactly 32 bytes (AES 256)"))
}
// Same rule for the organization secret KEK, and for the same reason: a key of the wrong length is a boot
// time mistake, not a runtime surprise discovered the first time a customer saves a password.
if len(c.Auth.OrgSecretKEK) != 0 && len(c.Auth.OrgSecretKEK) != 32 {
errs = append(errs, errors.New("MM_ORG_SECRET_KEK must decode to exactly 32 bytes (AES 256)"))
}
if len(c.Auth.OrgSecretKEKPrevious) != 0 && len(c.Auth.OrgSecretKEKPrevious) != 32 {
errs = append(errs, errors.New("MM_ORG_SECRET_KEK_PREVIOUS must decode to exactly 32 bytes (AES 256)"))
}
if c.Auth.PepperVersion <= 0 {
errs = append(errs, errors.New("MM_CREDENTIAL_PEPPER_VERSION must be positive"))
}
if c.Auth.SessionIdleSeconds <= 0 || c.Auth.SessionAbsoluteSeconds <= 0 || c.Auth.PendingAbsoluteSeconds <= 0 {
errs = append(errs, errors.New("session timeout seconds must all be positive"))
}
if c.DecisionRetentionDays <= 0 {
errs = append(errs, errors.New("MM_DECISION_RETENTION_DAYS must be positive"))
}
if c.Env == EnvProduction {
if c.AppDatabaseURL == "" {
errs = append(errs, errors.New("MM_DATABASE_URL required in production"))
}
if c.Embeddings.Provider == ProviderFake {
errs = append(errs, errors.New("the fake embedding provider must not be used in production"))
}
// A production deployment must name a government cloud, UNLESS the operator explicitly declares a
// self hosted deployment with MM_ALLOW_SELF_HOSTED_PRODUCTION.
//
// The check exists to stop someone shipping to a commercial region by accident, not to forbid
// running the platform on hardware you own. Self hosting is a legitimate posture for cleared work,
// and it is arguably the stricter one: nothing leaves the operator's own network. What is NOT
// acceptable is resolving the conflict by setting MM_CLOUD to a government cloud you are not on,
// because that string travels into compliance conversations and would be a false claim.
//
// So the escape hatch is deliberately a SEPARATE, explicitly named flag rather than a widening of
// the cloud vocabulary: MM_CLOUD keeps telling the truth (local), and the operator has to state
// once, on purpose, that local is intended here. It is logged at boot so it can never be a silent
// default that somebody inherits without knowing.
if c.Cloud == CloudLocal && !c.AllowSelfHostedProduction {
errs = append(errs, errors.New(
"MM_CLOUD must be a government cloud in production, or set MM_ALLOW_SELF_HOSTED_PRODUCTION=true "+
"to declare a deliberate self hosted deployment"))
}
// The dev X-Org-ID resolver may never be the production tenant source.
if c.AllowDevHeaderAuth {
errs = append(errs, errors.New("MM_ALLOW_DEV_HEADER_AUTH must be false in production"))
}
// FIPS must be enforced, not optional, so the validated crypto path is the one that runs.
if !c.RequireFIPS {
errs = append(errs, errors.New("MM_REQUIRE_FIPS must be true in production"))
}
// Every auth secret must be present so the validated crypto has its keys.
if len(c.Auth.Pepper) == 0 {
errs = append(errs, errors.New("MM_CREDENTIAL_PEPPER required in production"))
}
if len(c.Auth.TOTPKEK) != 32 {
errs = append(errs, errors.New("MM_TOTP_KEK (32 byte AES 256 key) required in production"))
}
if len(c.Auth.CSRFKey) == 0 {
errs = append(errs, errors.New("MM_CSRF_KEY required in production"))
}
if len(c.Auth.RecoveryLookupKey) == 0 {
errs = append(errs, errors.New("MM_RECOVERY_LOOKUP_KEY required in production"))
}
// The CSRF Origin allowlist must be enforced; an empty list disables it (a dev only path).
if len(c.AllowedOrigins) == 0 {
errs = append(errs, errors.New("MM_ALLOWED_ORIGINS required in production (the CSRF Origin allowlist must be enforced)"))
}
// Both cookies use the __Host- prefix, which a browser only accepts when Secure is set; a non
// Secure production deploy silently breaks all auth and exposes the token.
if !c.Auth.CookieSecure {
errs = append(errs, errors.New("MM_COOKIE_SECURE must be true in production (the __Host- cookie prefix requires Secure)"))
}
// MFA is mandatory in production. The pilot only password only relaxation can never ship to a
// government cloud, so a production deploy with it set is a hard configuration error.
if !c.Auth.MFARequired {
errs = append(errs, errors.New("MM_MFA_REQUIRED must be true in production (MFA is mandatory)"))
}
// The operator relaxation is a pilot accommodation like the one above, and it is refused in a
// production MM_ENV for the same reason: the platform plane reaches every tenant at once, so it is
// the last seat that should ever be reachable with a password alone in a government cloud.
if !c.Auth.OperatorMFARequired {
errs = append(errs, errors.New("MM_OPERATOR_MFA_REQUIRED must be true in production (the platform plane always requires a second factor)"))
}
}
return errors.Join(errs...)
}
// IsProduction reports whether this is a production deployment.
func (c Config) IsProduction() bool { return c.Env == EnvProduction }
func getDefault(key, def string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return def
}
func getInt(key string, def int) int {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
if n, err := strconv.Atoi(v); err == nil {
return n
}
}
return def
}
// getDuration reads a Go duration string such as "15m" or "90s". An unparseable value falls back to the
// default rather than failing the boot, matching how getInt and getBool already behave: a malformed
// tuning knob should not take the service down, and the effective value is logged at startup.
func getDuration(key string, def time.Duration) time.Duration {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
if d, err := time.ParseDuration(v); err == nil {
return d
}
}
return def
}
func getBool(key string, def bool) bool {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
if b, err := strconv.ParseBool(v); err == nil {
return b
}
}
return def
}
func getFloat(key string, def float64) float64 {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
if f, err := strconv.ParseFloat(v, 64); err == nil {
return f
}
}
return def
}
// splitCSV splits a comma separated env value into trimmed, non empty entries.
func splitCSV(v string) []string {
if strings.TrimSpace(v) == "" {
return nil
}
parts := strings.Split(v, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
if t := strings.TrimSpace(p); t != "" {
out = append(out, t)
}
}
return out
}
// getBytes loads a secret key from the environment using an EXPLICIT encoding prefix so the key
// material is never ambiguous: "base64:", "hex:", or "raw:". A bare value (no prefix) is decoded as
// standard base64, and a decode failure is a hard error rather than a silent fall back to the raw
// string. The silent fall back was a real footgun: a passphrase that happens to be valid base64 would
// be decoded to different bytes than the operator intended, so the same variable could mean two
// different keys in two contexts (a TOTP KEK that no longer decrypts its secrets, a pepper that no
// longer verifies its passwords). Forcing the operator to declare the encoding removes that class of
// bug. An unset variable yields nil with no error.
func getBytes(key string) ([]byte, error) {
v := strings.TrimSpace(os.Getenv(key))
if v == "" {
return nil, nil
}
switch {
case strings.HasPrefix(v, "raw:"):
return []byte(strings.TrimPrefix(v, "raw:")), nil
case strings.HasPrefix(v, "hex:"):
b, err := hex.DecodeString(strings.TrimPrefix(v, "hex:"))
if err != nil {
return nil, fmt.Errorf("%s: invalid hex value: %w", key, err)
}
return b, nil
case strings.HasPrefix(v, "base64:"):
b, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(v, "base64:"))
if err != nil {
return nil, fmt.Errorf("%s: invalid base64 value: %w", key, err)
}
return b, nil
default:
b, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, fmt.Errorf("%s: value must be base64, or carry a raw: / hex: / base64: prefix: %w", key, err)
}
return b, nil
}
}