{"record":{"id":"bb8e046b8083c651","repo":"JuliusBrussee/caveman","slug":"s-is-not-valid-base64-w","errorCode":null,"errorMessage":"%s is not valid base64: %w","messagePattern":"(.+?) is not valid base64: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/secretbox/secretbox.go","lineNumber":43,"sourceCode":"\t\"time\"\n\n\t\"github.com/JuliusBrussee/caveman/shared/platform/kms\"\n\t\"github.com/JuliusBrussee/caveman/shared/platform/runtimeenv\"\n)\n\n// envKey is the name of the environment variable holding the base64-encoded\n// 32-byte master key.\nconst envKey = \"CAVE_LOCAL_ENCRYPTION_KEY\"\n\n// loadKey reads and validates the 32-byte AES key from the environment.\nfunc loadKey() ([]byte, error) {\n\tkeyB64 := os.Getenv(envKey)\n\tif keyB64 == \"\" {\n\t\treturn nil, fmt.Errorf(\"%s is not set; cannot encrypt/decrypt secrets\", envKey)\n\t}\n\tkeyBytes, err := base64.StdEncoding.DecodeString(keyB64)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%s is not valid base64: %w\", envKey, err)\n\t}\n\tif len(keyBytes) != 32 {\n\t\treturn nil, fmt.Errorf(\"%s must decode to exactly 32 bytes, got %d\", envKey, len(keyBytes))\n\t}\n\treturn keyBytes, nil\n}\n\n// Encrypt seals plaintext with AES-256-GCM and a fresh random nonce, returning\n// nonce(12) || ciphertext+tag as raw bytes.\nfunc Encrypt(plaintext []byte) ([]byte, error) {\n\tif useKMS() {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\tdefer cancel()\n\t\twrapped, err := kms.Encrypt(ctx, plaintext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"secretbox: KMS encrypt: %w\", err)\n\t\t}\n\t\treturn wrapped, nil","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/secretbox/secretbox.go#L25-L61","documentation":"CAVE_LOCAL_ENCRYPTION_KEY was set but base64.StdEncoding.DecodeString failed on it, so it cannot be raw key material. Std (padded, standard-alphabet) base64 is required: the error commonly means URL-safe characters ('-','_' from base64.URLEncoding), missing padding, stray whitespace/quotes from shell or dotenv handling, or the value was generated as hex or raw bytes instead of base64.","triggerScenarios":"Encrypt/Decrypt in local mode where the env value contains characters outside the standard alphabet, has wrong/missing '=' padding, or wraps a quote/newline — e.g. KEY=\"abc...\" where the quotes became part of the value, or a 64-char hex string.","commonSituations":"Copying keys between systems that use different base64 variants; YAML/env files that fold long lines or append \\r (CRLF); pasting with trailing newline from a password manager; hex output from xxd used where base64 was expected.","solutions":["Regenerate cleanly: openssl rand -base64 32 produces exactly the accepted form; re-export and confirm with a decode check.","Strip whitespace/newlines/quotes from the value when loading it (or fix the env file so they were never stored).","If the key must be URL-safe base64, transcode it first: base64.URLEncoding -> raw bytes -> base64.StdEncoding."],"exampleFix":"# before (URL-safe alphabet or hex -> DecodeString fails)\nexport CAVE_LOCAL_ENCRYPTION_KEY='c2hvcnR'   # wrong length/alphabet\n\n# after\nexport CAVE_LOCAL_ENCRYPTION_KEY=\"$(openssl rand -base64 32)\" # 44 chars, standard alphabet, padded","handlingStrategy":"validation","validationCode":"func localKeyDecodable() bool {\n    v := os.Getenv(\"CAVE_LOCAL_ENCRYPTION_KEY\")\n    if v == \"\" { return false }\n    _, err := base64.StdEncoding.DecodeString(strings.TrimSpace(v))\n    return err == nil\n}","typeGuard":null,"tryCatchPattern":"if _, err := secretbox.Encrypt(pt); err != nil {\n    if strings.Contains(err.Error(), \"not valid base64\") {\n        // regenerate as: openssl rand -base64 32; check for stray quotes/whitespace/CRLF in the env file\n    }\n}","preventionTips":["Generate with openssl rand -base64 32 only; avoid re-encoding or switching alphabets.","Trim whitespace/newlines when reading env files; beware CRLF from Windows editors.","Preflight the key (decodes + 32 bytes) at process start."],"tags":["secrets","base64","environment","configuration"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}