{"record":{"id":"7d4c08948b576abb","repo":"paperclipai/paperclip","slug":"networkallowlist-index-must-be-a-string","errorCode":null,"errorMessage":"networkAllowlist[${index}] must be a string.","messagePattern":"networkAllowlist\\[(.+?)\\] must be a string\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/local-process-sandbox.ts","lineNumber":145,"sourceCode":"    const parsed = new URL(trimmed.includes(\"://\") ? trimmed : `https://${trimmed}`);\n    if (parsed.username || parsed.password || parsed.pathname !== \"/\" || parsed.search || parsed.hash) {\n      throw new Error(\"path\");\n    }\n    hostname = parsed.hostname.toLowerCase();\n    port = parsed.port || null;\n  } catch {\n    throw new Error(`networkAllowlist[${index}] must be a hostname, hostname:port, or origin URL.`);\n  }\n  if (!hostname || hostname === \"*\" || hostname.startsWith(\"*.\")) {\n    throw new Error(`networkAllowlist[${index}] must use an exact hostname; wildcards are not supported.`);\n  }\n  return { hostname, port };\n}\n\nexport function parseLocalProcessNetworkAllowlist(value: unknown): string[] {\n  if (!Array.isArray(value)) return [];\n  return value.map((entry, index) => {\n    if (typeof entry !== \"string\") throw new Error(`networkAllowlist[${index}] must be a string.`);\n    const rule = parseNetworkAllowlistEntry(entry, index);\n    return rule.port ? `${rule.hostname}:${rule.port}` : rule.hostname;\n  });\n}\n\nexport function parseLocalProcessNetworkScope(value: unknown): LocalProcessNetworkScope | null {\n  if (value == null || value === \"\") return null;\n  if (value === \"deny\" || value === \"allowlist\") return value;\n  throw new Error('networkScope must be \"deny\" or \"allowlist\".');\n}\n\nexport function parseLocalProcessFilesystemScope(value: unknown): \"workspace\" | null {\n  if (value == null || value === \"\") return null;\n  if (value === \"workspace\") return value;\n  throw new Error('filesystemScope must be \"workspace\".');\n}\n\nfunction isNetworkTargetAllowed(hostname: string, port: string, rules: NetworkAllowlistRule[]): boolean {","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/local-process-sandbox.ts#L127-L163","documentation":"Thrown by parseLocalProcessNetworkAllowlist while iterating the networkAllowlist option. Each entry is required to be a string before it is forwarded to parseNetworkAllowlistEntry for hostname/port parsing. The check exists because the array is consumed from untyped config (the value parameter is typed unknown) and silently coercing non-strings would mask genuine misconfiguration.","triggerScenarios":"Calling parseLocalProcessNetworkAllowlist with an array containing a non-string element, e.g. a number (443), boolean, null, nested array, or plain object. The map callback hits the typeof guard at local-process-sandbox.ts:145 and throws before any URL parsing runs.","commonSituations":"YAML/JSON config loaded with numeric ports (networkAllowlist: [443, \"example.com\"]), a copy/paste that drops quotes around a hostname, or a default-export that returns an object literal instead of a string array. Also hit when adapter config is forwarded verbatim from an upstream API payload that types the field as (string | number)[].","solutions":["Inspect the failing index in the message (networkAllowlist[i]) and confirm that element is a string in the source config.","Quote every entry: pass [\"example.com\", \"example.com:443\", \"https://example.com\"] rather than mixed types.","If the array is sourced from user input, coerce or reject before calling parseLocalProcessNetworkAllowlist: entries.filter((e): e is string => typeof e === \"string\").","Validate the array shape with a zod/JSON-schema validator on the config boundary so the failure surfaces at load time, not at sandbox spawn."],"exampleFix":"// before\nconst allowlist = [443, \"api.example.com\"];\nconst parsed = parseLocalProcessNetworkAllowlist(allowlist);\n\n// after\nconst allowlist = [\"example.com:443\", \"api.example.com\"];\nconst parsed = parseLocalProcessNetworkAllowlist(allowlist);","handlingStrategy":"validation","validationCode":"function isValidAllowlist(value: unknown): value is string[] {\n  return Array.isArray(value) && value.every((e) => typeof e === \"string\");\n}\n\nif (!isValidAllowlist(config.networkAllowlist)) {\n  throw new Error(\"networkAllowlist must be an array of strings\");\n}\nconst parsed = parseLocalProcessNetworkAllowlist(config.networkAllowlist);","typeGuard":"function isNetworkAllowlistEntry(value: unknown): value is string {\n  return typeof value === \"string\";\n}\n\nfunction assertNetworkAllowlist(value: unknown): asserts value is string[] {\n  if (!Array.isArray(value) || !value.every(isNetworkAllowlistEntry)) {\n    throw new Error(\"networkAllowlist must be string[]\");\n  }\n}","tryCatchPattern":"try {\n  const parsed = parseLocalProcessNetworkAllowlist(config.networkAllowlist);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith(\"networkAllowlist[\")) {\n    throw new ConfigError(`Invalid network allowlist config: ${error.message}`, { cause: error });\n  }\n  throw error;\n}","preventionTips":["Type the config field as string[] at the boundary, not (string | number)[].","Validate with a schema (zod, JSON-schema) at config load so this surfaces before sandbox spawn.","Add a unit test that round-trips mixed-type arrays through your validator."],"tags":["network","config-validation","sandbox","typescript"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}