semaphoreui/semaphore · error

secret must be valid json in key

Error message

secret must be valid json in key '%s'

What it means

After decrypting an access key secret, unmarshalAppropriateField parses the plaintext as JSON into the key's typed field. If decryption succeeded but the plaintext is not syntactically valid JSON (json.SyntaxError), the raw parse error is replaced by this clearer message naming the access key. The stored secret was valid ciphertext but its plaintext content is malformed.

Solutions

  1. Inspect and re-save the secret for the named key so its plaintext is valid JSON matching the key type (SshKey or LoginPassword shape).
  2. Re-create the access key via the API/UI so the serializer marshals a correctly structured JSON payload.
  3. If the plaintext is intentionally raw, change the key's type to one that stores plain values or fix the importing pipeline to JSON-encode values.

Example fix

// before: stored secret
my-secret-password
// after: valid JSON for a login-password key
{"login":"deploy","password":"my-secret-password"}
Defensive patterns

Strategy: validation

Validate before calling

var v interface{}
if err := json.Unmarshal([]byte(plaintext), &v); err != nil {
    // fix the stored secret before deserializing
}

Try / catch

if err := svc.DeserializeSecret(key); err != nil && strings.Contains(err.Error(), "must be valid json") { /* re-save or recreate the key secret */ }

Prevention

When it happens

Trigger: DeserializeSecret (invoked via FillEnvironmentSecrets or GetTaskSurveySecrets) on a key whose decrypted secret is not parseable JSON — e.g. someone stored a plain string/password in a key that requires a JSON document.

Common situations: Secrets hand-edited in the secret store; values imported from another system as raw text; SSH/login-password keys overwritten with non-JSON values; encoding corruption after manual export/import.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at services/server/access_key_encryption_svc.go:158

func (s *accessKeyEncryptionServiceImpl) DeserializeSecret(key *db.AccessKey) error {
	if key.ExpireAt != nil && tz.Now().After(*key.ExpireAt) {
		return ErrAccessKeyExpired
	}

	d, _, err := s.getDeserializer(key)
	if err != nil {
		return err
	}
	ciphertext, err := d.DeserializeSecret(key)
	if err != nil {
		return err
	}

	err = unmarshalAppropriateField(key, []byte(ciphertext))

	var syntaxError *json.SyntaxError
	if errors.As(err, &syntaxError) {
		err = fmt.Errorf("secret must be valid json in key '%s'", key.Name)
	}

	return err
}

func (s *accessKeyEncryptionServiceImpl) FillEnvironmentSecrets(env *db.Environment, deserializeSecret bool) error {
	keys, err := s.environmentRepo.GetEnvironmentSecrets(env.ProjectID, env.ID)

	if err != nil {
		return err
	}

	for _, k := range keys {
		var secretName string
		var secretType db.EnvironmentSecretType

		switch k.Owner {
		case db.AccessKeyVariable:

View on GitHub (pinned to 1774ccb71a)