Skip to main content

Network Policy

The network policy category controls outbound HTTP requests made by agents. Use it to enforce that agents only contact approved domains, block exfiltration to untrusted services, restrict connections to HTTPS, and lock agents to internal-only endpoints.

Rules

RuleTypeDefaultDescription
allowed_domainsstring[][]If non-empty, agents may only contact domains in this list. Supports wildcard prefix patterns (*.example.com).
blocked_domainsstring[][]Domains that agents are never allowed to contact. Supports wildcards. Checked before the allowed list.
allowed_protocolsstring[]["https"]Protocols agents may use. Set to ["http", "https"] to allow both; leave as ["https"] for TLS enforcement.
block_externalbooleanfalseWhen true and allowed_domains is non-empty, any domain not in allowed_domains is treated as external and blocked. Enables strict internal-only mode.
log_all_requestsbooleantrueEmit an info-level log line at after_workflow listing the total request count and unique domains.
action_on_violationstring"block""block" raises a PolicyViolationError; "warn" logs the violation and lets the agent continue.

How It Works

The network handler runs at three phases:

before_workflow

Runs before the agent does any work. Checks context.configured_endpoints — if the agent is pre-configured with an endpoint whose domain is in blocked_domains, it is stopped before any LLM call or tool use occurs.

mid_execution

Triggered every time the agent calls ctx.record_network_request(url=...). Evaluation order for each URL:

  1. Protocol check: extract the scheme from the URL. If allowed_protocols is non-empty and the scheme is not in it → violation.
  2. Blocklist check: extract the domain. If it matches any entry in blocked_domains → violation.
  3. External check: if block_external is true AND allowed_domains is non-empty AND the domain is not in allowed_domains → violation.
  4. Allowlist check: if allowed_domains is non-empty AND the domain is not in it → violation (catches cases where block_external is false).

If any check produces a violation, action_on_violation determines whether the agent is blocked or just warned.

after_workflow

Produces a final network audit. If log_all_requests is true, logs the total request count and unique domain set. Emits warnings for any requests that were made to blocked domains (belt-and-suspenders after mid_execution).

Domain Matching

The handler uses wildcard-aware matching:

PatternDomainMatch?Why
api.example.comapi.example.comYesExact match
api.example.comother.example.comNoDifferent subdomain
*.example.comapi.example.comYesWildcard matches subdomain
*.example.comdeep.api.example.comYesWildcard matches any suffix
*.example.comexample.comYesWildcard also matches the root
*.example.comnotexample.comNoDifferent root domain
pastebin.comapi.pastebin.comNoExact match only, no auto-wildcard

To block all subdomains of a domain, use the *. prefix: *.pastebin.com blocks sub.pastebin.com but pastebin.com requires a separate entry (or use *.pastebin.com which also matches the root).

Example Policies

Internal-Only Agent (strict)

Allow only company internal endpoints; block pastebin and competitor domains; require HTTPS:

{
"allowed_domains": ["*.internal.company.com", "api.internal.company.com"],
"blocked_domains": ["pastebin.com", "*.pastebin.com", "*.competitor.com"],
"allowed_protocols": ["https"],
"block_external": true,
"log_all_requests": true,
"action_on_violation": "block"
}

API Integration Agent (controlled external access)

Allow specific external APIs; block data exfiltration targets; no internal-only restriction:

{
"allowed_domains": ["api.openai.com", "api.anthropic.com", "api.stripe.com"],
"blocked_domains": ["pastebin.com", "*.pastebin.com", "webhook.site"],
"allowed_protocols": ["https"],
"block_external": false,
"log_all_requests": true,
"action_on_violation": "block"
}

Development Agent (permissive with logging)

Allow most traffic but log everything and warn on suspicious domains:

{
"allowed_domains": [],
"blocked_domains": ["*.onion", "*.darkweb.com"],
"allowed_protocols": ["http", "https"],
"block_external": false,
"log_all_requests": true,
"action_on_violation": "warn"
}

SDK Integration

Recording Network Requests

Call ctx.record_network_request() for every outbound HTTP request. The handler evaluates the URL immediately at mid_execution:

import waxell_observe as waxell
from waxell_observe.errors import PolicyViolationError

waxell.init()

try:
async with waxell.WaxellContext(
agent_name="network-agent",
enforce_policy=True,
) as ctx:

# Record before (or after) making the actual HTTP call
ctx.record_network_request(url="https://api.internal.company.com/v1/reports")

# Make the actual request (not intercepted automatically)
response = httpx.get("https://api.internal.company.com/v1/reports")

ctx.set_result({"data": response.json()})

except PolicyViolationError as e:
print(f"Network request blocked: {e}")
# e.g. "Request to blocked domain 'pastebin.com'"
# e.g. "External request to 'api.openai.com' blocked (internal-only mode)"
# e.g. "Protocol 'http' not in allowed list for 'http://legacy.internal.com/api'"
# e.g. "Domain 'staging.example.com' is not in allowed list"

Method Signature

ctx.record_network_request(
url: str, # Full URL including scheme — e.g. "https://api.example.com/endpoint"
) -> None

The handler extracts both the domain and protocol from the url string. Always pass the full URL (with https:// or http://) so protocol enforcement works correctly. Passing a bare domain like "api.example.com" skips protocol checking.

Using the Decorator

@waxell.observe(
agent_name="network-agent",
enforce_policy=True,
)
async def fetch_data(ctx, url: str):
ctx.record_network_request(url=url)
response = await httpx.AsyncClient().get(url)
return response.json()

Enforcement Flow

Agent starts (WaxellContext.__aenter__)

└── before_workflow governance
└── Check configured_endpoints vs blocked_domains
└── Pre-configured blocked domain? → BLOCK (always)

Agent calls ctx.record_network_request(url="https://pastebin.com/abc")

└── mid_execution governance (per URL)
├── Extract protocol: "https"
├── allowed_protocols non-empty AND "https" not in it? → action_on_violation
├── Extract domain: "pastebin.com"
├── domain matches blocked_domains? → action_on_violation
├── block_external AND allowed_domains non-empty AND not in allowed? → action_on_violation
└── allowed_domains non-empty AND not in allowed? → action_on_violation

Agent completes

└── after_workflow governance
├── log_all_requests? → info log with request count and domains
└── Any blocked domains accessed? → WARN with domain list

Creating via Dashboard

  1. Navigate to Governance > Policies
  2. Click New Policy
  3. Select category Network
  4. Configure domain lists, protocol restrictions, and block_external
  5. Set scope to target specific agents (e.g., network-agent)
  6. Enable

Creating via API

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://acme.waxell.dev/waxell/v1/policies/ \
-d '{
"name": "Internal Network Policy",
"category": "network",
"rules": {
"allowed_domains": ["*.internal.company.com"],
"blocked_domains": ["pastebin.com", "*.competitor.com"],
"allowed_protocols": ["https"],
"block_external": true,
"log_all_requests": true,
"action_on_violation": "block"
},
"scope": {
"agents": ["network-agent"]
},
"enabled": true
}'

Observability

Governance Tab

Network evaluations appear with:

FieldExample
Policy nameInternal Network Policy
Actionallow, warn, or block
Categorynetwork
Reason"Request to blocked domain 'pastebin.com'"
Metadata{"url": "https://pastebin.com/abc", "blocked_domain": "pastebin.com"}

For protocol violations:

FieldExample
Reason"Protocol 'http' not in allowed list for 'http://legacy.internal.com/api'"
Metadata{"url": "http://...", "protocol": "http", "allowed_protocols": ["https"]}

For external-block violations:

FieldExample
Reason"External request to 'api.openai.com' blocked (internal-only mode)"
Metadata{"url": "https://api.openai.com/...", "domain": "api.openai.com"}

For allow cases:

FieldExample
Reason"Network access within policy (3 request(s))"
Metadata{"domains_accessed": ["api.internal.company.com"], "request_count": 3}

After-Workflow Audit

The after_workflow phase always records an audit result. With log_all_requests: true, the server-side log line looks like:

Network audit: 3 requests to 2 domains

Common Gotchas

  1. allowed_domains is an allowlist when non-empty. An empty list means "no domain restriction." As soon as you add one entry, all domains not in the list are blocked when block_external is true, or warned if action_on_violation is "warn".

  2. blocked_domains does not auto-wildcard. Adding "pastebin.com" blocks only pastebin.com. To also block sub.pastebin.com, add "*.pastebin.com" as a separate entry.

  3. Protocol is checked before the domain. A request to http://api.internal.company.com/ is blocked for the wrong protocol, not for being a blocked domain. The error message will reference the protocol, which can be surprising.

  4. block_external only activates when allowed_domains is non-empty. With an empty allowlist and block_external: true, no external blocking occurs — there is nothing to be "external" to. Always pair block_external: true with at least one allowed_domains entry.

  5. The url parameter is used for both domain and protocol extraction. Pass the full URL including the scheme. A bare domain or path skips protocol checking. Bare domains are matched for domain checks only.

  6. Wildcard *.example.com also matches example.com itself. The handler strips the *. prefix and checks that the domain ends with .example.com OR equals example.com. This is intentional — a wildcard policy should cover the root domain too.

  7. record_network_request does not make the actual HTTP call. It only records the intent for governance evaluation. You are responsible for making the actual request with your HTTP client. This means governance fires even if the request ultimately fails.

  8. Multiple calls per run are each evaluated independently. If the first URL passes and the second URL is blocked, the first request is already recorded in the trace. The block fires at the second call.

Combining with Other Policies

The network policy works well alongside:

  • Data access policy — use both to control what data an agent can read (via database) and where it can send data (via HTTP)
  • Compliance policy — PCI-DSS compliance profiles often list network in required_categories to ensure network restrictions are active
  • Audit policy — combine with log_all_requests: true to create a complete paper trail of every outbound connection
  • Scope policy — limit total side effects while network policy specifically controls which endpoints are reachable

Next Steps