{"record":{"id":"00bdd8e6f5a6adb0","repo":"paperclipai/paperclip","slug":"networkscope-must-be-deny-or-allowlist","errorCode":null,"errorMessage":"networkScope must be \"deny\" or \"allowlist\".","messagePattern":"networkScope must be \"deny\" or \"allowlist\"\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/local-process-sandbox.ts","lineNumber":154,"sourceCode":"  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 {\n  const normalizedHostname = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n  return rules.some((rule) => rule.hostname === normalizedHostname && (rule.port === null || rule.port === port));\n}\n\nfunction assertUnixSocketPathLength(socketPath: string): void {\n  const pathBytes = Buffer.byteLength(socketPath);\n  if (pathBytes > UNIX_SOCKET_PATH_MAX_BYTES) {\n    throw new Error(\n      `Paperclip sandbox proxy socket path is ${pathBytes} bytes, exceeding the Linux limit of ${UNIX_SOCKET_PATH_MAX_BYTES}: ${socketPath}`,","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/local-process-sandbox.ts#L136-L172","documentation":"Thrown by parseLocalProcessNetworkScope when the supplied value is not null, not empty, and not exactly the strings \"deny\" or \"allowlist\". The scope controls whether the sandbox blocks all network egress (\"deny\") or routes traffic through an allowlist proxy (\"allowlist\"). Only two modes are implemented; passing any other literal is treated as programmer error rather than defaulted.","triggerScenarios":"Calling parseLocalProcessNetworkScope with values like \"none\", \"off\", \"disabled\", \"allowed\", \"whitelist\" (legacy synonym), boolean true/false, or the integer 0. Any value that survives the null/empty check but fails the strict equality falls through to the throw at local-process-sandbox.ts:154.","commonSituations":"Migration from an older config vocabulary (\"whitelist\" -> \"allowlist\"), typos in YAML keys, env vars read with the wrong case, or a UI dropdown that submits a label instead of the canonical value. Also hit when callers pass \"allow\" expecting it to mean allowlist.","solutions":["Use exactly one of the two accepted literals: \"deny\" or \"allowlist\".","Map legacy or UI values to the canonical vocabulary before parsing: { whitelist: \"allowlist\", off: \"deny\", on: \"allowlist\" }.","To disable sandbox networking entirely, pass null, undefined, or \"\" — these return null and the scope is treated as unset.","Add a unit test that round-trips every accepted spelling through parseLocalProcessNetworkScope to lock the contract."],"exampleFix":"// before\nconst scope = parseLocalProcessNetworkScope(\"whitelist\");\n\n// after\nconst scope = parseLocalProcessNetworkScope(\"allowlist\");","handlingStrategy":"validation","validationCode":"const NETWORK_SCOPES = new Set([\"deny\", \"allowlist\"]);\nfunction normalizeNetworkScope(value: unknown): \"deny\" | \"allowlist\" | null {\n  if (value == null || value === \"\") return null;\n  const legacy = { whitelist: \"allowlist\", allow: \"allowlist\", off: \"deny\", none: \"deny\" } as Record<string, string>;\n  const mapped = typeof value === \"string\" ? (legacy[value] ?? value) : value;\n  if (typeof mapped !== \"string\" || !NETWORK_SCOPES.has(mapped)) {\n    throw new Error(`Unsupported networkScope: ${String(value)}`);\n  }\n  return mapped as \"deny\" | \"allowlist\";\n}","typeGuard":"function isNetworkScope(value: unknown): value is \"deny\" | \"allowlist\" {\n  return value === \"deny\" || value === \"allowlist\";\n}","tryCatchPattern":"try {\n  const scope = parseLocalProcessNetworkScope(config.networkScope);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('networkScope must be')) {\n    throw new ConfigError(`networkScope must be \\\"deny\\\" or \\\"allowlist\\\" (got: ${String(config.networkScope)})`, { cause: error });\n  }\n  throw error;\n}","preventionTips":["Keep an enum/union type for the scope at the config boundary: type NetworkScope = 'deny' | 'allowlist'.","Translate any legacy vocabulary (whitelist, allow, off) to the canonical literals at the boundary.","Lock the contract with a round-trip unit test of every accepted spelling."],"tags":["network","config-validation","sandbox"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}