sipeed/picoclaw · warning
invalid CIDR %q
Error message
invalid CIDR %q
What it means
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.
Source
Thrown at web/backend/api/launcher_config.go:92
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
cfg, err := h.loadLauncherConfig()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load launcher config: %v", err), http.StatusInternalServerError)
return
}
cfg.Port = payload.Port
cfg.Public = payload.Public
cfg.AllowedCIDRs = append([]string(nil), payload.AllowedCIDRs...)
if payload.AllowLocalhostBypass != nil {
cfg.AllowLocalhostBypass = *payload.AllowLocalhostBypass
}
cfg.TrustedProxyCIDRs = append([]string(nil), payload.TrustedProxyCIDRs...)
cfg.LegacyLauncherToken = ""
if err := launcherconfig.Validate(cfg); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := launcherconfig.Save(h.launcherConfigPath(), cfg); err != nil {
http.Error(w, fmt.Sprintf("Failed to save launcher config: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(launcherConfigPayload{
Port: cfg.Port,
Public: cfg.Public,
AllowedCIDRs: append([]string(nil), cfg.AllowedCIDRs...),
AllowLocalhostBypass: cfg.AllowLocalhostBypass,
TrustedProxyCIDRs: append([]string(nil), cfg.TrustedProxyCIDRs...),
})
}
View on GitHub (pinned to 49183d7e8d)
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
Example fix
// before
{"allowed_cidrs": ["192.168.1.5"]}
// after - bare IP expressed as a /32 CIDR
{"allowed_cidrs": ["192.168.1.5/32"]} Defensive patterns
Strategy: validation
Validate before calling
function isValidCIDR(s) {
if (typeof s !== 'string' || s !== s.trim() || s === '') return false; // server validates BEFORE trimming
const m = s.match(/^(\d{1,3}(?:\.\d{1,3}){3}|[0-9a-fA-F:]+)\/(\d{1,3})$/);
if (!m) return false;
const prefix = Number(m[2]);
if (m[1].includes(':')) return prefix <= 128;
return prefix <= 32 && m[1].split('.').every(o => Number(o) <= 255);
}
const bad = payload.allowed_cidrs.filter(c => !isValidCIDR(c));
if (bad.length) throw new Error('invalid CIDR(s): ' + bad.join(', ')); Type guard
const isCidrList = (v: unknown): v is string[] => Array.isArray(v) && v.every(x => typeof x === 'string' && isValidCIDR(x));
Try / catch
if (res.status === 400) {
const text = await res.text();
const m = text.match(/invalid CIDR "(.*)"/);
if (m) highlightField('allowed_cidrs', m[1]); // mark the exact offending entry in the UI
throw new Error(text);
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Users thinking in terms of single allowed IPs; UIs that auto-complete bare addresses; CSV import paths that preserve spaces.
Related errors
- invalid trusted proxy CIDR %q
- Failed to load launcher config: %v
- Invalid JSON: %v
- port %d is out of range (1-65535)
- after %d retries: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/9ded2d76a7555464.
Report an issue: GitHub.