{"record":{"id":"06f9c9983de454af","repo":"semaphoreui/semaphore","slug":"s-read-key-file-q-w","errorCode":null,"errorMessage":"%s: read key file %q: %w","messagePattern":"(.+?): read key file %q: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/config.go","lineNumber":1448,"sourceCode":"\t\treturn fmt.Errorf(\n\t\t\t\"value of field '%v' is not valid: %v (Must match regex: '%v')\",\n\t\t\tfieldType.Name, strVal, rule,\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// resolveKeySource returns the key material from a KeySource: the inline Value,\n// or the trimmed contents of File. Value and File are mutually exclusive.\nfunc resolveKeySource(ks KeySource, name string) (string, error) {\n\tif ks.Value != \"\" && ks.File != \"\" {\n\t\treturn \"\", fmt.Errorf(\"%s: 'value' and 'file' are mutually exclusive\", name)\n\t}\n\tif ks.File != \"\" {\n\t\tdata, err := os.ReadFile(ks.File)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"%s: read key file %q: %w\", name, ks.File, err)\n\t\t}\n\t\treturn strings.TrimSpace(string(data)), nil\n\t}\n\treturn ks.Value, nil\n}\n\n// resolveEncryptionKeysFrom builds the runtime keyset from the keys-file config\n// plus the legacy flat fields, validating every resolved key. It does not mutate\n// global state. The flat fields are added to the registry (so new writes can stamp\n// them) and recorded as the legacy no-prefix decrypt keys.\nfunc resolveEncryptionKeysFrom(enc *EncryptionKeysConfig, flatAccess, flatOption string) (*keyset, error) {\n\tks := &keyset{\n\t\tbyID:         map[string]string{},\n\t\tlegacyAccess: flatAccess,\n\t\tlegacyOption: flatOption,\n\t}\n\n\t// byLabel maps a human label (inline keys map key, or a folder filename) to its","sourceCodeStart":1430,"sourceCodeEnd":1466,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/config.go#L1430-L1466","documentation":"resolveKeySource wraps the os.ReadFile failure when a KeySource specifies a `file` path that cannot be read. The %w preserves the underlying OS error (ENOENT, permission denied, is-a-directory), so the message reads `<name>: read key file \"<path>\": <os error>`.","triggerScenarios":"encryption_keys.keys.<label>.file pointing at a non-existent path, a path the semaphore process cannot read, a directory instead of a file, or a path valid on the operator's machine but not inside the container.","commonSituations":"Docker/K8s secret not mounted at the configured path; wrong relative path (resolved against process CWD, not config dir); permissions after running as non-root; secret name typo in the deployment.","solutions":["Verify the file exists at the exact path: `ls -l <path>` from inside the container","Mount the secret/key file into the container at the configured path and fix the volume/secret reference","Fix file permissions so the semaphore process user can read it (e.g. chmod 640, correct owner)","Use an absolute path, or confirm the process working directory for relative paths"],"exampleFix":"# before\nkeys:\n  primary:\n    file: ./secrets/primary.key   # not mounted in container\n# after\nkeys:\n  primary:\n    file: /etc/semaphore/keys/primary.key","handlingStrategy":"validation","validationCode":"if ks.File != \"\" {\n    if _, err := os.Stat(ks.File); err != nil {\n        return fmt.Errorf(\"key file %s unreadable before start: %w\", ks.File, err)\n    }\n}","typeGuard":"func readableFile(path string) bool {\n    f, err := os.Open(path)\n    if err != nil { return false }\n    f.Close()\n    return true\n}","tryCatchPattern":"material, err := resolveKeySource(ks, name)\nif err != nil {\n    log.Fatalf(\"key material unavailable (%s): %v — check mount and permissions\", name, err)\n}","preventionTips":["Use absolute paths for key files in containerized deployments","Verify secret mounts exist inside the container, not just on the host","Set file ownership/permissions readable by the semaphore process user","Test key loading after every deployment or secret rotation"],"tags":["config","encryption","file","filesystem","semaphore"],"backgroundTag":"file-read-failed","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"}