Skip to content
Deep Dive

AI Agent Policy URL Security

Remote policy fetch is privileged egress. Treat it like one.

Deep Dive10 min readMar 2026
TL;DR
  • -If policy is loaded from a URL, SSRF becomes a governance integrity risk, not only a web-app bug.
  • -Cordum applies URL host checks before request and again at dial time, which helps against DNS rebinding.
  • -Production fetch defaults include a 10s timeout, redirect cap of 5, and 2,097,152-byte size limit.
  • -URL hardening is not enough by itself; pair it with policy signature verification.
Egress guard

Host allowlist and private-address blocking are enforced before outbound policy fetch.

Redirect control

Each redirect URL is revalidated and the chain is capped at five hops.

Policy integrity

Keep signature verification on, so even a reachable URL cannot inject unsigned policy.

Scope

This guide covers policy URL fetch hardening for the Safety Kernel loader path. It focuses on pre-dispatch governance impact, not generic API proxy SSRF patterns.

The production problem

A remote policy URL can change agent behavior without shipping new code. That is convenient for rollouts and dangerous for the same reason.

If attackers influence that fetch path, they can try redirects, DNS rebinding, metadata endpoint access, or oversized payloads. One weak boundary turns your governance layer into a network client under attacker direction.

The hard part is not listing SSRF payloads. The hard part is building fetch rules that survive real bypass techniques while keeping operations practical.

What top results miss

SourceStrong coverageMissing piece
OWASP SSRF Prevention in Node.jsNormalization, scheme restrictions, DNS/IP checks, and redirect validation patterns.No control-plane guidance for policy reload behavior, rollout safety, or governance semantics.
PortSwigger SSRF labs and filter bypass patternsReal bypass payloads: alternate IP forms, redirect abuse, and whitelist parser confusion.No operator runbook for remote policy artifact loading in autonomous agent systems.
AWS Security Blog: IMDSv2 migrationCloud metadata abuse risk and hardening controls that reduce credential theft impact.No mapping to policy-engine fetch pipelines where policy bytes directly change decision logic.

The gap is control-plane specificity: how these protections map to policy reload, signature requirements, and fail-open/fail-closed governance semantics.

Attack path model

StepAttacker moveRequired defense
Attacker-controlled URL valueSupply external endpoint or crafted hostname to influence policy sourceLoad policy only from explicit allowlisted hostnames
DNS rebindingSafe hostname resolves to private IP after initial validationResolve host during validation and again before dial
Redirect pivotAllowed URL redirects to internal service or metadata endpointRevalidate every redirect target and cap chain length
Oversized payloadLarge response attempts resource exhaustionEnforce hard byte limits and reject bodies above limit
Unsigned policy injectionServe reachable but malicious policy fileRequire signature verification before parse and snapshot update

Most incident writeups stop at metadata theft. For AI control planes, the blast radius can include policy mutation itself. Treat the policy URL as a high-trust artifact path.

Cordum runtime behavior

BoundaryCurrent behaviorOperational impact
Scheme handlingRemote fetch is only entered for `http://` or `https://` policy sources.Non-HTTP schemes do not enter this fetch path.
Production TLS rule`http://` policy URLs are rejected in production.Reduces MITM and downgrade risk on policy transport.
Host policy`SAFETY_POLICY_URL_ALLOWLIST` supports comma-separated host constraints.Limits fetches to approved domains and blocks unknown hosts.
Private network blockPrivate/loopback/link-local hosts are denied unless `SAFETY_POLICY_URL_ALLOW_PRIVATE=true`.Blocks common SSRF targets such as metadata services by default.
Rebinding resistanceDNS resolution runs in URL validation and again in `DialContext`.Reduces time-of-check vs time-of-use gaps.
Operational limitsHTTP client timeout is 10s, redirect limit is 5, size default is 2,097,152 bytes.Caps fetch latency and payload size during policy reloads.

Implementation examples

Fetch guard path (Go)

policy_url_fetch_guard.go
Go
func fetchPolicyURL(raw string) ([]byte, error) {
  parsed, err := url.Parse(raw)
  if err != nil {
    return nil, fmt.Errorf("invalid policy url: %w", err)
  }
  if env.IsProduction() && parsed.Scheme == "http" {
    return nil, fmt.Errorf("HTTPS required for policy URL in production")
  }
  if err := validatePolicyURL(parsed); err != nil {
    return nil, err
  }

  transport := http.DefaultTransport.(*http.Transport).Clone()
  transport.DialContext = policyDialContext

  client := &http.Client{
    Timeout:   10 * time.Second,
    Transport: transport,
    CheckRedirect: func(req *http.Request, via []*http.Request) error {
      if len(via) >= 5 {
        return errors.New("policy fetch redirect limit exceeded")
      }
      return validatePolicyURL(req.URL)
    },
  }

  // Fetch response and enforce policyMaxBytes() before parse.
  return readPolicyBody(resp.Body, policyMaxBytes())
}

Production env baseline

policy_url_hardening.env
Bash
# Remote policy source
export SAFETY_POLICY_URL=https://policy.corp.example/prod/safety.yaml

# SSRF controls
export SAFETY_POLICY_URL_ALLOWLIST=policy.corp.example
export SAFETY_POLICY_URL_ALLOW_PRIVATE=false
export SAFETY_POLICY_MAX_BYTES=2097152

# Integrity control (recommended with remote URLs)
export SAFETY_POLICY_SIGNATURE_REQUIRED=true
export SAFETY_POLICY_PUBLIC_KEY="<base64-or-hex-ed25519-public-key>"
export SAFETY_POLICY_SIGNATURE_PATH=/etc/cordum/policy.sig

Abuse simulation drill

policy_url_ssrf_drill.sh
Bash
# 1) Confirm happy path host passes allowlist
export SAFETY_POLICY_URL=https://policy.corp.example/prod/safety.yaml

# 2) Attempt metadata endpoint (should fail)
export SAFETY_POLICY_URL=http://169.254.169.254/latest/meta-data/
# expect: "policy url host not allowed"

# 3) Attempt non-allowlisted host (should fail)
export SAFETY_POLICY_URL=https://attacker.example/policy.yaml
# expect: "policy url host not allowed"

# 4) Restore valid URL and verify signature check remains enabled
export SAFETY_POLICY_URL=https://policy.corp.example/prod/safety.yaml

Limitations and tradeoffs

  • - Strict allowlists reduce risk but add rollout friction for new policy hosts.
  • - Blocking private hosts by default can conflict with internal artifact storage designs.
  • - DNS checks lower rebinding risk, but resolver trust and DNS cache behavior still matter.
  • - A secure URL path still needs signature enforcement, or reachable tampered policy can pass through.

If you enable `SAFETY_POLICY_URL_ALLOW_PRIVATE=true` in production, document the reason and add explicit compensating controls. Future you will ask why this was allowed.

Next step

Run this hardening checklist this week:

  1. 1. Pin `SAFETY_POLICY_URL` to a dedicated host and enforce `SAFETY_POLICY_URL_ALLOWLIST`.
  2. 2. Keep `SAFETY_POLICY_URL_ALLOW_PRIVATE=false` in production unless you have a reviewed exception.
  3. 3. Set `SAFETY_POLICY_SIGNATURE_REQUIRED=true` and verify reload behavior on bad signatures.
  4. 4. Execute one SSRF drill with redirect and metadata targets, then capture alert evidence.

Continue with Policy Signature Verification and LLM Safety Kernel.

Policy URL fetch is not a helper

It is part of your safety boundary. Give it the same rigor as authn, signing, and deployment controls.