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

  1. Write every entry as base/prefix, using /32 for a single IPv4 host and /128 for a single IPv6 host
  2. Trim whitespace and drop empty strings client-side before sending
  3. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/9ded2d76a7555464. Report an issue: GitHub.