goharbor/harbor · error

malformed scan report object

Error message

malformed scan report object

What it means

Returned by basicManager.Create when the report is non-nil but one of Digest, RegistrationUUID, or MimeType is empty. These three fields are the minimum identity of a scan report (what artifact, scanned by which scanner, in which format); without them the row cannot be inserted meaningfully.

Source

Thrown at src/pkg/scan/report/manager.go:133

}

// NewManager news basic manager.
func NewManager() Manager {
	return &basicManager{
		dao:     scan.New(),
		vulnDao: scan.NewVulnerabilityRecordDao(),
	}
}

// Create ...
func (bm *basicManager) Create(ctx context.Context, r *scan.Report) (string, error) {
	// Validate report object
	if r == nil {
		return "", errors.New("nil scan report object")
	}

	if len(r.Digest) == 0 || len(r.RegistrationUUID) == 0 || len(r.MimeType) == 0 {
		return "", errors.New("malformed scan report object")
	}

	r.UUID = uuid.New().String()

	// Insert
	if _, err := bm.dao.Create(ctx, r); err != nil {
		return "", err
	}

	return r.UUID, nil
}

func (bm *basicManager) Delete(ctx context.Context, uuid string) error {
	_, err := bm.vulnDao.DeleteForReport(ctx, uuid)
	if err != nil {
		return err
	}
	query := q.Query{Keywords: q.KeyWords{"uuid": uuid}}

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Populate all three required fields before Create: Digest (sha256:...), RegistrationUUID (from the scanner registration), MimeType (e.g. application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0)
  2. Log the offending struct right before Create to see which field is blank
  3. Validate at the API boundary and return BAD_REQUEST with field details

Example fix

// before
r := &scan.Report{Digest: dgst}
uuid, err := bm.Create(ctx, r)

// after
r := &scan.Report{
    Digest:           dgst,
    RegistrationUUID: reg.UUID,
    MimeType:         v1.MimeTypeNativeReport,
}
uuid, err := bm.Create(ctx, r)
Defensive patterns

Strategy: validation

Validate before calling

if r == nil || len(r.Digest) == 0 || len(r.RegistrationUUID) == 0 || len(r.MimeType) == 0 {
    return errors.New("report needs digest, registration UUID and mime type")
}
uuid, err := bm.Create(ctx, r)

Type guard

func isReportComplete(r *scan.Report) bool {
    return r != nil &&
        len(r.Digest) > 0 &&
        len(r.RegistrationUUID) > 0 &&
        len(r.MimeType) > 0
}

Prevention

When it happens

Trigger: Creating a report from a partially populated struct, e.g. digest set but registration UUID missing because the scanner lookup failed silently; requests arriving without mime type after mime negotiation; code that fills fields conditionally.

Common situations: On-demand scan API handlers losing the registration context; adapters that do not report a mime type; refactors that rename fields and leave the old ones empty.

Understand the failure class

Related errors


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