{"record":{"id":"519c893b1714af38","repo":"goharbor/harbor","slug":"missing-custom-token-key-data","errorCode":null,"errorMessage":"missing custom token/key data","messagePattern":"missing custom token/key data","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/pkg/p2p/preheat/provider/auth/custom_handler.go","lineNumber":40,"sourceCode":"\n// CustomAuthHandler handle the custom auth mode.\ntype CustomAuthHandler struct {\n\t*BaseHandler\n}\n\n// Mode implements @Handler.Mode\nfunc (c *CustomAuthHandler) Mode() string {\n\treturn AuthModeCustom\n}\n\n// Authorize implements @Handler.Authorize\nfunc (c *CustomAuthHandler) Authorize(req *http.Request, cred *Credential) error {\n\tif err := c.BaseHandler.Authorize(req, cred); err != nil {\n\t\treturn err\n\t}\n\n\tif len(cred.Data) == 0 {\n\t\treturn errors.New(\"missing custom token/key data\")\n\t}\n\n\tkey := reflect.ValueOf(cred.Data).MapKeys()[0].String()\n\treq.Header.Set(key, cred.Data[key])\n\n\treturn nil\n}\n","sourceCodeStart":22,"sourceCodeEnd":48,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/pkg/p2p/preheat/provider/auth/custom_handler.go#L22-L48","documentation":"CustomAuthHandler.Authorize rejects a preheat request when the provider instance uses CUSTOM auth mode but the credential data map is empty — there is no header name/value to set. As with the basic handler, it takes the FIRST key of the map as the HTTP header name and its value as the header value.","triggerScenarios":"A preheat instance registered with auth_mode \"CUSTOM\" and empty/missing auth_info; auth_info present but not deserializing into map[string]string; any policy execution against that instance fails at the first authorized request.","commonSituations":"Providers secured by a static API-key header (e.g. X-API-Key) where the key was never entered; storing auth_info as \"X-API-Key: abc\" (a string) instead of an object; instance updated later and auth_info wiped.","solutions":["Re-save the instance with auth_info containing the header name as key and the token as value: {\"auth_mode\": \"CUSTOM\", \"auth_info\": {\"X-API-Key\": \"abc123\"}}.","Ensure exactly the intended single entry, since only the first map key is used.","If the provider truly needs no auth, use auth_mode \"NONE\"."],"exampleFix":"# before\ncurl -X POST https://harbor/api/v2.0/p2p/preheat/instances -d '{\"name\": \"kraken\", \"provider\": \"kraken\", \"endpoint\": \"https://kraken.example.com\", \"auth_mode\": \"CUSTOM\", \"auth_info\": {}}'\n# after\ncurl -X POST https://harbor/api/v2.0/p2p/preheat/instances -d '{\"name\": \"kraken\", \"provider\": \"kraken\", \"endpoint\": \"https://kraken.example.com\", \"auth_mode\": \"CUSTOM\", \"auth_info\": {\"X-API-Key\": \"abc123\"}}'","handlingStrategy":"validation","validationCode":"function validateCustomAuthInfo(authInfo) {\n  const entries = Object.entries(authInfo || {});\n  if (entries.length !== 1) throw new Error('CUSTOM auth requires exactly one {headerName: headerValue} entry');\n  const [k, v] = entries[0];\n  if (!/^X-[A-Za-z-]+$|^Authorization$/.test(k) || !v) throw new Error('invalid header key/value');\n}","typeGuard":"function isCustomCredValid(cred) {\n  return cred != null && cred.Data != null && Object.keys(cred.Data).length > 0;\n}","tryCatchPattern":"On 'missing custom token/key data', fix the instance's auth_info to a single {headerName: value} entry via PUT, then trigger a fresh policy execution.","preventionTips":["CUSTOM auth = one map entry: key is the header name (e.g. X-API-Key), value is the header value.","Send auth_info as a JSON object, never a string.","Add schema validation for provider instance payloads in IaC."],"tags":["p2p-preheat","custom-auth","credentials","provider","harbor"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}