Skip to content

Network Egress

chant sends no telemetry. Nothing in core or in any lexicon reports usage, versions, errors or identities anywhere, and there is no endpoint to opt out of.

What remains is the network chant needs to do its job, and this page is the whole of it: every module that calls a network primitive, which command reaches it, and where it dials. The tables below are generated from test/egress-catalogue.ts, which the same test that guards the offline phases checks against the tree on every run. A module that starts calling fetch and is not in the catalogue fails CI.

A first-class air-gapped mode does not belong in core.

The network chant needs at apply time is the substrate itself — the AWS, Azure, GCP, Kubernetes or Fly endpoint the project describes. A mode that severed it would sever the product. Every other piece an adopter needs to cut already has a per-surface answer: committed spec snapshots for codegen, the forgejo lexicon’s mirror base for uses: resolution, the build archive’s load-image-on-host backend for registry-free delivery, and the emulators for a whole substrate.

So the story stays an integration pattern, documented here, with the offline half guaranteed by test rather than by claim.

A guarded row is executed by test/no-egress.test.ts over every project in the example corpus, with net.Socket.prototype.connect and globalThis.fetch replaced by throwing stubs. Adding a network call to one of these paths fails that test.

CommandBehaviourGuarded by test
chant buildDiscovery, evaluation and serialization. No network in process. One declaration puts a network-reaching child on this path — HelmRender with a repo, which renders the chart at synthesis time; see the shell-outs below.yes
chant build --foldThe same, with modules reduced to values instead of executed. No network in process.yes
chant build --sandboxProject code is evaluated in a child process instead. Neither process reaches the network.yes
chant lintDeclarative rules and post-synth checks, over the built documents. No network in process.yes
chant scenario checkScenarios are evaluated against a fixture snapshot, which is what replaces the live read. A scenario whose given is snapshot(env) rather than a file first runs git fetch for the lifecycle branch — see the shell-outs below.yes
chant searchAnswers from the declared graph. --live and --at are the flags that read an estate, and they land in the apply phase above.yes
chant graph, describe, listProjections of the same built graph. --live again is the opt-in.static scan only

An in-process guard says nothing about a child process, so the offline phases’ shell-outs are enumerated instead. The guard records every spawn and fails on a binary that is not listed here.

Reaches a network is the column to read. Two of these do, and both are conditional — they need a declaration or a flag that a project either has or does not. Nothing else spawned on an offline path leaves the machine.

BinarySpawned byReaches a networkWhat it does
gitchant lintnogit check-ignore --stdin, to drop ignored paths from the file scan. Local repository read; no remote.
child.mjschant build --sandbox, chant lint --sandboxnoThe esbuild-bundled sandbox child, forked under process.execPath with --permission and a closed environment (PATH, plus CHANT_ENV for the config child). Node’s Permission Model has no network flag, so the child is bounded by what the bundle contains rather than by a kernel gate — see Sandboxed Execution.
gitchant scenario checkyes, conditionallygit remote then git fetch <remote> chant/lifecycle, and only when a scenario’s given is snapshot(env) rather than a fixture file. This one does reach the configured git remote — the same remote the repository is already cloned from.
helmchant build, on a project declaring HelmRenderyes, conditionallyhelm version, then helm template <name> <chart> --include-crds [--repo <url> --version <v>]. A HelmRender composite resolves at synthesis time, so a project that declares one puts a chart render on its build path. With repo set, helm fetches the chart from that repository — this is the one build-path shell-out that reaches a network, on first synth only: the rendered manifests are cached under ~/.chant/helm-renders/ and every later build reads the cache. A project declaring no HelmRender never spawns it.

chant lifecycle diff --live, plan, snapshot, apply, converge, chant search --live, chant import --live, chant run and every component verb talk to the substrate they manage. This is the provider API, not a chant service — the same endpoint a console session or an SDK call would use, with the same credentials.

ModulePrimitiveDestinationWhy
packages/core/src/cli/handlers/run.tsnode:netthe configured Temporal server’s gRPC addresschant run --temporal opens a TCP probe against the server address and retries until it answers, so a not-yet-ready cluster reports as a wait rather than as a client error.
packages/core/src/components/verbs/cloud-executor.tsnode:neta declared cluster member’s bolt portThe agnostic wait-cluster-healthy verb probes each member’s port to decide quorum; injectable, so a test never opens a socket.
packages/core/src/components/verbs/wait-verify.tsfetchthe endpoint a component declareswait-endpoint and health-gate poll the deployed thing they were pointed at; the fetcher is injectable for the same reason.
lexicons/aws/src/api/read-client.tsfetchAWS Cloud Control and CloudFormation, or AWS_ENDPOINT_URLThe aws lexicon’s read transport, signed with SigV4 and retargetable at a local emulator; there is no AWS SDK in the tree.
lexicons/aws/src/op/activities/aws-apply.tsfetchAWS Cloud Control, or AWS_ENDPOINT_URLawsApply writes the change set the plan produced, over the same transport the read client uses.
lexicons/aws/src/receipt-store.tsfetchAWS SSM Parameter Store, or AWS_ENDPOINT_URL_SSMEffect receipts are read before an effect step and written after it succeeds; the store is the aws implementation of core’s injectable receipt seam.
lexicons/aws/src/agentcore/trace-fetch.tsfetchBedrock AgentCoreawsAgentCoreFetchTrace pulls a session history and renders it as replay-trace text for a later cedar policy replay.
lexicons/azure/src/api/read-client.tsfetchAzure Resource Manager, or a declared endpoint overrideThe azure lexicon’s read transport for observation and live import.
lexicons/azure/src/op/activities/az-apply.tsfetchAzure Resource Manager, or a declared endpoint overrideazApply performs ARM resource CRUD for the native local applier and for real subscriptions alike.
lexicons/gcp/src/api/read-client.tsfetchGoogle Cloud REST APIs, or a declared endpoint overrideThe gcp lexicon’s read transport, resolving Config Connector kinds to their underlying REST resources.
lexicons/gcp/src/op/activities/gcp-apply.tsfetchGoogle Cloud REST APIs, or a declared endpoint overridegcpApply performs the REST calls a Config Connector manifest implies.
lexicons/cpln/src/api.tsfetchthe Control Plane API, or a declared endpointThe cpln lexicon’s read transport, injectable through CplnHttp so tests need no network.
lexicons/fly/src/op/activities/fly-apply.tsfetchthe Fly Machines API, or FLY_API_HOSTNAME (the mudflaps emulator)flyApply creates, updates and destroys Machines; pointing it at the emulator is how the Fly tutorials run offline.
lexicons/fly/src/op/activities/sprites.tsfetchthe Sprites API, or SPRITES_BASE_URLSprite lifecycle activities — create, exec, destroy — for the Sprites Ops.
lexicons/fly/src/op/activities/sprite-fs.tsfetchthe Sprites filesystem API, or SPRITES_BASE_URLStages an input file into a sprite and reads a result out, without shelling it through spriteExec.
lexicons/fountain/src/op/activities/fountain-apply.tsfetchthe fountain control-plane API, or a declared endpointfountainApply performs the lexicon’s resource CRUD.
lexicons/render/src/op/activities/render-apply.tsfetchthe Render APIrenderApply performs the lexicon’s resource CRUD.
lexicons/k8s/src/op/activities/argo.tsfetchan Argo CD server’s APIThe Argo sync/wait activities drive an installed Argo CD rather than reimplementing it.
lexicons/cedar/src/avp/client.tsfetchAmazon Verified PermissionsThe AVP read transport, hand-rolled over the AWS JSON protocol for the same reason the aws lexicon is: no AWS SDK in a vendor-neutral lexicon’s dependency tree.
lexicons/temporal/src/op/activities/http-check.tsfetchwhatever URL the Op step declareshttpCheck is a generic probe step — the URL is the caller’s, and the activity has no default of its own.
lexicons/temporal/src/op/activities/workflow-audit.tsfetchapi.github.com, or a declared GitHub hostThe workflow-audit activity resolves an action reference to a commit so a pipeline can assert what it actually ran.
lexicons/temporal/src/op/activities/pipeline-audit.tsfetcha GitLab instance’s APIThe pipeline-audit activity reads project and pipeline metadata for the same governance question on GitLab.

chant emulator up pulls a container image and then polls the container it just started on localhost. The poll is loopback traffic; the image pull is the container runtime’s, and is the only egress in the phase.

ModulePrimitiveDestinationWhy
packages/core/src/op/emulator-lifecycle.tsfetchthe just-started emulator container on localhostThe shared boot loop polls the container’s health endpoint until it answers; loopback only, and the image pull it depends on is the container runtime’s.

chant dev generate, chant dev pinned-upgrade and chant vendor fetch upstream schemas. Every lexicon’s spec/fetch.ts runs here and nowhere else: the generated types and the committed spec snapshot are what a build reads, so a machine that never runs codegen never needs the endpoints below. chant dev pinned-upgrade is the one command here that queries api.github.com, and it is a lexicon-maintainer command — it moves a pin in a lexicon’s own source, and nothing on an adopter’s build, lint or apply path calls it.

ModulePrimitiveDestinationWhy
packages/core/src/codegen/fetch.tsfetchthe upstream schema URL a caller passesfetchWithCache/fetchWithRetry — the single transport under every lexicon’s spec/fetch.ts, the k8s CRD loader, chant vendor and chant dev pinned-upgrade. Caches to disk, bounds each attempt, and falls back to the committed snapshot when the endpoint is unreachable.
lexicons/aws/src/spec/fetch.tsfetchthe CloudFormation resource specification and Cloud Control schema endpointsThe aws spec fetch follows redirects itself rather than going through the shared cache helper; run by chant dev generate, never by a build.
lexicons/cpln/src/spec/snapshot-cli.tsfetchthe Control Plane OpenAPI documentjust snapshot refreshes the committed offline spec snapshot; run on a networked machine on purpose, and the snapshot is what everything else reads.
lexicons/azure/scripts/fetch-quickstart-templates.tsfetchthe Azure quickstart-templates repositoryBuilds the ARM fixture corpus the azure serializer is tested against; a maintainer script, not part of the lexicon’s published surface.

chant audit <url> reads someone else’s repository over a git host’s API. Auditing a local path reaches nothing.

ModulePrimitiveDestinationWhy
packages/core/src/audit/fetch.tsfetchan allowlisted git host’s API — GitHub, GitLab or ForgejoThe only audit module that touches the network: chant audit <url> pulls candidate files so the auditor can run on a URL. Hosts are allowlisted, redirects refused, and file count, size, bytes and time all capped.

Freshness checks that run in this repository’s own CI, against api.github.com. Nothing here sits on a path an adopter runs — no CLI command reaches these modules.

ModulePrimitiveDestinationWhy
packages/core/src/op/emulator-freshness.tsfetchapi.github.com release metadataReports how far behind an emulator’s pinned image is. Advisory, never gating, and reached only from scripts/check-emulator-freshness.ts — no CLI command calls it.
scripts/dogwood-freshness.tsfetchapi.github.com commit and tree metadataCompares the committed dogwood pin against upstream for scripts/check-dogwood-freshness.ts. The transport is injectable, which is why the comparison half is unit-tested without a network.

30 modules across packages/, lexicons/, scripts/, ops/ call a network primitive directly. Every other module that reaches the network does so through one of them.

None of the above covers how chant and its outputs arrive on a machine, which is a separate question with separate answers.

npm install reaches a registry, like any Node project. A private mirror or an offline cache serves chant the same way it serves anything else — there is no chant-specific endpoint in the install path.

An emitted pipeline references container images and, on GitHub and Forgejo, uses: action refs. Those are fetched by the CI runner, not by chant, at a time chant is not running. The forgejo lexicon resolves uses: against a configurable mirror base for exactly this reason, so a self-hosted Forgejo with a local action mirror emits refs that resolve inside the perimeter.

Container images that chant itself produces travel in the build archive, and the archive’s load-image-on-host backend delivers them to a host without a registry in between.

SurfaceHow to cut it
CodegenDo not run chant dev generate. The generated types and the committed spec snapshot in each lexicon are what a build reads; a lexicon is fully usable on a machine that never fetches a spec.
Kubernetes CRDsCRD YAML is baked in at generation time, so chant build on a project using third-party CRDs stays offline. See Add a Third-Party CRD.
Helm chart rendersWarm the render cache on a networked machine before going offline — chant build once, and ~/.chant/helm-renders/ carries every later build. A HelmRender pointed at a local chart path instead of a repo needs no fetch at all.
Action referencesPoint the forgejo lexicon at a mirror base, and uses: refs resolve against it instead of a public forge.
Image deliveryUse the build archive’s load-image-on-host publish backend instead of a registry.
A whole substrateRun against an emulator. chant emulator up needs the image once; everything after it is loopback.
Build-ledger referrersNothing to cut. Referrer lookup defaults to noopReferrerLookup, which reaches nothing; the oras-backed lookup is opt-in, because a query that was network-free should not silently start reaching a registry.

test/egress-catalogue.ts holds the catalogue as data. test/no-egress.test.ts checks it from three sides on every run:

  • Closure. A scan of packages/, lexicons/, scripts/ and ops/ finds every module naming a network primitive. It must equal the catalogue exactly — so an added fetch fails, and so does a stale row for a call that no longer exists.
  • Accuracy. Each row declares which primitives it reaches for, and the scan must agree.
  • Publication. The generated block above must match what the catalogue renders, byte for byte. Refresh it with npm run generate:egress-catalogue.

A prose audit answers the question once and is wrong after the next merge. This one is answered by the test suite.

It is not a security boundary against project code you do not trust.

The guard is a regression gate over chant’s own source, run by chant’s own CI, and every mechanism in it is defeatable by code that wants to defeat it — a module can import its own copy of node:http, or shell out. That is the same argument Sandboxed Execution makes for why --sandbox does not ship a bootstrap-time fetch patch and does not claim to block egress: against a hostile file, a userland patch is theatre.

The two are answering different questions. The sandbox asks whether a repository chant does not own can reach out from inside a build; the answer is yes, and the deployment guidance there — a container with no route out, a network namespace, a seatbelt profile — is what closes it. This page asks whether chant itself reaches out on the paths you run every day; the answer is no, and a test fails the day that stops being true.