AID-H-017.006: Agent Delegation-Graph Bounds & Capability Attenuation

Version 1.20261015 · Actionable sub-technique

Constrain protocol-neutral multi-agent delegation graphs so authority can only attenuate as work is re-delegated. Bind each hop to the root principal, task, parent delegation, target agent, allowed capabilities, expiry, path, and policy-defined maximum depth; reject loops, repeated agents, scope widening, expired links, and unverifiable lineage before dispatch.

Pillars: app, infra

Lifecycle: operation

Scope boundary

Owns protocol-neutral end-to-end delegation lineage, root-principal and task binding, hop and loop bounds, capability attenuation, expiry, and rejection of scope widening before dispatch.

AID-H-004.004: AID-H-017.006 constrains authority across a multi-agent delegation chain; AID-H-004.004 proves the discovered agent endpoint and advertised capability record. A verified route does not authorize delegation through that agent.

AID-H-004.003: AID-H-017.006 validates whole-chain delegation bounds and attenuation; AID-H-004.003 validates each receiver-side message, task transition, replay state, and communication binding. Secure per-hop transport does not prevent scope widening across the end-to-end graph.

Implementation guidance

AID-H-017.006-G001

Verify the complete signed delegation lineage, enforce capability attenuation and expiry at every hop, and reject loops or policy-defined depth violations before dispatch.
Concept

A hop counter supplied by the caller is not a security control. Validate a signed lineage in which every child delegation names its parent, task, root principal, issuer, subject, capability subset, and expiry. The next hop may narrow authority but may never widen it. H-004.003 verifies the authenticated transport and receiver; this control verifies the end-to-end authority graph.

Protocol-neutral lineage validator
# File: delegation/validate_lineage.py
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Callable, FrozenSet


@dataclass(frozen=True)
class DelegationClaims:
    delegation_id: str
    parent_delegation_id: str | None
    root_principal: str
    task_id: str
    issuer_agent: str
    subject_agent: str
    capabilities: FrozenSet[str]
    expires_at: datetime
    policy_version: str


class DelegationDenied(PermissionError):
    pass


def validate_lineage(
    signed_links: list[str],
    *,
    verify_link: Callable[[str], DelegationClaims],
    expected_root_principal: str,
    expected_root_issuer: str,
    expected_task_id: str,
    expected_target_agent: str,
    requested_capability: str,
    maximum_hops: int,
    now: datetime | None = None,
) -> dict:
    if not expected_root_issuer:
        raise DelegationDenied("missing_root_issuer_policy")
    if not signed_links:
        raise DelegationDenied("missing_delegation_lineage")
    if maximum_hops < 1 or len(signed_links) > maximum_hops:
        raise DelegationDenied("delegation_depth_exceeded")

    observed_at = now or datetime.now(timezone.utc)
    links = [verify_link(token) for token in signed_links]
    seen_ids: set[str] = set()
    seen_agents: set[str] = set()

    for index, link in enumerate(links):
        if link.delegation_id in seen_ids:
            raise DelegationDenied("delegation_id_replayed")
        if link.subject_agent in seen_agents:
            raise DelegationDenied("delegation_loop_detected")
        seen_ids.add(link.delegation_id)
        seen_agents.add(link.subject_agent)

        if link.root_principal != expected_root_principal:
            raise DelegationDenied("root_principal_mismatch")
        if link.task_id != expected_task_id:
            raise DelegationDenied("task_binding_mismatch")
        if link.expires_at.tzinfo is None or link.expires_at.astimezone(timezone.utc) <= observed_at:
            raise DelegationDenied("delegation_expired")
        if not link.capabilities:
            raise DelegationDenied("empty_capability_set")

        if index == 0:
            if link.parent_delegation_id is not None:
                raise DelegationDenied("root_link_has_parent")
            if link.issuer_agent != expected_root_issuer:
                raise DelegationDenied("root_issuer_mismatch")
            continue

        parent = links[index - 1]
        if link.parent_delegation_id != parent.delegation_id:
            raise DelegationDenied("parent_link_mismatch")
        if link.issuer_agent != parent.subject_agent:
            raise DelegationDenied("issuer_chain_mismatch")
        if not link.capabilities.issubset(parent.capabilities):
            raise DelegationDenied("capability_scope_widened")

    leaf = links[-1]
    if leaf.subject_agent != expected_target_agent:
        raise DelegationDenied("target_agent_mismatch")
    if requested_capability not in leaf.capabilities:
        raise DelegationDenied("requested_capability_not_delegated")

    return {
        "status": "PASS",
        "root_principal": expected_root_principal,
        "root_issuer": expected_root_issuer,
        "task_id": expected_task_id,
        "target_agent": expected_target_agent,
        "requested_capability": requested_capability,
        "delegation_ids": [link.delegation_id for link in links],
        "policy_versions": sorted({link.policy_version for link in links}),
        "observed_hops": len(links),
        "maximum_hops": maximum_hops,
    }
Verification and evidence

The verify_link callback must perform strict schema, signature, issuer, audience, key-status, and token-time validation against a trusted key registry; it must not accept a caller-supplied signature_verified flag. Replay a valid chain, widened child scope, wrong parent, wrong task, wrong root principal, wrong target, expired link, duplicate delegation ID, repeated agent, missing link, bad signature, unknown key, and over-depth chain. Preserve token digests, verified issuer/key IDs, task and principal bindings, capability sets, policy versions, denial reason, and dispatcher readback without logging bearer tokens.

Action: Load maximum_hops, capability vocabulary, and the expected root issuer from a signed deployment policy (never from the caller), validate the lineage immediately before delegation dispatch, and deny on any missing, unverifiable, expired, replayed, cyclic, or widened link. Use AID-H-018 for the receiving agent's final action authorization.

Open-source tools

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-017.006: Agent Delegation-Graph Bounds & Capability Attenuation | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version