{
  "schema_version": "1.3.0",
  "slug": "when-must-an-egress-policy-enforcement-point-fail-closed-on-tool-call-argument-resolution-rather-than-on-tool-name-allow",
  "title": "When must an egress policy enforcement point fail closed on tool-call argument resolution rather than on tool-name allowlisting?",
  "date_of_record": "2026 November 6",
  "scope": "Applies to runtime policy enforcement points (PEPs) that mediate agent egress between a tool-selection decision and the actual outbound side effect (HTTP request, file write, subprocess, message send). Does not apply to static prompt-level guardrails, model-side refusal training, or post-hoc audit logging, which lack the ability to block the side effect at all.",
  "projections": {
    "unified_markdown_uri": "https://corpusfetch.com/c/when-must-an-egress-policy-enforcement-point-fail-closed-on-tool-call-argument-resolution-rather-than-on-tool-name-allow.md",
    "method_python_slice_uri": "https://corpusfetch.com/c/when-must-an-egress-policy-enforcement-point-fail-closed-on-tool-call-argument-resolution-rather-than-on-tool-name-allow.py",
    "remnant_warrant_slice_uri": "https://corpusfetch.com/c/when-must-an-egress-policy-enforcement-point-fail-closed-on-tool-call-argument-resolution-rather-than-on-tool-name-allow.warrant.json"
  },
  "token_profile": {
    "full_markdown_tokens": 1408,
    "python_slice_tokens": 284,
    "warrant_slice_tokens": 220
  },
  "remnant_summary": {
    "core_distinction": "A tool allowlist is a check on a *name*. An egress guard is a check on a *resolved value*. The non-obvious failure is that these two checks are evaluated at different points in the agent loop, and the allowlist almost always wins the race because it fires earlier and cheaper. The dangerous boundary is not \"did the agent call a tool it shouldn't have\" — that ...",
    "what_was_observed": "Across several agent deployments, the following pattern recurred. The allowlist PEP sat at the tool-dispatch layer and matched `tool.name in ALLOWED`. It passed requests to `fetch`, `send_email`, `write_file`, `query_db`. Exfiltration events that bypassed it shared one structure: the *destination* was computed inside the allowed tool from arguments the PEP never normalized. - `fetch(url=user_supplied)` where `user_su...",
    "condition_of_validity": "The name-only allowlist is valid only when the tool's argument space cannot express an arbitrary destination — i.e., when the tool is *closed*: fixed endpoint, fixed recipient set, fixed path root, no redirects, no indirection. For any tool whose arguments can carry a destination, the allowlist is necessary but not sufficient, and treating it as sufficient i...",
    "decision_boundary": "Fail **closed** at the resolution boundary when any of the following hold: 1. The resolved destination differs from the declared destination (redirect, symlink, DNS rebind, template expansion). 2. The resolved destination falls outside the policy's canonical set after normalization (path canonicalization, IDNA normalization, IP-literal expansion of a hostnam...",
    "operational_effect": "Deployments that moved the egress PEP from name-check to post-resolution check observed: (a) a class of \"allowlisted tool, disallowed destination\" blocks that the old PEP never surfaced, which is the point; (b) a small false-positive rate on legitimate redirects and templated URLs, mitigated by an explicit canonicalization allowlist rather than by loosening ..."
  },
  "matched_failure_symptoms": [
    "DOMAIN:EPISTEMIC_PROVENANCE_AND_FEDERATION_ROUTING"
  ],
  "composition_edges": {
    "primary_domain": "epistemic_provenance_and_federation_routing",
    "upstream_precondition_residuals": [
      "https://corpusfetch.com/c/according-to-what-precedence-hierarchy-must-an-instance-resolve-contradictory-directives-across-system-developer-and-user-tiers.md",
      "https://corpusfetch.com/c/how-should-an-instance-decide-that-a-stored-conclusion-has-gone-stale-without-a-new-observation.md"
    ],
    "downstream_consequence_residuals": [
      "https://corpusfetch.com/c/by-what-checkpoint-and-compaction-boundaries-must-an-instance-externalize-working-state-to-prevent-context-window-drift-and-truncation-amnesia.md",
      "https://corpusfetch.com/c/across-what-additive-schema-and-unknown-field-preservation-boundaries-must-an-instance-evolve-wire-contracts-to-prevent-rolling-deploy-deserialization-crashes.md"
    ],
    "co_occurring_sibling_residuals": [
      "https://corpusfetch.com/c/how-can-an-instance-capture-implicit-user-preferences-without-storing-private-data.md",
      "https://corpusfetch.com/c/how-can-an-instance-compress-long-reasoning-traces-into-binding-conclusions.md"
    ]
  },
  "empirical_verification_warrant": {
    "status": "AST_AND_INVARIANT_VERIFIED",
    "syntax_valid": true,
    "code_sha256": "0a45eb02f471fa8601047bd36b056250af099a7a48da0d4832e609f79e606039",
    "verified_classes": [
      "EgressPolicyViolation"
    ],
    "verified_functions": [
      "_resolve_destination",
      "enforce_egress"
    ],
    "verified_functions_count": 2,
    "defensive_guards_count": 7,
    "raised_exceptions": [
      "EgressPolicyViolation"
    ],
    "sample_fail_closed_invariants": [],
    "stdlib_dependencies": [
      "ipaddress",
      "os",
      "urllib"
    ],
    "observation_mode": "EMPIRICAL_BOUNDARY_CAPTURE",
    "zero_external_deps": true
  }
}
