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
| Source | Strong coverage | Missing piece |
|---|---|---|
| OWASP SSRF Prevention in Node.js | Normalization, 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 patterns | Real 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 migration | Cloud 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
| Step | Attacker move | Required defense |
|---|---|---|
| Attacker-controlled URL value | Supply external endpoint or crafted hostname to influence policy source | Load policy only from explicit allowlisted hostnames |
| DNS rebinding | Safe hostname resolves to private IP after initial validation | Resolve host during validation and again before dial |
| Redirect pivot | Allowed URL redirects to internal service or metadata endpoint | Revalidate every redirect target and cap chain length |
| Oversized payload | Large response attempts resource exhaustion | Enforce hard byte limits and reject bodies above limit |
| Unsigned policy injection | Serve reachable but malicious policy file | Require 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
| Boundary | Current behavior | Operational impact |
|---|---|---|
| Scheme handling | Remote 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 block | Private/loopback/link-local hosts are denied unless `SAFETY_POLICY_URL_ALLOW_PRIVATE=true`. | Blocks common SSRF targets such as metadata services by default. |
| Rebinding resistance | DNS resolution runs in URL validation and again in `DialContext`. | Reduces time-of-check vs time-of-use gaps. |
| Operational limits | HTTP 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)
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
# 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
# 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. Pin `SAFETY_POLICY_URL` to a dedicated host and enforce `SAFETY_POLICY_URL_ALLOWLIST`.
- 2. Keep `SAFETY_POLICY_URL_ALLOW_PRIVATE=false` in production unless you have a reviewed exception.
- 3. Set `SAFETY_POLICY_SIGNATURE_REQUIRED=true` and verify reload behavior on bad signatures.
- 4. Execute one SSRF drill with redirect and metadata targets, then capture alert evidence.
Continue with Policy Signature Verification and LLM Safety Kernel.