{"record":{"id":"e6e6841186a64004","repo":"goharbor/harbor","slug":"empty-secret-is-not-allowed","errorCode":null,"errorMessage":"empty secret is not allowed","messagePattern":"empty secret is not allowed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/jobservice/api/authenticator.go","lineNumber":66,"sourceCode":"// DoAuth implements same method in interface 'Authenticator'.\nfunc (sa *SecretAuthenticator) DoAuth(req *http.Request) error {\n\tif req == nil {\n\t\treturn errors.New(\"nil request\")\n\t}\n\n\th := strings.TrimSpace(req.Header.Get(authHeader))\n\tif utils.IsEmptyStr(h) {\n\t\treturn fmt.Errorf(\"header '%s' missing\", authHeader)\n\t}\n\n\tif !strings.HasPrefix(h, secretPrefix) {\n\t\treturn fmt.Errorf(\"'%s' should start with '%s'\", authHeader, secretPrefix)\n\t}\n\n\tsecret := strings.TrimSpace(strings.TrimPrefix(h, secretPrefix))\n\t// incase both two are empty\n\tif utils.IsEmptyStr(secret) {\n\t\treturn errors.New(\"empty secret is not allowed\")\n\t}\n\n\texpectedSecret := config.GetUIAuthSecret()\n\tif subtle.ConstantTimeCompare([]byte(expectedSecret), []byte(secret)) == 0 {\n\t\treturn errors.New(\"unauthorized\")\n\t}\n\n\treturn nil\n}\n","sourceCodeStart":48,"sourceCodeEnd":76,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/jobservice/api/authenticator.go#L48-L76","documentation":"Jobservice DoAuth rejects the Authorization header when it carries the 'Secret' prefix but the token after it is empty after trimming (e.g. 'Authorization: Secret' or 'Secret   '). The comment in code says 'in case both two are empty' - an absent secret value is never acceptable.","triggerScenarios":"A component (typically core) calling jobservice APIs with a Secret-scheme header whose value came from an empty environment variable or template placeholder.","commonSituations":"JOBSERVICE_SECRET unset when the caller rendered its header; config templating produced 'Secret ' with nothing after it.","solutions":["Configure the real shared secret on the caller and send 'Authorization: Secret <value>'","Check the env var / config key that renders the header on the caller side","Redeploy so both sides pick up the value"],"exampleFix":"// before\nAuthorization: Secret \n\n// after\nAuthorization: Secret aGVsbG8gam9ic2VydmljZSBzZWNyZXQ=","handlingStrategy":"validation","validationCode":"secret := strings.TrimSpace(os.Getenv(\"JOBSERVICE_SECRET\"))\nif secret == \"\" {\n    return errors.New(\"JOBSERVICE_SECRET must be set before calling jobservice\")\n}\nreq.Header.Set(\"Authorization\", \"Secret \"+secret)","typeGuard":"func isEmptySecretErr(err error) bool { return err != nil && strings.Contains(err.Error(), \"empty secret is not allowed\") }","tryCatchPattern":"if err := sa.DoAuth(req); err != nil {\n    if strings.Contains(err.Error(), \"empty secret\") {\n        return errors.New(\"secret env var not rendered: fix caller config\")\n    }\n    return err\n}","preventionTips":["Validate the secret env var at process start","Disallow empty values in config templates","Add an integration test that asserts the header renders with a non-empty token"],"tags":["harbor","jobservice","auth","secret"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}