{"record":{"id":"6b734d6caee607d3","repo":"goharbor/harbor","slug":"trace-enabled-but-no-trace-exporter-set","errorCode":null,"errorMessage":"Trace enabled but no trace exporter set","messagePattern":"Trace enabled but no trace exporter set","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"make/photon/prepare/models.py","lineNumber":190,"sourceCode":"            raise Exception('Trace endpoint not set')\n        if not self.url_path:\n            raise Exception('Trace url path not set')\n\n\nclass Trace:\n    def __init__(self, config: dict):\n        self.enabled = config.get('enabled') or False\n        self.sample_rate = config.get('sample_rate', 1)\n        self.namespace = config.get('namespace') or ''\n        self.jaeger = JaegerExporter(config.get('jaeger'))\n        self.otel = OtelExporter(config.get('otel'))\n        self.attributes = config.get('attributes') or {}\n\n    def validate(self):\n        if not self.enabled:\n            return\n        if not self.jaeger.enabled and not self.otel.enabled:\n            raise Exception('Trace enabled but no trace exporter set')\n        elif self.jaeger.enabled and self.otel.enabled:\n            raise Exception('Only can have one trace exporter at a time')\n        elif self.jaeger.enabled:\n            self.jaeger.validate()\n        elif self.otel.enabled:\n            self.otel.validate()\n\n\nclass PurgeUpload:\n    def __init__(self, config: dict):\n        if not config:\n            self.enabled = False\n        self.enabled = config.get('enabled')\n        self.age = config.get('age') or '168h'\n        self.interval = config.get('interval') or '24h'\n        self.dryrun = config.get('dryrun') or False\n        return\n","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/make/photon/prepare/models.py#L172-L208","documentation":"ScanRequest.FromJSON unmarshals a JSON string into the request struct. An empty input string is rejected before json.Unmarshal is invoked, because unmarshalling zero bytes would only produce a confusing syntax error. It is the parse-side guard used when a scan request arrives as a JSON body.","triggerScenarios":"req.FromJSON(\"\") — an HTTP handler or queue consumer passed an empty body string, e.g. the request body was empty or the read returned nothing.","commonSituations":"Empty POST bodies reaching the scan API; message-queue payloads with empty text; proxies or middleware stripping the body; curl calls with a mistyped -d flag.","solutions":["Check that the body is non-empty at the transport layer and reject with 400 there","Propagate body-read errors instead of continuing with an empty string","Log the content length of incoming request bodies to catch stripped payloads early"],"exampleFix":"// before\nvar req v1.ScanRequest\nerr := req.FromJSON(body) // body may be \"\"\n\n// after\nif body == \"\" {\n    return errors.New(\"empty scan request body\")\n}\nerr := req.FromJSON(body)","handlingStrategy":"validation","validationCode":"if body == \"\" {\n    return errors.New(\"scan request body is empty\")\n}\nvar req v1.ScanRequest\nreturn req.FromJSON(body)","typeGuard":null,"tryCatchPattern":"var req v1.ScanRequest\nif err := req.FromJSON(body); err != nil {\n    if strings.Contains(err.Error(), \"empty json data to parse\") {\n        // reject at the HTTP layer with 400 rather than parsing\n        return writeBadRequest(w, \"empty request body\")\n    }\n    return err\n}","preventionTips":["Reject empty bodies at the transport layer with 400","Check Content-Length before reading the body","Never swallow body-read errors"],"tags":["go","harbor","json","scan","validation"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}