{"record":{"id":"8ff8b4ddbf5e5f24","repo":"paperclipai/paperclip","slug":"plugin-kubernetes-egressmode-standard-cannot-enf-8ff8b4","errorCode":null,"errorMessage":"[plugin-kubernetes] egressMode=standard cannot enforce FQDN-based egress rules. The following FQDNs are reachable only via operator-supplied egressAllowCidrs: ${totalFqdnsForWarn.join(\", \")}. Switch egressMode to \"cilium\" for exact FQDN allow-listing.","messagePattern":"\\[plugin-kubernetes\\] egressMode=standard cannot enforce FQDN-based egress rules\\. The following FQDNs are reachable only via operator-supplied egressAllowCidrs: (.+?)\\. Switch egressMode to \"cilium\" for exact FQDN allow-listing\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/plugins/sandbox-providers/kubernetes/src/plugin.ts","lineNumber":323,"sourceCode":"    // The adapter for THIS run is the agent's adapter (params.adapterType) when\n    // supplied, so one environment can serve mixed harnesses; otherwise fall back\n    // to the environment's configured default adapter. getAdapterDefaults validates\n    // it is a registered adapter (throws otherwise), so a curated-out adapter fails\n    // the lease as before.\n    const effectiveAdapterType = resolveRunAdapterType(params.adapterType, config.adapterType);\n\n    // Emit a runtime warning if FQDNs are configured but egressMode=standard\n    // cannot enforce them. Mirrors the validateConfig warning so operators see\n    // it in paperclip-server logs even if they missed the validation step.\n    const adapterDefaultsForWarn = getAdapterDefaults(effectiveAdapterType, config.adapters);\n    const totalFqdnsForWarn = [...adapterDefaultsForWarn.allowFqdns, ...config.egressAllowFqdns];\n    if (config.egressMode === \"standard\" && totalFqdnsForWarn.length > 0) {\n      if (config.egressAllowCidrs.length === 0) {\n        console.warn(\n          `[plugin-kubernetes] egressMode=standard cannot enforce FQDN-based egress rules; falling back to public-IPv4 (TCP 80/443) with private/link-local ranges excluded so the configured FQDNs (${totalFqdnsForWarn.join(\", \")}) remain reachable. Switch egressMode to \"cilium\" for exact FQDN allow-listing.`,\n        );\n      } else {\n        console.warn(\n          `[plugin-kubernetes] egressMode=standard cannot enforce FQDN-based egress rules. The following FQDNs are reachable only via operator-supplied egressAllowCidrs: ${totalFqdnsForWarn.join(\", \")}. Switch egressMode to \"cilium\" for exact FQDN allow-listing.`,\n        );\n      }\n    }\n\n    const kc = createKubeConfig({\n      inCluster: config.inCluster,\n      kubeconfig: config.kubeconfig,\n    });\n    const clients = makeKubeClients(kc);\n\n    // Ensure the tenant namespace and all its RBAC / network policy resources\n    // exist before we try to create the Job.\n    const adapterDefaults = getAdapterDefaults(effectiveAdapterType, config.adapters);\n\n    await ensureTenant(clients, {\n      namespace,\n      companyId: params.companyId,","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/packages/plugins/sandbox-providers/kubernetes/src/plugin.ts#L305-L341","documentation":"Variant of the standard-mode egress warning: FQDN allow-lists are configured AND operator-supplied egressAllowCidrs exist. The FQDNs are reachable only through those CIDRs — standard NetworkPolicy cannot bind rules to hostnames. If a target service's IPs rotate (CDNs, provider endpoints), egress breaks silently because the CIDR list no longer matches.","triggerScenarios":"egressMode === 'standard', non-empty FQDN set, and non-empty egressAllowCidrs — the plugin emits this warn at lease creation to flag that FDN enforcement is indirect via the operator's CIDR list.","commonSituations":"Pinning a provider's API IPs in CIDRs; CDN IP rotation making the allow-list stale; providers publishing changing ranges; operators copying yesterday's DNS answers into config.","solutions":["Prefer egressMode \"cilium\" for hostname-exact enforcement.","If staying on standard, automate refreshing egressAllowCidrs from the FQDNs' current DNS resolution on a schedule.","Widen CIDRs to the provider's officially published IP ranges instead of point-in-time resolutions.","Remove FQDNs that are no longer needed to shrink the drift surface."],"exampleFix":"// before\n{\n  \"egressMode\": \"standard\",\n  \"egressAllowFqdns\": [\"api.anthropic.com\"],\n  \"egressAllowCidrs\": [\"160.79.104.0/23\"]\n}\n\n// after\n{\n  \"egressMode\": \"cilium\",\n  \"egressAllowFqdns\": [\"api.anthropic.com\"]\n}","handlingStrategy":"validation","validationCode":"// Keep CIDRs honest: verify each FQDN still resolves into the allow-list.\nimport dns from 'node:dns/promises';\nimport ip from 'ip6addr';\n\nasync function fqdnsCoveredByCidrs(fqdns: string[], cidrs: string[]): Promise<string[]> {\n  const uncovered: string[] = [];\n  for (const fqdn of fqdns) {\n    const addrs = await dns.resolve4(fqdn).catch(() => [] as string[]);\n    const covered = addrs.some((a) => cidrs.some((c) => ip.parse(c).contains(a)));\n    if (!covered) uncovered.push(fqdn);\n  }\n  return uncovered;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer egressMode=cilium whenever the target uses CDNs or rotating IPs.","If pinned to CIDRs, automate DNS-to-CIDR refresh on a schedule shorter than the provider's rotation period.","Alert when a scheduled fqdnsCoveredByCidrs check returns non-empty.","Prefer providers' published IP ranges over point-in-time DNS answers."],"tags":["kubernetes","egress","network-policy","fqdn","cidr","drift"],"backgroundTag":"network-policy-fqdn-limitation","analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}