Agentic Covenants

Protect (PR) · Content integrity

Content integrity at the server side layer

external · Outside the agent entirely

If the agent decides to violate this concern, what stops it at this layer?

What this cell does

Egress NetworkPolicy so exfiltration has nowhere to post, DLP at the boundary, and send-side audit written outside the agent's reach. Deliberately the weakest server-side cell in the framework: it catches the consequence, not the manipulation. The control that actually works is not a content control at all. A scoped credential, deny-by-default RBAC, and default-deny egress make a successful injection worthless, so injection is an authorization and blast-radius problem wearing a content costume.

Artifacts (2)

egress-audit-policy.yamlview on GitHub
# ABOUTME: Kyverno policy requiring agent workloads to carry egress audit annotations.
# ABOUTME: Ensures what left the boundary is recorded outside the agent's reach.
#
# Evidence, not prevention. The EU AI Act Article 73 reporting clocks (2 days
# for serious disruption of critical infrastructure, 15 days generally, in
# force since 2 August 2026) are unmeetable without a record that survives the agent.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: agents-require-egress-audit
  annotations:
    policies.kyverno.io/title: Agent workloads must be egress-audited
    policies.kyverno.io/category: Content integrity
    policies.kyverno.io/severity: medium
    policies.kyverno.io/description: >-
      An agent that can be prompt-injected will eventually be prompt-injected.
      This policy does not prevent that. It requires that whatever the agent
      sent is recorded somewhere the agent cannot edit, so an incident has an
      evidence trail and a reporting clock can be met.
spec:
  validationFailureAction: Enforce
  background: true
  rules:

  - name: require-egress-audit-annotation
    match:
      any:
      - resources:
          kinds: [Pod]
          namespaces:
          - "agent-*"
    validate:
      message: >-
        Agent pods must declare an egress audit sink via the
        covenants.io/egress-audit-sink annotation. Exfiltration you cannot
        evidence is exfiltration you cannot report.
      pattern:
        metadata:
          annotations:
            covenants.io/egress-audit-sink: "?*"

  - name: audit-sink-must-be-external
    match:
      any:
      - resources:
          kinds: [Pod]
          namespaces:
          - "agent-*"
    validate:
      message: >-
        The egress audit sink must not resolve into the agent's own namespace.
        A log the agent can delete is not evidence.
      deny:
        conditions:
          any:
          - key: "{{ request.object.metadata.annotations.\"covenants.io/egress-audit-sink\" }}"
            operator: AnyIn
            value:
            - "*.agent-claude-prod.svc.cluster.local*"
egress-exfiltration-policy.yamlview on GitHub
# ABOUTME: Cilium egress policy bounding where an injected agent can send data.
# ABOUTME: The real anti-exfiltration control: injection succeeds, and has nowhere to post.
#
# This is the server-side answer to prompt injection, and it is not a content
# control. It does not detect the manipulation. It removes the payoff.
#
# Substitute the FQDNs for your own approved set. Every host listed here is a
# host an injected agent may exfiltrate to, so the list is a threat-model
# decision and belongs in the agent's charter, not in a platform default.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: agent-egress-exfiltration-bound
  namespace: agent-claude-prod
spec:
  endpointSelector:
    matchLabels:
      app.kubernetes.io/component: agent

  egress:

  # DNS is required for FQDN policy to function. Restricted to the cluster
  # resolver so the agent cannot use an external resolver as a covert channel.
  - toEndpoints:
    - matchLabels:
        io.kubernetes.pod.namespace: kube-system
        k8s-app: kube-dns
    toPorts:
    - ports:
      - port: "53"
        protocol: UDP
      rules:
        dns:
        - matchPattern: "*"

  # Model API. The one destination the agent genuinely needs.
  - toFQDNs:
    - matchName: "api.anthropic.com"
    toPorts:
    - ports:
      - port: "443"
        protocol: TCP

  # Approved internal services, by FQDN rather than CIDR so the intent is
  # readable and so a re-IP does not silently widen the policy.
  - toFQDNs:
    - matchName: "artifacts.internal.example.com"
    toPorts:
    - ports:
      - port: "443"
        protocol: TCP

  # NOTE ON WHAT THIS DOES NOT STOP.
  #
  # Any allowlisted host is an exfiltration path if it accepts writes. If you
  # allow a code-hosting domain, a Gist is permitted traffic. Narrow by method
  # and path where the proxy supports it, and treat a write-capable allowlisted
  # host as a residual risk you have accepted rather than one you have closed.
  #
  # DNS tunnelling survives this policy. The matchPattern "*" above is required
  # for FQDN enforcement to work at all, and it is a covert channel. Detect it
  # at the resolver rather than pretending the policy closes it. See
  # sentinels/blast-radius/server-side/.

Cell notes

Content integrity: server-side

Deliberately the weakest server-side cell in the framework, and the reason this concern was added.

Every other concern has a server-side answer that ends the argument. RBAC denies the verb. The admission controller rejects the manifest. cosign refuses the unsigned image. The agent's opinion is irrelevant.

There is no equivalent here. Nothing on the server side can tell a prompt injection from the document it arrived in, because it is well-formed text on an authorized channel from an approved source. By the time anything server-side observes the agent, the manipulation has already happened.

So this cell catches the consequence rather than the manipulation, and it does that well enough to matter.

What belongs here

ControlCatchesArtifact
Egress containmentExfiltration having anywhere to go. If the agent can only reach an approved set of hosts, a successful injection cannot post data to the attacker's endpointegress-exfiltration-policy.yaml
Send-side auditWhat actually left, recorded outside the agent's reach, so an incident has an evidence trailegress-audit-policy.yaml
DLP at the boundaryCredentials and regulated data in outbound payloads, independent of whether the client-side scanner ranVendor-specific; see notes below
Volume and shape anomalyThe aggregate case, where every individual request was authorized and only the pattern is a leaksentinels/blast-radius/server-side/

The control that actually works is not in this directory

The strongest server-side answer to prompt injection is not a content control at all. It is making a successful injection worthless:

This is why the framework's answer to "what about prompt injection?" is not "we scan for it." It is that injection is an authorization and blast-radius problem wearing a content costume, and those cells are where the enforcement lives.

The lethal trifecta framing makes this concrete: injection becomes exfiltration only when the agent has private data, untrusted content, and external communication together. Removing any one is a design decision available on day one and costs nothing at runtime.

Honest limitations

  • - Egress allowlists leak through allowed hosts. If the agent may reach GitHub, exfiltration to a Gist is permitted traffic. Allowlisting by host is coarse; allowlisting by host and method and path is better and still incomplete.
  • - DNS and timing channels survive most egress policy.
  • - The audit trail is after the fact. It converts an unknown into a known, which is the Article 73 reporting requirement, and it stops nothing.
  • - DLP inspects payloads it can read. End-to-end encrypted destinations defeat it.

Verification


# Policy must deny egress to a host outside the allowlist.
kubectl run egress-probe --rm -i --restart=Never \
  --image=curlimages/curl:8.11.1 \
  --namespace agent-claude-prod \
  -- curl -s --max-time 5 https://example.invalid && echo "FAIL: egress permitted" || echo "ok: egress denied"

# Allowlisted host must still work, or the policy is an outage.
kubectl run egress-probe --rm -i --restart=Never \
  --image=curlimages/curl:8.11.1 \
  --namespace agent-claude-prod \
  -- curl -s --max-time 5 -o /dev/null -w '%{http_code}' https://api.anthropic.com

Both directions, for the same reason the Kyverno suite tests both: a policy that denies everything is an outage rather than a control.

Primary bypasses

Documented, not hypothetical. A control whose bypass is undocumented is worse than no control, because somebody trusted it.

  • exfiltration through an allowlisted write-capable host
  • DNS and timing channels
  • end-to-end encrypted destinations defeating DLP

Crosswalk

NIST CSF 2 0PR.DS-02, DE.CM-01, PR.IR-01
NIST AI RMFMANAGE 2.2
OWASP LLMLLM02
OWASP AGENTICASI02