AID-H-018.004: Intent-Based Dynamic Capability Scoping
How it works: The grant service intersects role, workload, and request policy, selects the smallest allowed tool set and action budget, and issues a tamper-evident signed scope whose subject, workload, registry ID/version, expiry, and budget the server verifies on every action.
Pillars: app, infra
Lifecycle: building, operation
Scope boundary
Owns per-request or per-session capability minimization by intersecting authenticated subject, workload, intent, role, tool-registry version, expiry, and action-budget policy, then issuing and atomically enforcing a signed scope at the dispatcher. Alternate paths outside that dispatcher are not protected.
AID-H-017.002: AID-H-018.004 chooses the smallest currently allowed subset from the tool registry; AID-H-017.002 designs the registry itself around narrow, single-purpose, least-privilege tools. Dynamic scoping cannot make an overpowered primitive safe.
AID-H-018.007: AID-H-018.004 scopes capabilities for the current request or session; AID-H-018.007 enforces the declared resource permissions of a specific installed skill. A session grant and a skill manifest are intersecting but distinct authorization layers.
AID-M-009.001: AID-H-018.004 issues the concrete runtime tool set and budget; AID-M-009.001 defines the approved degree of autonomy for Observe, Analyze, Decide, Execute, Delegate, and Persist. Autonomy profile informs the grant but does not enumerate the exact current capabilities.
Implementation guidance
- AID-H-018.004-G001: Intersect registry-bound tools, action budgets, and TTL ceilings, then enforce the signed scope with an atomic server-side counter.
- AID-H-018.004-G002: Load grants and registry bindings from a pinned signed snapshot and require a verified external audit receipt before the dispatcher invokes any scoped tool.
AID-H-018.004-G001
# File: capability_scope.py
from __future__ import annotations
import hashlib
import os
import time
import uuid
from dataclasses import dataclass
from pathlib import Path
import jwt
import redis
ISSUER = "aidefend-control-plane"
AUDIENCE = "aidefend-tool-dispatcher"
PRIVATE_KEY = Path("/run/secrets/capability_scope_ed25519.pem").read_text(encoding="utf-8")
PUBLIC_KEY = Path("/etc/aidefend/capability_scope_ed25519.pub").read_text(encoding="utf-8")
KEY_ID = os.environ["CAPABILITY_SCOPE_KEY_ID"]
@dataclass(frozen=True)
class GrantEvidence:
subject_id: str
workload_identity: str
subject_registry_id: str
subject_registry_version: str
role_policy_version: str
workload_grant_version: str
intent_policy_version: str
role_tools: frozenset[str]
workload_tools: frozenset[str]
requested_tools: frozenset[str]
role_max_actions: int
workload_max_actions: int
requested_max_actions: int
role_max_ttl_seconds: int
workload_max_ttl_seconds: int
requested_ttl_seconds: int
@dataclass(frozen=True)
class EffectiveScope:
tools: tuple[str, ...]
max_actions: int
ttl_seconds: int
def normalized_tools(values: frozenset[str]) -> set[str]:
tools = {value.strip() for value in values if isinstance(value, str) and value.strip()}
if not tools or any(" " in value or "/" in value for value in tools):
raise PermissionError("invalid_or_empty_tool_grant")
return tools
def derive_scope(evidence: GrantEvidence) -> EffectiveScope:
if not all([
evidence.subject_id,
evidence.workload_identity,
evidence.subject_registry_id,
evidence.subject_registry_version,
]):
raise PermissionError("subject_or_registry_binding_missing")
role = normalized_tools(evidence.role_tools)
workload = normalized_tools(evidence.workload_tools)
requested = normalized_tools(evidence.requested_tools)
durable_ceiling = role & workload
if requested - durable_ceiling:
raise PermissionError("request_attempted_tool_scope_expansion")
tools = tuple(sorted(durable_ceiling & requested))
actions = min(
evidence.role_max_actions,
evidence.workload_max_actions,
evidence.requested_max_actions,
)
ttl = min(
evidence.role_max_ttl_seconds,
evidence.workload_max_ttl_seconds,
evidence.requested_ttl_seconds,
)
if not tools or actions <= 0 or ttl <= 0:
raise PermissionError("effective_scope_is_empty")
return EffectiveScope(tools=tools, max_actions=actions, ttl_seconds=ttl)
def mint_scope(session_id: str, evidence: GrantEvidence) -> str:
if not session_id:
raise PermissionError("session_id_missing")
effective = derive_scope(evidence)
now = int(time.time())
claims = {
"iss": ISSUER,
"aud": AUDIENCE,
"sub": evidence.subject_id,
"workload_identity": evidence.workload_identity,
"subject_registry_id": evidence.subject_registry_id,
"subject_registry_version": evidence.subject_registry_version,
"jti": str(uuid.uuid4()),
"iat": now,
"nbf": now,
"exp": now + effective.ttl_seconds,
"session_id": session_id,
"allowed_tools": list(effective.tools),
"max_actions": effective.max_actions,
"role_policy_version": evidence.role_policy_version,
"workload_grant_version": evidence.workload_grant_version,
"intent_policy_version": evidence.intent_policy_version,
}
return jwt.encode(
claims,
PRIVATE_KEY,
algorithm="EdDSA",
headers={"kid": KEY_ID, "typ": "JWT"},
)
class RedisAtomicBudget:
SCRIPT = """
local key = KEYS[1]
local expires_at = tonumber(ARGV[1])
local maximum = tonumber(ARGV[2])
local count = redis.call('INCR', key)
if count == 1 then
redis.call('EXPIREAT', key, expires_at)
elseif redis.call('TTL', key) < 0 then
return redis.error_reply('budget key lost expiry')
end
if count > maximum then
return {0, count}
end
return {1, count}
"""
def __init__(self, client: redis.Redis) -> None:
self.client = client
self.script = client.register_script(self.SCRIPT)
def consume(self, scope_id: str, expires_at: int, maximum: int) -> tuple[bool, int]:
try:
allowed, count = self.script(
keys=[f"capability_budget:{scope_id}"],
args=[expires_at, maximum],
)
except redis.RedisError as exc:
raise PermissionError("atomic_budget_store_unavailable") from exc
return bool(allowed), int(count)
def enforce_tool_call(
token: str,
expected_session_id: str,
expected_subject_id: str,
expected_workload_identity: str,
expected_registry_id: str,
expected_registry_version: str,
tool_name: str,
budget: RedisAtomicBudget,
) -> dict:
claims = jwt.decode(
token,
PUBLIC_KEY,
algorithms=["EdDSA"],
issuer=ISSUER,
audience=AUDIENCE,
options={"require": [
"iss", "aud", "sub", "workload_identity", "subject_registry_id",
"subject_registry_version", "jti", "iat", "nbf", "exp", "session_id",
"allowed_tools", "max_actions",
]},
)
expected = {
"sub": expected_subject_id,
"workload_identity": expected_workload_identity,
"subject_registry_id": expected_registry_id,
"subject_registry_version": expected_registry_version,
"session_id": expected_session_id,
}
if any(claims.get(field) != value for field, value in expected.items()):
raise PermissionError("capability_scope_binding_mismatch")
tools = claims["allowed_tools"]
if not isinstance(tools, list) or tools != sorted(set(tools)) or tool_name not in tools:
raise PermissionError("tool_not_in_effective_scope")
maximum = claims["max_actions"]
if isinstance(maximum, bool) or not isinstance(maximum, int) or maximum <= 0:
raise PermissionError("invalid_action_budget")
allowed, count = budget.consume(claims["jti"], int(claims["exp"]), maximum)
if not allowed:
raise PermissionError("scope_action_budget_exceeded")
return {
"schema_version": "aidefend.capability-dispatch-decision.v1",
"decision": "allow",
"scope_id": claims["jti"],
"scope_token_sha256": hashlib.sha256(token.encode()).hexdigest(),
"subject_id": claims["sub"],
"workload_identity": claims["workload_identity"],
"subject_registry_id": claims["subject_registry_id"],
"subject_registry_version": claims["subject_registry_version"],
"session_id": claims["session_id"],
"tool_name": tool_name,
"action_count": count,
"max_actions": maximum,
"role_policy_version": claims["role_policy_version"],
"workload_grant_version": claims["workload_grant_version"],
"intent_policy_version": claims["intent_policy_version"],
}
Keep the action safely bounded
Reconcile the complete population of every requested tool, scope dimension, action budget, and TTL constraint in a grant request; reject expired, revoked, or stale policy and asset bindings under the policy-defined freshness window.
Intersect rather than union subject, workload, registry, intent, and policy bounds; reject empty or widened scopes; mint a signed audience/task/tenant-bound grant with policy-derived TTL/actions and unique ID; initialize an atomic server-side counter.
Verify safely
A separately credentialed verifier independently replays grant derivation, signature/claims, and counter with widening, mixed-tenant, unknown-tool, zero/negative, replay, expiry, and concurrent-use fixtures, reads back effective state from the enforcement system, exercises positive, negative, boundary, stale-evidence, and dependency-failure fixtures, and reconciles every eligible item.
AID-H-018.004-G002
Mandatory production trust boundary
The intersection code above is accepted only behind this wrapper. Authenticated subject and workload identities come from middleware; durable grants come from an exact-schema registry snapshot verified under a key pinned in the issuer and dispatcher images. Request intent may narrow those grants but cannot supply registry versions or ceilings. The dispatcher re-reads the same registry and obtains a separately signed audit receipt before returning an executable allow decision.
# File: trusted_capability_dispatch.py
from __future__ import annotations
import hashlib
import json
import math
import os
import subprocess
import tempfile
from pathlib import Path
from typing import Any
import jwt
import requests
from capability_scope import (
GrantEvidence, RedisAtomicBudget, enforce_tool_call, mint_scope,
)
AUDIT_TIMEOUT_SECONDS = float(os.environ["VERIFIED_H018_CAPABILITY_AUDIT_TIMEOUT_SECONDS"])
REGISTRY_VERIFY_TIMEOUT_SECONDS = float(os.environ["VERIFIED_H018_CAPABILITY_REGISTRY_VERIFY_TIMEOUT_SECONDS"])
AUDIT_RESPONSE_MAX_BYTES = int(os.environ["VERIFIED_H018_CAPABILITY_AUDIT_RESPONSE_MAX_BYTES"])
if (
not math.isfinite(AUDIT_TIMEOUT_SECONDS) or AUDIT_TIMEOUT_SECONDS <= 0
or not math.isfinite(REGISTRY_VERIFY_TIMEOUT_SECONDS)
or REGISTRY_VERIFY_TIMEOUT_SECONDS <= 0
or AUDIT_RESPONSE_MAX_BYTES <= 0
):
raise RuntimeError("verified capability timeouts must be finite and positive")
REGISTRY_PATH = Path("/etc/aidefend/capabilities/registry.json")
REGISTRY_SIGNATURE = Path("/etc/aidefend/capabilities/registry.sig")
REGISTRY_TRUST_KEY = Path("/opt/aidefend/trust/capability-registry-authority.pub")
AUDIT_RECEIPT_KEY = Path("/opt/aidefend/trust/capability-audit-receipt.pub").read_text()
AUDIT_URL = "https://capability-audit.internal.example/v1/decisions"
AUDIT_CA = "/opt/aidefend/trust/capability-audit-ca.pem"
AUDIT_CLIENT_CERT = "/var/run/secrets/capability-audit/client.crt"
AUDIT_CLIENT_KEY = "/var/run/secrets/capability-audit/client.key"
REGISTRY_FIELDS = {
"schema_version", "registry_id", "registry_version",
"role_policy_version", "subjects", "workloads",
}
GRANT_FIELDS = {"tools", "max_actions", "max_ttl_seconds", "grant_version"}
INTENT_FIELDS = {
"intent_policy_version", "requested_tools",
"requested_max_actions", "requested_ttl_seconds",
}
def strict_json_object(raw: bytes) -> dict:
def unique_pairs(pairs):
value = {}
for key, item in pairs:
if key in value:
raise ValueError(f"duplicate signed-JSON key: {key}")
value[key] = item
return value
def reject_constant(value):
raise ValueError(f"non-finite signed-JSON number: {value}")
document = json.loads(
raw.decode("utf-8", errors="strict"),
object_pairs_hook=unique_pairs, parse_constant=reject_constant,
)
if not isinstance(document, dict):
raise ValueError("signed JSON root must be an object")
return document
def load_registry() -> tuple[dict[str, Any], str]:
raw = REGISTRY_PATH.read_bytes()
snapshot: Path | None = None
try:
with tempfile.NamedTemporaryFile(
prefix="h018-registry-", suffix=".json", delete=False
) as handle:
handle.write(raw)
handle.flush()
os.fsync(handle.fileno())
snapshot = Path(handle.name)
subprocess.run([
"cosign", "verify-blob", "--key", str(REGISTRY_TRUST_KEY),
"--bundle", str(REGISTRY_SIGNATURE), str(snapshot),
], check=True, capture_output=True, text=True, timeout=REGISTRY_VERIFY_TIMEOUT_SECONDS)
finally:
if snapshot is not None:
snapshot.unlink(missing_ok=True)
registry = strict_json_object(raw)
if not isinstance(registry, dict) or set(registry) != REGISTRY_FIELDS:
raise PermissionError("capability_registry_schema_mismatch")
if registry["schema_version"] != "aidefend.capability-registry.v1":
raise PermissionError("capability_registry_version_unsupported")
for field in ("registry_id", "registry_version", "role_policy_version"):
if not isinstance(registry[field], str) or not registry[field]:
raise PermissionError("capability_registry_binding_missing")
if not isinstance(registry["subjects"], dict) or not isinstance(registry["workloads"], dict):
raise PermissionError("capability_registry_grants_missing")
return registry, hashlib.sha256(raw).hexdigest()
def validated_grant(grant: object, kind: str) -> dict:
if not isinstance(grant, dict) or set(grant) != GRANT_FIELDS:
raise PermissionError(f"{kind}_grant_schema_mismatch")
tools = grant["tools"]
if (
not isinstance(tools, list) or not tools or tools != sorted(set(tools))
or any(not isinstance(tool, str) or not tool for tool in tools)
):
raise PermissionError(f"{kind}_tool_grant_invalid")
for field in ("max_actions", "max_ttl_seconds"):
value = grant[field]
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
raise PermissionError(f"{kind}_{field}_invalid")
if not isinstance(grant["grant_version"], str) or not grant["grant_version"]:
raise PermissionError(f"{kind}_grant_version_missing")
return grant
def trusted_grant_evidence(
authenticated_subject: str,
authenticated_workload: str,
intent: dict[str, Any],
) -> tuple[GrantEvidence, dict, str]:
if not isinstance(intent, dict) or set(intent) != INTENT_FIELDS:
raise PermissionError("intent_scope_schema_mismatch")
registry, registry_sha256 = load_registry()
try:
subject = validated_grant(
registry["subjects"][authenticated_subject], "subject"
)
workload = validated_grant(
registry["workloads"][authenticated_workload], "workload"
)
except KeyError as exc:
raise PermissionError("authenticated_identity_has_no_registry_grant") from exc
requested_tools = intent["requested_tools"]
if (
not isinstance(requested_tools, list) or not requested_tools
or requested_tools != sorted(set(requested_tools))
):
raise PermissionError("requested_tool_scope_invalid")
for field in ("requested_max_actions", "requested_ttl_seconds"):
value = intent[field]
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
raise PermissionError("requested_ceiling_invalid")
evidence = GrantEvidence(
subject_id=authenticated_subject,
workload_identity=authenticated_workload,
subject_registry_id=registry["registry_id"],
subject_registry_version=registry["registry_version"],
role_policy_version=registry["role_policy_version"],
workload_grant_version=workload["grant_version"],
intent_policy_version=intent["intent_policy_version"],
role_tools=frozenset(subject["tools"]),
workload_tools=frozenset(workload["tools"]),
requested_tools=frozenset(requested_tools),
role_max_actions=subject["max_actions"],
workload_max_actions=workload["max_actions"],
requested_max_actions=intent["requested_max_actions"],
role_max_ttl_seconds=subject["max_ttl_seconds"],
workload_max_ttl_seconds=workload["max_ttl_seconds"],
requested_ttl_seconds=intent["requested_ttl_seconds"],
)
return evidence, registry, registry_sha256
def canonical_digest(value: dict) -> str:
return hashlib.sha256(json.dumps(
value, sort_keys=True, separators=(",", ":"), allow_nan=False
).encode()).hexdigest()
def write_and_verify_audit_receipt(decision: dict) -> tuple[str, str]:
digest = canonical_digest(decision)
with requests.post(
AUDIT_URL,
json={"decision": decision, "decision_sha256": digest},
cert=(AUDIT_CLIENT_CERT, AUDIT_CLIENT_KEY),
verify=AUDIT_CA,
timeout=AUDIT_TIMEOUT_SECONDS,
allow_redirects=False,
stream=True,
) as response:
response.raise_for_status()
raw = bytearray()
for chunk in response.iter_content(chunk_size=min(65536, AUDIT_RESPONSE_MAX_BYTES + 1)):
if chunk:
raw.extend(chunk)
if len(raw) > AUDIT_RESPONSE_MAX_BYTES:
raise PermissionError("audit response exceeds the signed decoded-byte cap")
body = json.loads(bytes(raw))
if not isinstance(body, dict) or set(body) != {"receipt_jws"}:
raise PermissionError("audit receipt response schema differs")
token = body["receipt_jws"]
if not isinstance(token, str) or not token:
raise PermissionError("audit receipt is missing")
claims = jwt.decode(
token,
AUDIT_RECEIPT_KEY,
algorithms=["EdDSA"],
issuer="aidefend-capability-audit",
audience="aidefend-tool-dispatcher",
options={"require": [
"iss", "aud", "iat", "nbf", "exp", "jti",
"decision_sha256", "scope_id", "tool_name", "action_count",
]},
)
expected = {
"decision_sha256": digest,
"scope_id": decision["scope_id"],
"tool_name": decision["tool_name"],
"action_count": decision["action_count"],
}
if any(claims.get(field) != value for field, value in expected.items()):
raise PermissionError("audit_receipt_binding_mismatch")
return claims["jti"], hashlib.sha256(token.encode()).hexdigest()
def mint_from_trusted_registry(
authenticated_subject: str,
authenticated_workload: str,
session_id: str,
intent: dict[str, Any],
) -> str:
evidence, _, _ = trusted_grant_evidence(
authenticated_subject, authenticated_workload, intent
)
return mint_scope(session_id, evidence)
def authorize_before_tool(
*, token: str, authenticated_subject: str, authenticated_workload: str,
session_id: str, tool_name: str, budget: RedisAtomicBudget,
) -> dict:
registry, registry_sha256 = load_registry()
# Re-read and validate both live grants immediately before dispatch.
validated_grant(registry["subjects"].get(authenticated_subject), "subject")
validated_grant(registry["workloads"].get(authenticated_workload), "workload")
decision = enforce_tool_call(
token=token,
expected_session_id=session_id,
expected_subject_id=authenticated_subject,
expected_workload_identity=authenticated_workload,
expected_registry_id=registry["registry_id"],
expected_registry_version=registry["registry_version"],
tool_name=tool_name,
budget=budget,
)
decision["registry_sha256"] = registry_sha256
receipt_id, receipt_sha256 = write_and_verify_audit_receipt(decision)
decision["audit_receipt_id"] = receipt_id
decision["audit_receipt_jws_sha256"] = receipt_sha256
return decision
Fail semantics: registry signature/schema failure, absent live grant, registry drift, invalid scope, budget-store failure, audit-service failure, or receipt mismatch denies execution. The dispatcher invokes the tool only after authorize_before_tool returns a verified receipt-bound decision. Persist the registry/signature, scope digest, budget event, decision, and receipt in the external append-only audit store; independently replay the intersection and receipt verification.
Keep the action safely bounded
Reconcile the complete population of every eligible grant use and all active dispatcher instances; reject expired, revoked, or stale policy and asset bindings under the policy-defined freshness window.
Scope externally initiated tasks without laundering their origin
When the initiating context includes a link-prefill or external-event origin, resolve the corresponding ingress/event receipt from the trusted registry. Preserve that ancestry across turns and sub-agents. The receipt may prove submission or event admission; it does not itself authorize private reads, memory writes, exports, or arbitrary tools. Intersect capabilities with a separately authorized task scope and the caller/workflow's current entitlements. Sensitive read tools such as mailbox and internal-document search need the same task binding as writes. A natural-language instruction to “trust this source” cannot widen the capability set. Reconfirmation must bind the specific requested expansion; later turns cannot silently remove the restriction. Test a link-derived private read, a follow-up write, and a sub-agent relay against the same task scope.
Open-source tools
- Open Policy Agent (OPA)
- Cedar
- jwcrypto
- PyJWT
- Authlib (JOSE/JWT handling with explicit algorithm allowlists)
- pyca/cryptography
- Envoy Proxy (custom ext_authz/policy required at agent-tool dispatch)
- LangChain (orchestration only; custom authorization interceptor required)
- LangGraph (orchestration only; custom authorization interceptor required)
- Microsoft Semantic Kernel (orchestration only; custom authorization interceptor required)
Commercial / hosted tools
- AWS Key Management Service (AWS KMS)
- Google Cloud Key Management Service
- Azure Key Vault
- Apigee API Management (custom dispatcher policy/extension required)
- Kong Gateway Enterprise (custom dispatcher policy/plugin required)
- Kong Konnect (custom dispatcher policy/plugin required)
- AWS API Gateway (custom dispatcher authorization integration required)
- Azure API Management (custom dispatcher authorization policy required)
- Tetrate Service Bridge (custom dispatcher authorization policy required)
- Solo.io Gloo Mesh Enterprise (custom dispatcher authorization policy required)
Threat mappings
MITRE ATLAS
- AML.T0051 LLM Prompt Injection
- AML.T0051.000 LLM Prompt Injection: Direct
- AML.T0051.001 LLM Prompt Injection: Indirect
- AML.T0053 AI Agent Tool Invocation
- AML.T0054 LLM Jailbreak
- AML.T0098 AI Agent Tool Credential Harvesting (dynamic scoping limits credential exposure per session)
- AML.T0085 Data from AI Services (dynamic scoping restricts tools available for data collection)
- AML.T0085.001 Data from AI Services: AI Agent Tools (per-request scoping limits which agent tools can retrieve data)
- AML.T0108 AI Agent
- AML.T0034.002 Cost Harvesting: Agentic Resource Consumption
- AML.T0117 Autonomous Attack-Path Adaptation (capabilities are bound to the authenticated initial intent, so replanning cannot acquire tools outside the grant)
- AML.T0131 Crafted AI Assistant Links (a prefill submission cannot authorize private reads or expand task capabilities)
MAESTRO
- Agent Tool Misuse (L7)
- Agent Goal Manipulation (L7)
- Privilege Escalation (Cross-Layer)
OWASP LLM Top 10 2026
- LLM01:2026 Prompt Injection (intent-bound least capability limits the effects available to an injected prompt but does not identify malicious instructions)
- LLM03:2026 Excessive Agency
- LLM06:2026 Unbounded Consumption
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- ASI01:2026 Agent Goal Hijack
- ASI02:2026 Tool Misuse and Exploitation
- ASI03:2026 Identity and Privilege Abuse (signed scopes prevent privilege expansion)
- ASI05:2026 Unexpected Code Execution (RCE) (dynamic scoping restricts execution to authorized tools)
- ASI10:2026 Rogue Agents
NIST Adversarial Machine Learning 2025
- NISTAML.015 Indirect Prompt Injection (dynamic scoping prevents injection from expanding privileges)
- NISTAML.018 Prompt Injection
- NISTAML.039 Compromising connected resources
Cisco Integrated AI Security and Safety Framework
- AISubtech-13.1.2 Memory Flooding (bounds excessive tool-call or delegation growth in agent processing pipelines)
- AISubtech-13.1.4 Application Denial of Service
- AISubtech-13.2.1 Service Misuse for Cost Inflation
- AISubtech-14.1.2 Insufficient Access Controls
- AISubtech-18.2.1 Abuse of APIs for Mass Automation (Caps bulk tool API calls per signed grant and denies dispatch after exhaustion; no global flood protection)
- AISubtech-2.1.2 Capability and Permission Overreach
- AISubtech-2.2.1 Unauthorized Scope Expansion (bounds agent-originated capability expansion and authority expiry, without proving semantic goal fidelity)
- AISubtech-2.2.3 Authorization and Constraint Erosion (bounds agent-originated capability expansion and authority expiry, without proving semantic goal fidelity)
- AISubtech-4.3.5 Capability Inflation
- AITech-12.1 Tool Exploitation
- AITech-13.1 Disruption of Availability
- AITech-13.2 Cost Harvesting / Repurposing
- AITech-2.1 Excessive Agency
- AITech-2.2 Goal Drift (bounds agent-originated capability expansion and authority expiry, without proving semantic goal fidelity)
- AITech-4.3 Protocol Manipulation
Google Secure AI Framework 2.0 - Risks
- PIJ: Prompt Injection (dynamic scoping prevents injection from expanding agent privileges)
- RA: Rogue Actions (per-request scoping limits what rogue actions can achieve)
Databricks AI Security Framework 3.0
- Agents - Core 13.2: Tool Misuse (dynamic scoping restricts tool misuse per request)
- Agents - Core 13.3: Privilege Compromise (signed session scopes prevent privilege escalation)
- Agents - Core 13.6: Intent Breaking & Goal Manipulation (intent-based scoping constrains manipulated goals)