goharbor/harbor · warning · lib/errors.Error
BAD_REQUEST
BAD_REQUEST
Error message
audit log forward endpoint should be configured before enable skip audit log in database
What it means
When updating Harbor configurations, enabling skip_audit_log_database=true is only legal if an audit_log_forward_endpoint is also set (in the same payload or already persisted). The config controller verifies this pair and returns BAD_REQUEST, because skipping the DB audit sink without a forward endpoint would silently drop audit logs.
Source
Thrown at src/controller/config/controller.go:165
}
func verifySkipAuditLogCfg(ctx context.Context, cfgs map[string]any, mgr config.Manager) error {
updated := false
endPoint := mgr.Get(ctx, common.AuditLogForwardEndpoint).GetString()
skipAuditDB := mgr.Get(ctx, common.SkipAuditLogDatabase).GetBool()
if skip, exist := cfgs[common.SkipAuditLogDatabase]; exist {
skipAuditDB = skip.(bool)
updated = true
}
if endpoint, exist := cfgs[common.AuditLogForwardEndpoint]; exist {
endPoint = endpoint.(string)
updated = true
}
if updated {
if skipAuditDB && len(endPoint) == 0 {
return errors.BadRequestError(errors.New("audit log forward endpoint should be configured before enable skip audit log in database"))
}
}
return nil
}
// verifyValueLengthCfg verifies the cfgs which need to check the value max length to align with frontend.
func verifyValueLengthCfg(_ context.Context, cfgs map[string]any) error {
maxValue := maxValueLimitedByLength(common.UIMaxLengthLimitedOfNumber)
validateCfgs := []string{
common.TokenExpiration,
common.RobotTokenDuration,
common.SessionTimeout,
}
for _, c := range validateCfgs {
if v, exist := cfgs[c]; exist {
// the cfgs is unmarshal from json string, the number type will be float64
if vf, ok := v.(float64); ok {View on GitHub (pinned to 7b2fd08cc5)
Solutions
- Set audit_log_forward_endpoint to a reachable listener address in the same or an earlier update, then set skip_audit_log_database=true.
- Verify current values with GET /api/v2/configs and compare with your PUT payload to ensure the endpoint is not being overwritten to empty.
- Confirm the forward endpoint actually receives logs after the change, so audits are not lost.
Example fix
# before
PUT /api/v2/configs
{"skip_audit_log_database": true}
# after
{"audit_log_forward_endpoint": "127.0.0.1:8514", "skip_audit_log_database": true} Defensive patterns
Strategy: validation
Validate before calling
// client-side invariant before PUT /api/v2/configs:
func validateAuditCfg(current, update map[string]any) error {
skip := update["skip_audit_log_database"]
if skip == nil { skip = current["skip_audit_log_database"] }
ep := update["audit_log_forward_endpoint"]
if ep == nil { ep = current["audit_log_forward_endpoint"] }
if skip == true && (ep == nil || ep == "") {
return fmt.Errorf("set audit_log_forward_endpoint before enabling skip_audit_log_database")
}
return nil
} Try / catch
if err := configCtl.Update(ctx, cfgs, false); err != nil {
if liberrors.IsErr(err, liberrors.BadRequestCode) &&
strings.Contains(err.Error(), "audit log forward endpoint") {
// fix payload: add endpoint, resend
}
return err
} Prevention
- Always send the endpoint key together with skip_audit_log_database.
- Read current config first so your update does not blank persisted values.
- Codify the pairing in IaC modules with a precondition check.
When it happens
Trigger: PUT /api/v2/configs with {"skip_audit_log_database": true} while audit_log_forward_endpoint is absent or sent as an empty string in the same request.
Common situations: Migrating audit output to a log forwarder (e.g. syslog endpoint) but enabling the skip flag first; a config-management tool (Terraform/Ansible) sends only the changed key; upgrading to a Harbor version that introduced the flag.
Related errors
AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16).
Data as JSON: /api/errors/7fd52a66332bcb1e.
Report an issue: GitHub.