goharbor/harbor · error

missing custom token/key data

Error message

missing custom token/key data

What it means

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.

Source

Thrown at src/pkg/p2p/preheat/provider/auth/custom_handler.go:40

// CustomAuthHandler handle the custom auth mode.
type CustomAuthHandler struct {
	*BaseHandler
}

// Mode implements @Handler.Mode
func (c *CustomAuthHandler) Mode() string {
	return AuthModeCustom
}

// Authorize implements @Handler.Authorize
func (c *CustomAuthHandler) Authorize(req *http.Request, cred *Credential) error {
	if err := c.BaseHandler.Authorize(req, cred); err != nil {
		return err
	}

	if len(cred.Data) == 0 {
		return errors.New("missing custom token/key data")
	}

	key := reflect.ValueOf(cred.Data).MapKeys()[0].String()
	req.Header.Set(key, cred.Data[key])

	return nil
}

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. 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"}}.
  2. Ensure exactly the intended single entry, since only the first map key is used.
  3. If the provider truly needs no auth, use auth_mode "NONE".

Example fix

# before
curl -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": {}}'
# after
curl -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"}}'
Defensive patterns

Strategy: validation

Validate before calling

function validateCustomAuthInfo(authInfo) {
  const entries = Object.entries(authInfo || {});
  if (entries.length !== 1) throw new Error('CUSTOM auth requires exactly one {headerName: headerValue} entry');
  const [k, v] = entries[0];
  if (!/^X-[A-Za-z-]+$|^Authorization$/.test(k) || !v) throw new Error('invalid header key/value');
}

Type guard

function isCustomCredValid(cred) {
  return cred != null && cred.Data != null && Object.keys(cred.Data).length > 0;
}

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16). Data as JSON: /api/errors/519c893b1714af38. Report an issue: GitHub.