{"record":{"id":"9ded2d76a7555464","repo":"sipeed/picoclaw","slug":"invalid-cidr-q","errorCode":null,"errorMessage":"invalid CIDR %q","messagePattern":"invalid CIDR %q","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"web/backend/api/launcher_config.go","lineNumber":92,"sourceCode":"\t\thttp.Error(w, fmt.Sprintf(\"Invalid JSON: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tcfg, err := h.loadLauncherConfig()\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to load launcher config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tcfg.Port = payload.Port\n\tcfg.Public = payload.Public\n\tcfg.AllowedCIDRs = append([]string(nil), payload.AllowedCIDRs...)\n\tif payload.AllowLocalhostBypass != nil {\n\t\tcfg.AllowLocalhostBypass = *payload.AllowLocalhostBypass\n\t}\n\tcfg.TrustedProxyCIDRs = append([]string(nil), payload.TrustedProxyCIDRs...)\n\tcfg.LegacyLauncherToken = \"\"\n\tif err := launcherconfig.Validate(cfg); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif err := launcherconfig.Save(h.launcherConfigPath(), cfg); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to save launcher config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(launcherConfigPayload{\n\t\tPort:                 cfg.Port,\n\t\tPublic:               cfg.Public,\n\t\tAllowedCIDRs:         append([]string(nil), cfg.AllowedCIDRs...),\n\t\tAllowLocalhostBypass: cfg.AllowLocalhostBypass,\n\t\tTrustedProxyCIDRs:    append([]string(nil), cfg.TrustedProxyCIDRs...),\n\t})\n}\n","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/launcher_config.go#L74-L110","documentation":"Validation error (400) from PUT /api/system/launcher-config: an allowed_cidrs entry failed net.ParseCIDR (launcherconfig/config.go:55-59). Entries must be complete CIDR blocks with a prefix length - '192.168.1.0/24', '10.0.0.5/32', 'fd00::/8'. Bare IPs without a prefix, subnet-mask notation ('255.255.255.0'), and hostnames all fail. Important ordering detail: the handler validates the raw payload (launcher_config.go:91) BEFORE Save runs NormalizeCIDRs, so entries with leading/trailing whitespace or duplicates fail here even though Save would have cleaned them.","triggerScenarios":"Sending \"192.168.1.5\" instead of \"192.168.1.5/32\"; sending \"localhost\"; whitespace-padded entries like \" 10.0.0.0/8\" copied from a spreadsheet; IPv4 with prefix >32.","commonSituations":"Users thinking in terms of single allowed IPs; UIs that auto-complete bare addresses; CSV import paths that preserve spaces.","solutions":["Write every entry as base/prefix, using /32 for a single IPv4 host and /128 for a single IPv6 host","Trim whitespace and drop empty strings client-side before sending","Check the quoted value in the error message - it names the exact offending entry"],"exampleFix":"// before\n{\"allowed_cidrs\": [\"192.168.1.5\"]}\n\n// after - bare IP expressed as a /32 CIDR\n{\"allowed_cidrs\": [\"192.168.1.5/32\"]}","handlingStrategy":"validation","validationCode":"function isValidCIDR(s) {\n  if (typeof s !== 'string' || s !== s.trim() || s === '') return false;   // server validates BEFORE trimming\n  const m = s.match(/^(\\d{1,3}(?:\\.\\d{1,3}){3}|[0-9a-fA-F:]+)\\/(\\d{1,3})$/);\n  if (!m) return false;\n  const prefix = Number(m[2]);\n  if (m[1].includes(':')) return prefix <= 128;\n  return prefix <= 32 && m[1].split('.').every(o => Number(o) <= 255);\n}\nconst bad = payload.allowed_cidrs.filter(c => !isValidCIDR(c));\nif (bad.length) throw new Error('invalid CIDR(s): ' + bad.join(', '));","typeGuard":"const isCidrList = (v: unknown): v is string[] =>\n  Array.isArray(v) && v.every(x => typeof x === 'string' && isValidCIDR(x));","tryCatchPattern":"if (res.status === 400) {\n  const text = await res.text();\n  const m = text.match(/invalid CIDR \"(.*)\"/);\n  if (m) highlightField('allowed_cidrs', m[1]);   // mark the exact offending entry in the UI\n  throw new Error(text);\n}","preventionTips":["Express single hosts as /32 (IPv4) or /128 (IPv6) - bare IPs are rejected","Trim entries client-side: the handler validates the raw payload before the server normalizes","Offer a CIDR-aware input or an 'append /32' affordance in UIs collecting host addresses"],"tags":["validation","cidr","network","launcher","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}