{"record":{"id":"8b4edb9b823ac1a5","repo":"JuliusBrussee/caveman","slug":"production-requires-s-to-contain-at-least-32-char","errorCode":null,"errorMessage":"production requires %s to contain at least 32 characters","messagePattern":"production requires (.+?) to contain at least 32 characters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"shared/platform/env/env.go","lineNumber":104,"sourceCode":"\tif err := validateProductionTextSecrets([]string{\"CAVE_KEY_HASH_PEPPER\"}); err != nil {\n\t\treturn err\n\t}\n\tif Bool(\"CAVE_REPLAY_ENABLED\", false) {\n\t\tif err := validateProductionTextSecrets([]string{\"CAVE_ROUTER_REPLAY_TOKEN\"}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn validateProductionPublicURL()\n}\n\nfunc validateProductionTextSecrets(names []string) error {\n\tfor _, name := range names {\n\t\tv := strings.TrimSpace(os.Getenv(name))\n\t\tif v == \"\" || v == \"generated\" || strings.Contains(strings.ToLower(v), \"changeme\") {\n\t\t\treturn fmt.Errorf(\"production refuses default or empty %s\", name)\n\t\t}\n\t\tif len(v) < 32 {\n\t\t\treturn fmt.Errorf(\"production requires %s to contain at least 32 characters\", name)\n\t\t}\n\t\tif distinctBytes([]byte(v)) < 8 {\n\t\t\treturn fmt.Errorf(\"production refuses low-diversity %s\", name)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateProductionPublicURL() error {\n\tpublicURL := strings.TrimSpace(os.Getenv(\"CAVE_PUBLIC_URL\"))\n\tu, err := url.Parse(publicURL)\n\tif err != nil || u.Scheme != \"https\" || u.Hostname() == \"\" || u.User != nil || u.RawQuery != \"\" || u.Fragment != \"\" || (u.Path != \"\" && u.Path != \"/\") {\n\t\treturn fmt.Errorf(\"production requires CAVE_PUBLIC_URL to be an HTTPS origin without credentials, path, query, or fragment\")\n\t}\n\treturn nil\n}\n\nfunc distinctBytes(value []byte) int {","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/env/env.go#L86-L122","documentation":"Produced by validateProductionTextSecrets (env.go:104) during the production gates: the secret passed the empty/placeholder check but is shorter than 32 characters. The library enforces a minimum entropy budget by raw length; a short pepper or signing key is brute-forceable and therefore refuses boot in production. The variable name is included.","triggerScenarios":"CAVE_ENV is production and a checked secret has fewer than 32 characters after trimming (31 chars fails; 32 passes this check, then the diversity check applies).","commonSituations":"A human-chosen password phrase like 'cave-prod-2024' used as a pepper; short dev tokens promoted to prod; base64 of a 16-byte value (22-24 chars); truncation by a secret manager field limit.","solutions":["Replace the secret with at least 32 characters of random material: openssl rand -base64 48.","If a key is derived from a passphrase, feed the passphrase through a KDF at rest and store the long output instead.","Check the secret was not truncated in transit - compare lengths between source and process env.","Re-run the boot validation after updating the injection."],"exampleFix":"# before\nCAVE_KEY_HASH_PEPPER=s3cr3t   # 6 chars\n\n# after\nCAVE_KEY_HASH_PEPPER=$(openssl rand -base64 48)","handlingStrategy":"validation","validationCode":"if len(strings.TrimSpace(secret)) < 32 {\n    return fmt.Errorf(\"secret too short: need >=32 chars\")\n}","typeGuard":null,"tryCatchPattern":"if err := env.RefuseProductionDefaults(); err != nil {\n    return err // regenerate the named secret with more material\n}","preventionTips":["Standardize on openssl rand -base64 48 output for every text secret.","Add secret-length assertions to your provisioning scripts.","Check secret lengths after secret-manager injection, since some managers truncate."],"tags":["go","production","secrets","config","security"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}