{"record":{"id":"159b3c6bb095a067","repo":"crowdsecurity/crowdsec","slug":"empty-master-secret","errorCode":null,"errorMessage":"empty master secret","messagePattern":"empty master secret","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"pkg/appsec/challenge/secret.go","lineNumber":38,"sourceCode":"\n// generateRandomSecret returns a fresh 32-byte secret suitable for use when\n// no master_secret is configured. Single-instance deployments are fine with\n// this; distributed deployments MUST configure a shared secret because each\n// instance generates an independent random one here.\nfunc generateRandomSecret() ([]byte, error) {\n\tbuf := make([]byte, 32)\n\tif _, err := crand.Read(buf); err != nil {\n\t\treturn nil, fmt.Errorf(\"generate random master secret: %w\", err)\n\t}\n\treturn buf, nil\n}\n\n// ParseConfiguredSecret accepts a configured master secret as either a hex\n// string (preferred — encodes raw bytes unambiguously) or a raw passphrase\n// (fallback for human-edited configs). The result is at least minSecretBytes.\nfunc ParseConfiguredSecret(value string) ([]byte, error) {\n\tif value == \"\" {\n\t\treturn nil, errors.New(\"empty master secret\")\n\t}\n\n\t// Hex form: even length, hex digits only.\n\tif isHex(value) {\n\t\traw, err := hex.DecodeString(value)\n\t\tif err == nil {\n\t\t\tif len(raw) < minSecretBytes {\n\t\t\t\treturn nil, fmt.Errorf(\"hex master secret decodes to %d bytes; minimum is %d\", len(raw), minSecretBytes)\n\t\t\t}\n\t\t\treturn raw, nil\n\t\t}\n\t\t// Fall through to passphrase handling on hex parse failure — defensive.\n\t}\n\n\tif len(value) < minSecretBytes {\n\t\treturn nil, fmt.Errorf(\"passphrase master secret is %d bytes; minimum is %d\", len(value), minSecretBytes)\n\t}\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge/secret.go#L20-L56","documentation":"ParseConfiguredSecret parses the configured appsec master secret, accepting either a hex string or a raw passphrase, and requires a non-empty value yielding at least minSecretBytes. An empty configuration string cannot derive any key material, so it is rejected immediately with this error. It is called by BuildOptions when constructing the challenge runtime.","triggerScenarios":"BuildOptions (or tests TestParseConfiguredSecret_*) passes \"\" as the configured secret value to ParseConfiguredSecret (secret.go:38).","commonSituations":"appsec config YAML has an empty secret/secret_file value; the config key was never set on a fresh install; an env var interpolated to empty at runtime; a config template left the field blank.","solutions":["Set a non-empty master secret in the appsec configuration (hex string preferred, e.g. `openssl rand -hex 32`).","If using a passphrase, ensure it is at least minSecretBytes long.","Check env-var interpolation isn't producing an empty value (e.g. ${SECRET} unset).","Re-generate from the config template so the secret field is populated, then restart crowdsec."],"exampleFix":"// before: empty value in appsec config\napi:\n  appsec:\n    secret: \"\"\n// after\napi:\n  appsec:\n    secret: \"6f1b...generated-hex...\"\n# or: openssl rand -hex 32","handlingStrategy":"validation","validationCode":"// before calling BuildOptions/ParseConfiguredSecret\nif secret == \"\" {\n    return errors.New(\"appsec master secret is not configured\")\n}\nif _, err := challenge.ParseConfiguredSecret(secret); err != nil {\n    return fmt.Errorf(\"invalid master secret: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"key, err := challenge.ParseConfiguredSecret(cfg.AppsecSecret)\nif err != nil {\n    return fmt.Errorf(\"appsec secret config: %w\", err) // includes \"empty master secret\"\n}","preventionTips":["Generate and set the secret at install time (openssl rand -hex 32)","Fail fast at startup with a clear message when the secret is empty","Validate config templates so the secret field is never blank","If secrets come from env, check interpolation resolved to a non-empty value"],"tags":["appsec","config","secret","crypto"],"backgroundTag":"missing-required-config-field","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}