{"record":{"id":"4924bfe322afba98","repo":"amir20/dozzle","slug":"no-container-ids-provided","errorCode":null,"errorMessage":"no container ids provided","messagePattern":"no container ids provided","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/web/download.go","lineNumber":24,"sourceCode":"\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/amir20/dozzle/internal/auth\"\n\t\"github.com/amir20/dozzle/internal/container\"\n\tcontainer_support \"github.com/amir20/dozzle/internal/support/container\"\n\tsupport_web \"github.com/amir20/dozzle/internal/support/web\"\n\t\"github.com/go-chi/chi/v5\"\n\t\"github.com/rs/zerolog/log\"\n)\n\nfunc (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {\n\thostIds := strings.Split(chi.URLParam(r, \"hostIds\"), \",\")\n\tif len(hostIds) == 0 {\n\t\tlog.Error().Msg(\"no container ids provided\")\n\t\thttp.Error(w, \"no container ids provided\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tuserLabels := h.config.Labels\n\tpermit := true\n\tif h.config.Authorization.Provider != NONE {\n\t\tuser := auth.UserFromContext(r.Context())\n\t\tif user.ContainerLabels.Exists() {\n\t\t\tuserLabels = user.ContainerLabels\n\t\t}\n\t\tpermit = user.Roles.Has(auth.Download)\n\t}\n\n\tif !permit {\n\t\tlog.Warn().Msg(\"user is not permitted to download logs from container\")\n\t\thttp.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)\n\t\treturn\n\t}","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/web/download.go#L6-L42","documentation":"downloadLogs splits the `hostIds` URL path parameter on commas and errors with 400 if none were provided. Note a practical quirk: strings.Split never returns an empty slice, so this fires when the URL captures an empty hostIds segment, meaning the route was hit with a blank id list.","triggerScenarios":"Requesting the download endpoint with an empty {hostIds} path segment, e.g. GET /api/hosts//logs/download or a hand-crafted/malformed URL where the param resolves to an empty string.","commonSituations":"Frontend bug building the download URL from an empty container/host id; user hits the endpoint manually; URL templating left the placeholder unexpanded.","solutions":["Fix the URL to include at least one host/container id: /api/hosts/{hostId}/logs/download.","Check the frontend code that constructs the download link and ensure the id is non-empty before navigation.","Verify the container or host you are downloading from actually exists and its id is populated."],"exampleFix":"// before\n/api/hosts//logs/download\n// after\n/api/hosts/local/logs/download?stdout=true","handlingStrategy":"validation","validationCode":"const ids = [hostId].filter(Boolean);\nif (ids.length === 0) return; // do not build a download URL without an id\nconst url = `/api/hosts/${ids.join(',')}/logs/download?stdout=true`;","typeGuard":"function canDownload(hostId: string | undefined): hostId is string {\n  return typeof hostId === 'string' && hostId.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Always build download URLs from a verified, non-empty host/container id.","Guard UI actions on missing ids before navigation.","Test the download link after id-related refactors."],"tags":["http-400","url-parameter","download"],"backgroundTag":"missing-required-argument","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}