{"record":{"id":"88169a9b176f4f9a","repo":"semaphoreui/semaphore","slug":"encryption-keys-keys-folder-key-q-w","errorCode":null,"errorMessage":"encryption_keys.keys_folder: key %q: %w","messagePattern":"encryption_keys\\.keys_folder: key %q: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/config.go","lineNumber":1593,"sourceCode":"\tif err != nil {\n\t\treturn fmt.Errorf(\"encryption_keys.keys_folder %q: %w\", folder, err)\n\t}\n\tfor _, e := range entries {\n\t\tname := e.Name()\n\t\tif strings.HasPrefix(name, \".\") {\n\t\t\tcontinue\n\t\t}\n\t\tpath := filepath.Join(folder, name)\n\t\tinfo, err := os.Stat(path) // follow symlink\n\t\tif err != nil || !info.Mode().IsRegular() {\n\t\t\tcontinue\n\t\t}\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"encryption_keys.keys_folder: read %q: %w\", name, err)\n\t\t}\n\t\tif err := addLabeled(name, strings.TrimSpace(string(data))); err != nil {\n\t\t\treturn fmt.Errorf(\"encryption_keys.keys_folder: key %q: %w\", name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// EncryptionKeysFile returns the configured keys-file path (encryption.keys_file),\n// or \"\" when no encryption section is configured.\nfunc (conf *ConfigType) EncryptionKeysFile() string {\n\tif conf.Encryption == nil {\n\t\treturn \"\"\n\t}\n\treturn conf.Encryption.KeysFile\n}\n\n// EncryptionKeysPollInterval returns how often the keys file is polled for\n// changes. It defaults to 15s, and returns 0 when polling is disabled\n// (encryption.keys_poll_interval set to \"0\"). An unparseable value falls back to\n// the default.","sourceCodeStart":1575,"sourceCodeEnd":1611,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/config.go#L1575-L1611","documentation":"After successfully reading a file from encryption.keys_folder, the loader passes its trimmed content to addLabeled, which validates the key material (base64/AES size). If validation fails the error is wrapped as 'encryption_keys.keys_folder: key %q: %w', naming the offending file. The keyring cannot be built and the server fails fast at startup.","triggerScenarios":"loadKeysFolder() calls addLabeled(name, strings.TrimSpace(string(data))) for a file in encryption.keys_folder and the file's content is not a valid key (not base64, or decoded length not 16/24/32 bytes).","commonSituations":"A file placed in the keys folder contains a raw passphrase, a PEM block, trailing notes/comments, or a key generated with the wrong byte length; a README or backup file was accidentally left in the folder; an editor added whitespace/BOM (whitespace is trimmed, BOM is not).","solutions":["Regenerate the offending file's content with a valid key: `openssl rand -base64 32` and write only that value into the file.","Remove non-key files (README, backups, editor swap files) from encryption.keys_folder — every non-dot file is treated as a key.","Verify each file decodes to exactly 16, 24, or 32 bytes: `base64 -d <file> | wc -c`.","Strip any BOM or formatting: the content must be a single base64 string; use `printf '%s' \"$(cat file)\" > file` to normalize."],"exampleFix":"// before: keys folder contains a passphrase file\n$ cat /etc/semaphore/encryption-keys/mykey\nmy-secret-passphrase\n// after\n$ openssl rand -base64 32 > /etc/semaphore/encryption-keys/mykey\n$ base64 -d /etc/semaphore/encryption-keys/mykey | wc -c  # must print 32","handlingStrategy":"validation","validationCode":"// Validate each key file before the server starts\nfor _, f := range keyFiles {\n    data, _ := os.ReadFile(f)\n    raw, err := base64.StdEncoding.DecodeString(strings.TrimSpace(string(data)))\n    if err != nil || (len(raw) != 16 && len(raw) != 24 && len(raw) != 32) {\n        return fmt.Errorf(\"key %q invalid: decoded %d bytes\", f, len(raw))\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := util.ReloadEncryptionKeys(); err != nil {\n    log.Fatalf(\"invalid key in keys_folder: %v\", err)\n}","preventionTips":["Generate keys only with `openssl rand -base64 32`.","Never hand-edit key files; automate rotation scripts to write exact base64 content.","Add a CI/startup lint step that checks every file in the keys folder decodes to 16/24/32 bytes."],"tags":["config","encryption","aes","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}