{"record":{"id":"05577e9d93b6a794","repo":"semaphoreui/semaphore","slug":"access-key-expired","errorCode":null,"errorMessage":"access key expired","messagePattern":"access key expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/server/access_key_encryption_svc.go","lineNumber":22,"sourceCode":"\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/semaphoreui/semaphore/db\"\n\t\"github.com/semaphoreui/semaphore/pkg/common_errors\"\n\t\"github.com/semaphoreui/semaphore/pkg/tz\"\n\tpro \"github.com/semaphoreui/semaphore/pro/services/server\"\n)\n\nconst RekeyBatchSize = 100\n\nvar ErrReadOnlyStorage = errors.New(\"cannot modify secret in read-only storage\")\n\n// ErrAccessKeyExpired is returned when a key with ExpireAt in the past is\n// deserialized. Expired secrets must never be usable.\nvar ErrAccessKeyExpired = errors.New(\"access key expired\")\n\ntype AccessKeyEncryptionService interface {\n\tSerializeSecret(key *db.AccessKey) error\n\tDeserializeSecret(key *db.AccessKey) error\n\tFillEnvironmentSecrets(env *db.Environment, deserializeSecret bool) error\n\tDeleteSecret(key *db.AccessKey) error\n\tRekeyAccessKeys(oldKey string) (err error)\n\n\t// Task survey secrets: task-bound, expiring access keys\n\t// (owner AccessKeyTaskSecret). See task_secret_svc.go.\n\tCreateTaskSurveySecrets(projectID int, taskID int, secrets string, expireAt time.Time) error\n\tGetTaskSurveySecrets(projectID int, taskID int) (string, error)\n\tDeleteTaskSurveySecrets(projectID int, taskID int) error\n}\n\nfunc NewAccessKeyEncryptionService(\n\taccessKeyRepo db.AccessKeyManager,\n\tenvironmentRepo db.EnvironmentManager,","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/server/access_key_encryption_svc.go#L4-L40","documentation":"ErrAccessKeyExpired is returned by DeserializeSecret when the access key has an ExpireAt timestamp in the past. Expired secrets must never be usable, so any code path that materializes secret values (task dispatch, environment fill, remote job prep) fails with this sentinel so callers can distinguish expiry from other secret errors.","triggerScenarios":"DeserializeSecret(key) with key.ExpireAt != nil and tz.Now().After(*key.ExpireAt); reached from prepareRemoteJob, run, task survey secret gathering (api/runners/runners.go), and FillEnvironmentSecrets.","commonSituations":"Running a scheduled/old task whose access key TTL has elapsed; long-lived tasks re-reading secrets after the key expired; retrying a job that was queued past the key's expiry.","solutions":["Extend or remove ExpireAt on the access key (or create a new key) and update tasks referencing it.","Detect with errors.Is(err, ErrAccessKeyExpired) and surface a user-friendly 'key expired, run again' message (as the runner API does).","Re-run the task after rotating the expired key.","Set expiry policy on keys used by recurring tasks long enough to cover their schedule."],"exampleFix":"// before\nif err := encryptionService.DeserializeSecret(&key); err != nil { return err }\n// after\nif err := encryptionService.DeserializeSecret(&key); err != nil {\n    if errors.Is(err, server.ErrAccessKeyExpired) {\n        return fmt.Errorf(\"access key %q expired; please rotate it and re-run\", key.Name)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"if key.ExpireAt != nil && time.Now().After(*key.ExpireAt) {\n    return fmt.Errorf(\"access key %q expired at %s\", key.Name, key.ExpireAt)\n}","typeGuard":"func keyUsable(k db.AccessKey) bool { return k.ExpireAt == nil || time.Now().Before(*k.ExpireAt) }","tryCatchPattern":"if err := encryptionService.DeserializeSecret(&key); err != nil {\n    if errors.Is(err, server.ErrAccessKeyExpired) {\n        tsk.Log(\"Survey secrets expired before the task started. Please run the task again.\")\n        return\n    }\n    return err\n}","preventionTips":["Set ExpireAt generously for keys used by scheduled tasks.","Rotate expired keys proactively via monitoring on ExpireAt.","Always check expiry before dispatching jobs that embed secrets."],"tags":["secrets","access-key","expiry","ttl"],"backgroundTag":"jwt-token-expired","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"}