goharbor/harbor · critical
unauthorized
Error message
unauthorized
What it means
Jobservice DoAuth compares the presented secret against config.GetUIAuthSecret() with subtle.ConstantTimeCompare and returns 'unauthorized' on mismatch. The caller's secret - normally core's jobservice secret - does not equal what this jobservice instance was configured with, so service-to-service auth fails.
Source
Thrown at src/jobservice/api/authenticator.go:71
h := strings.TrimSpace(req.Header.Get(authHeader))
if utils.IsEmptyStr(h) {
return fmt.Errorf("header '%s' missing", authHeader)
}
if !strings.HasPrefix(h, secretPrefix) {
return fmt.Errorf("'%s' should start with '%s'", authHeader, secretPrefix)
}
secret := strings.TrimSpace(strings.TrimPrefix(h, secretPrefix))
// incase both two are empty
if utils.IsEmptyStr(secret) {
return errors.New("empty secret is not allowed")
}
expectedSecret := config.GetUIAuthSecret()
if subtle.ConstantTimeCompare([]byte(expectedSecret), []byte(secret)) == 0 {
return errors.New("unauthorized")
}
return nil
}
View on GitHub (pinned to 7b2fd08cc5)
Solutions
- Set the identical jobservice secret in core's config and jobservice's config (single shared value/env var)
- Restart both core and jobservice after changing the secret
- In compose/Helm, source the secret from one shared configuration entry so drift cannot happen
Example fix
# before: secrets drifted # core env: JOBSERVICE_SECRET=aaa # jobservice env: JOBSERVICE_SECRET=bbb # after: one shared value, both services restarted export JOBSERVICE_SECRET=$(openssl rand -hex 16) # use for BOTH deployments
Defensive patterns
Strategy: validation
Validate before calling
// Deployment guard: both sides must derive the secret from the same source
if coreSecret != jobserviceSecret {
return errors.New("core and jobservice secrets differ: abort deploy")
} Type guard
func isUnauthorizedSecret(err error) bool { return err != nil && strings.Contains(err.Error(), "unauthorized") } Try / catch
if err := sa.DoAuth(req); err != nil {
if strings.Contains(err.Error(), "unauthorized") {
// secret drift: do NOT retry; re-sync secrets and restart both services
return errors.New("jobservice secret mismatch: re-align configuration")
}
return err
} Prevention
- Source the jobservice secret from one shared config value/env var
- Restart core and jobservice together after any secret change
- Add a deployment check comparing secrets before rollout
When it happens
Trigger: Any core-to-jobservice API call (submit job, query status, fetch logs) after the shared secret drifted between the two deployments.
Common situations: Only one component redeployed so its secret rotated independently; secret edited in one env file but not the other; Helm values out of sync between core and jobservice.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16).
Data as JSON: /api/errors/a504af46f380cfde.
Report an issue: GitHub.