Umbra
← All docs

CIDR allowlist enforced at the agent

Defence in depth above the cloud-side scope check. Heartbeat-pushed CIDR list, locally enforced; refuses out-of-scope tasks without a single socket touched.

Last updated May 27, 2026

What it is

A second wall around the agent’s network scope. The cloud already enforces “submitted IPs belong in the claimed CIDR” on result ingest. With this feature, the agent ALSO refuses to probe anything outside its authorised scope, even if a compromised control plane tells it to.

What it produces

  • Pushed on every 30 s heartbeat: the agent’s authorised CIDR list (every active internal_cidr target bound to this agent)
  • Local in-memory cache on the agent, refreshed each heartbeat
  • Refusal at the dispatcher: an out-of-scope discover_network task errors with refusing task: cidr X is outside agent's authorised scope [...]. No socket opens.

What to use it for

  • Defence-in-depth posture review (single-point-of-trust failures are the most common breach pattern; this PR adds the symmetric wall)
  • Customer security review: hand them this doc + the source references for the local check
  • Quick scope changes: pause a target on the dashboard, the next heartbeat removes that CIDR from the agent’s accepted scope

Why it matters

A SaaS that ships an agent into customer networks has to assume its own control plane will be compromised someday. Without agent-side enforcement, that day means the attacker can task every customer agent to probe anything reachable: 8.8.8.8, AWS-metadata, internal HR systems out of scope. With agent-side enforcement, the attacker can task the agents but the agents refuse.

Why it’s valuable

  • No code path bypasses it. Every task kind (discover, proxy, future kinds) goes through the same agentCheckScope shim.
  • DNS-rebinding mitigated. URL hosts are resolved; every resolved IP must be in scope. A hostname that resolves to one in-scope + one out-of-scope IP gets refused.
  • CIDR subset check, not point check. A control plane that says “scan 10.0.0.0/8” when only “10.0.0.0/24” is enrolled gets refused; we don’t need to enumerate IPs to know it’s outside.

How to use it

Automatic: the agent enforces by default. Bind / unbind targets on the dashboard; the scope updates on the next heartbeat (~30 s).

The agent’s /healthz endpoint shows the cached scope so an operator on the host can verify what their agent currently accepts:

curl http://127.0.0.1:7780/healthz
# {"scope":["10.0.0.0/24","192.168.5.0/24"], "scope_loaded": true, ...}