{"record":{"id":"4f530db5ad48f974","repo":"netbirdio/netbird","slug":"invalid-duration-4f530d","errorCode":null,"errorMessage":"invalid duration: {}","messagePattern":"invalid duration: (.+?)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"proxy/internal/debug/handler.go","lineNumber":832,"sourceCode":"// handleCapture streams a pcap or text packet capture for the given client.\n//\n// Query params:\n//\n//\tduration: capture duration (0 or absent = max, capped at 30m)\n//\tformat:   \"text\" for human-readable output (default: pcap)\n//\tfilter:   BPF-like filter expression (e.g. \"host 10.0.0.1 and tcp port 443\")\nfunc (h *Handler) handleCapture(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {\n\tclient, ok := h.provider.GetClient(accountID)\n\tif !ok {\n\t\thttp.Error(w, \"client not found\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tduration := maxCaptureDuration\n\tif durationStr := r.URL.Query().Get(\"duration\"); durationStr != \"\" {\n\t\td, err := time.ParseDuration(durationStr)\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"invalid duration: \"+err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif d < 0 {\n\t\t\thttp.Error(w, \"duration must not be negative\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif d > 0 {\n\t\t\tduration = min(d, maxCaptureDuration)\n\t\t}\n\t}\n\n\tfilter := r.URL.Query().Get(\"filter\")\n\twantText := r.URL.Query().Get(\"format\") == \"text\"\n\tverbose := r.URL.Query().Get(\"verbose\") == \"true\"\n\tascii := r.URL.Query().Get(\"ascii\") == \"true\"\n\n\topts := nbembed.CaptureOptions{Filter: filter, Verbose: verbose, ASCII: ascii}\n\tif wantText {","sourceCodeStart":814,"sourceCodeEnd":850,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/proxy/internal/debug/handler.go#L814-L850","documentation":"The capture endpoint parses its duration parameter with time.ParseDuration, so the value must be a Go duration string such as 30s, 5m, or 1h30m. Malformed values like bare numbers, '10min', or '10seconds' return 400 with the ParseDuration error text appended.","triggerScenarios":"GET /debug/clients/<id>/capture?duration=10 (no unit), ?duration=10min, ?duration=10seconds, or any string time.ParseDuration rejects.","commonSituations":"Assuming a bare number means seconds; carrying duration formats from other libraries (moment.js, Java) into the URL; forwarding an unvalidated free-text field from a UI.","solutions":["Use Go duration syntax with s, m, or h suffixes: ?duration=30s or ?duration=5m","Omit duration entirely to capture for the 30-minute default (maxCaptureDuration)","Validate with time.ParseDuration client-side before sending"],"exampleFix":"// before\nGET /debug/clients/<id>/capture?duration=10min\n\n// after: Go duration units\nGET /debug/clients/<id>/capture?duration=10m","handlingStrategy":"validation","validationCode":"if _, err := time.ParseDuration(s); err != nil {\n    return fmt.Errorf(\"duration must use Go units (s, m, h): %w\", err)\n}\n// only then: GET /debug/clients/<id>/capture?duration=<s>","typeGuard":"func isGoDuration(s string) bool {\n    _, err := time.ParseDuration(s)\n    return err == nil\n}","tryCatchPattern":null,"preventionTips":["Always emit durations with time.Duration.String(), which produces valid units","Never send a bare number or formats like 'min'/'seconds' from other ecosystems"],"tags":["http","proxy","debug-ui","packet-capture","duration","validation"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}