semaphoreui/semaphore · error

source storage key is required

Error message

source storage key is required

What it means

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.

Solutions

  1. Populate key.SourceStorageKey with the original env var name / vault path before deserializing.
  2. If the secret now lives locally, clear SourceStorageType so the normal local-decryption path is used.
  3. Re-run the migration tooling that converts source-stored secrets into local encrypted secrets.

Example fix

// before
key.SourceStorageType = &t // set, but no source key
// after
src := "MY_SECRET_ENV_VAR"
key.SourceStorageType = &t
key.SourceStorageKey = &src
Defensive patterns

Strategy: validation

Validate before calling

if key.SourceStorageType != nil && key.SourceStorageKey == nil {
    // inconsistent record: set SourceStorageKey or clear SourceStorageType
}

Try / catch

if _, err := svc.DeserializeSecret(key); err != nil && strings.Contains(err.Error(), "source storage key is required") { /* repair the record before retry */ }

Prevention

When it happens

Trigger: DeserializeSecret/DeserializeSecret2 on an AccessKey with SourceStorageType != nil but SourceStorageKey == nil (e.g. partially migrated key, DB row missing the source key column).

Common situations: Interrupted migrations from env-var/vault-backed secrets; backup/restore dropping the source-storage-key column; hand-edited access_keys rows.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/cf1126582de13075. Report an issue: GitHub.

Appendix: source

Thrown at services/server/access_key_serializer_local.go:100

		return util.Config.DecryptAccessSecret(stored)
	})
}

// DeserializeSecret2 decrypts using a single explicit key (stripping any key-id
// prefix). It is kept for the rekey `--old-key` path and for tests.
func (d *LocalAccessKeyDeserializer) DeserializeSecret2(key *db.AccessKey, encryptionString string) (res string, err error) {
	return d.deserialize(key, func(stored string) ([]byte, error) {
		return util.Config.DecryptAccessSecretWithKey(stored, encryptionString)
	})
}

// deserialize handles the source-storage / legacy / nil cases, then decrypts the
// stored ciphertext with the supplied decryptor (keyset by id, or an explicit key).
func (d *LocalAccessKeyDeserializer) deserialize(key *db.AccessKey, decrypt func(string) ([]byte, error)) (res string, err error) {

	if key.SourceStorageType != nil {
		if key.SourceStorageKey == nil {
			return "", fmt.Errorf("source storage key is required")
		}

		switch *key.SourceStorageType {
		case db.AccessKeySourceStorageEnv:
			res = os.Getenv(*key.SourceStorageKey)
			return
		case db.AccessKeySourceStorageFile:

			filePath := filepath.Clean(*key.SourceStorageKey)
			if !filepath.IsAbs(filePath) {
				err = common_errors.NewUserErrorS("file path must be absolute")
				return
			}

			for _, segment := range strings.Split(filepath.ToSlash(*key.SourceStorageKey), "/") {
				if segment == ".." {
					err = common_errors.NewUserErrorS("file path must not contain traversal segments")
					return

View on GitHub (pinned to 1774ccb71a)