{"record":{"id":"cf1126582de13075","repo":"semaphoreui/semaphore","slug":"source-storage-key-is-required","errorCode":null,"errorMessage":"source storage key is required","messagePattern":"source storage key is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/server/access_key_serializer_local.go","lineNumber":100,"sourceCode":"\t\treturn util.Config.DecryptAccessSecret(stored)\n\t})\n}\n\n// DeserializeSecret2 decrypts using a single explicit key (stripping any key-id\n// prefix). It is kept for the rekey `--old-key` path and for tests.\nfunc (d *LocalAccessKeyDeserializer) DeserializeSecret2(key *db.AccessKey, encryptionString string) (res string, err error) {\n\treturn d.deserialize(key, func(stored string) ([]byte, error) {\n\t\treturn util.Config.DecryptAccessSecretWithKey(stored, encryptionString)\n\t})\n}\n\n// deserialize handles the source-storage / legacy / nil cases, then decrypts the\n// stored ciphertext with the supplied decryptor (keyset by id, or an explicit key).\nfunc (d *LocalAccessKeyDeserializer) deserialize(key *db.AccessKey, decrypt func(string) ([]byte, error)) (res string, err error) {\n\n\tif key.SourceStorageType != nil {\n\t\tif key.SourceStorageKey == nil {\n\t\t\treturn \"\", fmt.Errorf(\"source storage key is required\")\n\t\t}\n\n\t\tswitch *key.SourceStorageType {\n\t\tcase db.AccessKeySourceStorageEnv:\n\t\t\tres = os.Getenv(*key.SourceStorageKey)\n\t\t\treturn\n\t\tcase db.AccessKeySourceStorageFile:\n\n\t\t\tfilePath := filepath.Clean(*key.SourceStorageKey)\n\t\t\tif !filepath.IsAbs(filePath) {\n\t\t\t\terr = common_errors.NewUserErrorS(\"file path must be absolute\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor _, segment := range strings.Split(filepath.ToSlash(*key.SourceStorageKey), \"/\") {\n\t\t\t\tif segment == \"..\" {\n\t\t\t\t\terr = common_errors.NewUserErrorS(\"file path must not contain traversal segments\")\n\t\t\t\t\treturn","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/server/access_key_serializer_local.go#L82-L118","documentation":"LocalAccessKeyDeserializer.deserialize handles keys that were originally stored in an external source (env var or vault) before being imported locally. If SourceStorageType is set, SourceStorageKey must also be set — it names where the secret lives. A nil SourceStorageKey with a non-nil SourceStorageType is an inconsistent record and yields this error.","triggerScenarios":"DeserializeSecret/DeserializeSecret2 on an AccessKey with SourceStorageType != nil but SourceStorageKey == nil (e.g. partially migrated key, DB row missing the source key column).","commonSituations":"Interrupted migrations from env-var/vault-backed secrets; backup/restore dropping the source-storage-key column; hand-edited access_keys rows.","solutions":["Populate key.SourceStorageKey with the original env var name / vault path before deserializing.","If the secret now lives locally, clear SourceStorageType so the normal local-decryption path is used.","Re-run the migration tooling that converts source-stored secrets into local encrypted secrets."],"exampleFix":"// before\nkey.SourceStorageType = &t // set, but no source key\n// after\nsrc := \"MY_SECRET_ENV_VAR\"\nkey.SourceStorageType = &t\nkey.SourceStorageKey = &src","handlingStrategy":"validation","validationCode":"if key.SourceStorageType != nil && key.SourceStorageKey == nil {\n    // inconsistent record: set SourceStorageKey or clear SourceStorageType\n}","typeGuard":null,"tryCatchPattern":"if _, err := svc.DeserializeSecret(key); err != nil && strings.Contains(err.Error(), \"source storage key is required\") { /* repair the record before retry */ }","preventionTips":["Run migrations atomically so SourceStorageType and SourceStorageKey are written together.","Include source-storage columns in DB backups/restores.","Verify imported keys have consistent source-storage metadata."],"tags":["go","secrets","migration","data-integrity"],"backgroundTag":"missing-required-argument","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"}