AID-H-004.007: External Event-to-Agent Admission & Authority Binding
This applies when externally controlled events can initiate agent activity; an agent with no such triggers is NOT_APPLICABLE. Shipping event-driven agent platforms and demonstrated probing of agent trigger channels anchor this control. Provider signatures or aligned email domains alone do not authorize use of the agent maker's privileges.
Verify rejected-source, wrong-workspace, unauthorized-actor, replay, and altered-routing events against the actual receiver and worker. PASS requires correct run admission and unchanged authority across the applicable trigger population; an unauthorized run is FAIL, missing provider/actor evidence is INSUFFICIENT_DATA, and verification or policy-service failure is ERROR with admission denied. Applies to tenant-controlled event receivers/workflows, or platform-supported pre-run admission adapters that expose verifiable initiator/resource binding. A provider-native trigger with no such tenant integration point is NOT_APPLICABLE to this receiver control; AID-H-004.006 owns its hosted publication and native-trigger restrictions. If the admission architecture exists but its evidence or required adapter is missing, report INSUFFICIENT_DATA or ERROR rather than NOT_APPLICABLE.
Pillars: app, infra
Lifecycle: building, validation, operation
Scope boundary
Owns the external-event-to-agent-run boundary: authorized initiator, event resource, workflow, execution identity, and bounded task authority before scheduling.
AID-H-004.005: Reuse provider-native integrity, freshness, and durable replay protection; a valid delivery signature is not initiator authorization.
AID-M-009.003: Consume registered workload identities and delegation context; registration does not authorize an external event to start a run.
AID-H-018.004: The admitted task bounds downstream capabilities; runtime scoping can only narrow the admitted authority.
AID-H-018.002: Per-tool/service action authorization remains a later gate and does not substitute for event admission.
AID-H-018.008: Effect idempotency is separate from deduplicating event delivery and run creation.
AID-H-004.006: Hosted publication owns native trigger configuration when the provider exposes no tenant-controlled pre-run admission point. This receiver gate does not independently score the same hosted configuration or treat maker credentials as initiator authority.
Implementation guidance
- AID-H-004.007-G001: Verify native delivery evidence, then authorize the initiator and resource before choosing the agent workflow.
- AID-H-004.007-G002: Bind the admitted event to one durable run and preserve its restricted authority across asynchronous execution.
AID-H-004.007-G001
Step 1: Separate delivery authentication from authority
Register a distinct receiver route and provider credential for each integration. Verify the provider's signature over the raw body before parsing or transforming it, using AID-H-004.005 for freshness and replay handling. Then check the authenticated app, tenant/workspace, event type, actor, and resource against a versioned admission policy. A signed event from an attacker-owned workspace is still unauthorized. A public support inbox can allow unknown customers into a limited triage workflow; it must not grant them internal mailbox search or maker-level actions.
For email, obtain SPF/DKIM/DMARC results from the trusted receiving mail service, not an Authentication-Results header supplied by the sender. DMARC alignment authenticates a domain relationship, not a particular person or business authorization, and forwarding can change authentication results. Apply the supported forwarding/authentication policy and actor/resource rules; do not treat every aligned domain as an allowed sender. Route uncertain mail to inert review or restricted triage without privileged tools.
Step 2: Implement a provider-specific adapter
The following Slack adapter accepts only an approved app-mention workflow. Use the documented Slack signing profile. Load the signing secret from the receiver's secret store and the policy from the release registry; callers cannot provide either. Set request-body, clock-skew, and run-TTL limits in that policy (Slack recommends rejecting requests more than five minutes old). This adapter produces a candidate envelope; durable replay consumption and queue admission in the next step are required before running the agent. Handle subscription verification and non-run events in separate routes without invoking the model.
# event_admission.py -- Python 3.11+, standard library
# policy is loaded from a verified, immutable release artifact by the receiver.
import hashlib, hmac, json, time
class RejectedEvent(ValueError):
pass
def admit_slack(raw: bytes, timestamp: str, signature: str,
signing_secret: bytes, policy: dict, now: int | None = None):
now = int(time.time()) if now is None else now
if len(raw) > policy["max_body_bytes"]:
raise RejectedEvent("body_limit")
try:
issued = int(timestamp)
except (ValueError, TypeError):
raise RejectedEvent("timestamp") from None
if abs(now - issued) > policy["max_clock_skew_seconds"]:
raise RejectedEvent("stale_or_future")
base = b"v0:" + timestamp.encode("ascii") + b":" + raw
expected = "v0=" + hmac.new(signing_secret, base, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
raise RejectedEvent("signature")
body = json.loads(raw) # only after verification of exact raw bytes
if not isinstance(body, dict):
raise RejectedEvent("shape")
if body.get("type") != "event_callback":
raise RejectedEvent("not_a_run_event")
event = body.get("event")
if not isinstance(event, dict) or event.get("type") != "app_mention":
raise RejectedEvent("event_type")
if event.get("bot_id") or event.get("subtype"):
raise RejectedEvent("bot_or_unsupported_subtype")
actor, channel = event.get("user"), event.get("channel")
if body.get("api_app_id") != policy["app_id"] or body.get("team_id") != policy["team_id"]:
raise RejectedEvent("wrong_app_or_workspace")
if actor not in policy["actors"] or channel not in policy["channels"]:
raise RejectedEvent("initiator_not_authorized")
if not isinstance(body.get("event_id"), str) or not body["event_id"]:
raise RejectedEvent("missing_event_id")
if not isinstance(event.get("text"), str):
raise RejectedEvent("missing_text")
# No execution authority is copied from text or caller-supplied fields.
return {
"provider": "slack", "workspace": policy["team_id"],
"event_id": body["event_id"], "actor": actor, "channel": channel,
"raw_sha256": hashlib.sha256(raw).hexdigest(),
"policy_version": policy["version"], "workflow_id": policy["workflow_id"],
"execution_identity": policy["execution_identity"],
"scope_ref": policy["scope_ref"], "expires_at": now + policy["run_ttl_seconds"],
"untrusted_text": event["text"],
}Example policy keys are version, app_id, team_id, actors, channels, workflow_id, execution_identity, scope_ref, max_body_bytes, max_clock_skew_seconds, run_ttl_seconds. Validate their types and nonempty values on receiver startup. Resolve actor IDs and channel IDs administratively; do not authorize on display names or message text. For GitHub, use its own raw-body HMAC and delivery-ID profile instead of pretending it supplies Slack timestamps. Each provider adapter must explicitly handle its retry and key-rotation behavior.
AID-H-004.007-G002
Step 1: Commit admission and scheduling atomically
Use the durable replay store in AID-H-004.005 with a unique key over provider, integration/workspace, and event ID. In one database transaction, store the verified body digest, policy/version, actor/resource, workflow, execution identity, scope reference and expiry, and insert a transactional outbox row for the run. A duplicate with the same digest returns the existing receipt without a second run; a different digest under the same event ID is rejected and investigated. Do not consume an ID and then attempt an unrelated queue publish: a crash between the two would lose a legitimate event. Acknowledge provider retries after durable admission, not after agent completion.
Step 2: Consume the trusted envelope
The outbox worker signs the envelope using AID-H-004.005 or passes only its immutable database reference over an authenticated queue. Queue delivery is at least once: the executor atomically claims or resumes the existing durable run ID and rejects conflicting content, rather than creating a new run on redelivery. Use a durable lease/checkpoint protocol so a crashed worker can resume safely. The executor verifies the reference/signature, audience, expiry, and current revocation state, then resolves the workflow, identity, and scope from the server registry. It must not reload them from event text, URL query fields, model output, or a mutable queue attribute. Authorize sensitive reads and writes through AID-H-018.002 and narrow capabilities through AID-H-018.004. An automatic trigger is authorized only for the predetermined workflow; a model cannot turn an accepted support event into an unrelated finance task.
Step 3: Verify failure paths
Replay a valid delivery concurrently at two receiver replicas; observe exactly one durable run. Try wrong app/workspace, a legitimate but unauthorized actor, edited body bytes, a forged execution identity in the payload, a modified queued envelope, and a revoked workflow. Confirm no protected run reaches the executor. Simulate a crash after transaction commit and before queue delivery: recovery must deliver the same logical run ID; retrying delivery must not create another logical run. Inject a second crash after publishing but before marking the outbox row delivered to verify this duplicate-delivery path. Side-effect idempotency remains independently owned by AID-H-018.008. Retain admission and worker receipts tied to the same digest. An accepted delivery with no trustworthy actor identity may enter only an explicitly approved unprivileged workflow, never the default privileged workflow.
Open-source tools
- Slack Bolt for Python (HTTP request signature and timestamp verification; initiator authorization and durable replay admission remain deployment-owned)
Threat mappings
MITRE ATLAS
- AML.T0006 Active Scanning (rejects unauthorized trigger probes before agent run creation)
- AML.T0006.003 Active Scanning: Probe AI Agent Trigger Channels
- AML.T0132 Misconfigured or Publicly Exposed AI Services (gates externally reachable agent triggers and binds their execution authority)
MAESTRO
- Agent Identity Attack (L7)
- Integration Risks (L7)
- Privilege Escalation (Cross-Layer) (external events cannot select a more privileged workflow identity)
OWASP LLM Top 10 2026
- LLM03:2026 Excessive Agency (event-initiated runs retain bounded initiating authority instead of inheriting unrestricted maker credentials)
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- ASI03:2026 Identity and Privilege Abuse
NIST Adversarial Machine Learning 2025
- N/A
Cisco Integrated AI Security and Safety Framework
- AITech-2.1 Excessive Agency
- AITech-14.2 Abuse of Delegated Authority
- AISubtech-2.1.2 Capability and Permission Overreach
- AISubtech-14.1.2 Insufficient Access Controls
- AISubtech-14.2.1 Permission Escalation via Delegation
Google Secure AI Framework 2.0 - Risks
- IIC: Insecure Integrated Component
Databricks AI Security Framework 3.0
- Agents - Core 13.3: Privilege Compromise