AID-H-024.003: Fail-Closed Ambiguous Resolution with Typosquat Detection
Pillars: app
Lifecycle: operation
Scope boundary
Owns detection of name collisions, near misses, typosquats, and multiple eligible resolution candidates, and requires explicit disambiguation. It does not choose or authorize a candidate on similarity alone.
AID-H-024.001: AID-H-024.003 blocks ambiguous or suspicious candidate selection; AID-H-024.001 requires the resulting reference to use a canonical namespace and pinned version. Disambiguation determines the candidate, while full qualification makes that choice stable.
Implementation guidance
- AID-H-024.003-G001: If multiple candidates exist, stop and require explicit selection; block near-miss names based on similarity threshold.
AID-H-024.003-G001
Use this guard at tool discovery, onboarding, registry search, or any legacy path that can return multiple tool candidates. It never chooses a runnable tool from a similarity score. The execution path still requires the exact fully qualified ID and signed allowlist decision from AID-H-024.001. Similarity only explains why a non-exact request is blocked and routed for explicit review.
Evaluate the complete candidate population
# File: tool_resolution/ambiguity_guard.py
from __future__ import annotations
import hashlib
import json
import re
from dataclasses import dataclass
from rapidfuzz import fuzz
FQID = re.compile(
r"^[a-z0-9][a-z0-9._-]{0,62}/[a-z0-9][a-z0-9._-]{0,62}@"
r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)"
r"(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$"
)
SHA256 = re.compile(r"^[0-9a-f]{64}$")
@dataclass(frozen=True)
class VerifiedTyposquatPolicy:
similarity_threshold: int
policy_version: str
policy_sha256: str
signature_sha256: str
def assess_resolution(
requested: str,
candidate_ids: list[str],
policy: VerifiedTyposquatPolicy,
) -> dict:
if not isinstance(requested, str) or not 1 <= len(requested) <= 190:
return {"status": "ERROR", "reason": "invalid_requested_identifier"}
if not isinstance(candidate_ids, list) or not candidate_ids:
return {"status": "INSUFFICIENT_DATA", "reason": "candidate_population_missing"}
if any(not isinstance(item, str) or not FQID.fullmatch(item) for item in candidate_ids):
return {"status": "ERROR", "reason": "invalid_candidate_identifier"}
if len(candidate_ids) != len(set(candidate_ids)):
return {"status": "ERROR", "reason": "duplicate_registry_candidate"}
if not isinstance(policy, VerifiedTyposquatPolicy):
return {"status": "INSUFFICIENT_DATA", "reason": "verified_policy_missing"}
if type(policy.similarity_threshold) is not int or not 1 <= policy.similarity_threshold <= 100:
return {"status": "ERROR", "reason": "invalid_policy_threshold"}
if not SHA256.fullmatch(policy.policy_sha256) or not SHA256.fullmatch(policy.signature_sha256):
return {"status": "ERROR", "reason": "invalid_policy_receipt"}
population = sorted(candidate_ids)
population_sha256 = hashlib.sha256(
json.dumps(population, separators=(",", ":")).encode("utf-8")
).hexdigest()
exact = [item for item in population if item == requested]
if len(exact) == 1:
return {
"schema_version": "aidefend.h024003-resolution.v1",
"status": "PASS",
"requested": requested,
"selected_tool_fqid": exact[0],
"candidate_population_sha256": population_sha256,
"candidate_count": len(population),
"policy_version": policy.policy_version,
"policy_sha256": policy.policy_sha256,
}
requested_key = requested.casefold()
scores = sorted(
(
{"tool_fqid": item, "score": fuzz.ratio(requested_key, item.casefold())}
for item in population
),
key=lambda row: (-row["score"], row["tool_fqid"]),
)
near_matches = [
row for row in scores if row["score"] >= policy.similarity_threshold
]
return {
"schema_version": "aidefend.h024003-resolution.v1",
"status": "FAIL",
"reason": "near_match_requires_explicit_fqid" if near_matches else "exact_match_required",
"requested": requested,
"near_matches": near_matches,
"candidate_population_sha256": population_sha256,
"candidate_count": len(population),
"policy_version": policy.policy_version,
"policy_sha256": policy.policy_sha256,
}
Calibration and enforcement
Derive the threshold from a versioned labeled set containing approved names, common typing errors, homoglyph-normalized variants, affix squats, and benign similarly named tools. Calibration may run in log-only mode in a non-executing test environment; production resolution remains fail-closed throughout. A read-only verifier replays the complete recorded candidate population and policy release, reproduces the result, and confirms that only a single exact PASS FQID was handed to AID-H-024.001.
Action: Delete best-match and first-result selection. For every registry response, retain the complete candidate-set digest and signed threshold receipt; return a runnable ID only for one exact FQID match, and route every ambiguity or near match to explicit operator selection and subsequent allowlist approval.
Open-source tools
- RapidFuzz
- jellyfish (string distance alternatives)
- OpenTelemetry
Commercial / hosted tools
- Splunk
- Datadog
Threat mappings
MITRE ATLAS
- AML.T0010 AI Supply Chain Compromise
- AML.T0011 User Execution
- AML.T0115 Publish Poisoned AI Artifacts
- AML.T0010.005 AI Supply Chain Compromise: AI Agent Tool
- AML.T0074 Masquerading
- AML.T0115.002 Publish Poisoned AI Artifacts: AI Agent Tools
- AML.T0011.002 User Execution: Poisoned AI Agent Tool
- AML.T0060 Publish Hallucinated Entities (typosquat detection catches hallucination-squatted packages)
MAESTRO
- Agent Impersonation (L7)
- Malicious Agent Discovery (L7) (blocks discovery of typosquatted tool registrations)
OWASP LLM Top 10 2026
- LLM01:2026 Prompt Injection
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- ASI04:2026 Agentic Supply Chain Vulnerabilities
NIST Adversarial Machine Learning 2025
- N/A
Cisco Integrated AI Security and Safety Framework
- AISubtech-12.1.4 Tool Shadowing
- AISubtech-4.3.2 Namespace Collision
- AISubtech-9.3.1 Malicious Package / Tool Injection
- AISubtech-9.3.2 Dependency Name Squatting (Tools / Servers)
- AITech-12.1 Tool Exploitation
- AITech-4.3 Protocol Manipulation
- AITech-9.3 Dependency / Plugin Compromise
Google Secure AI Framework 2.0 - Risks
- IIC: Insecure Integrated Component (fail-closed resolution prevents binding to typosquatted or impersonated tools)
- RA: Rogue Actions (ambiguous resolution blocked before triggering unintended tool invocation)
Databricks AI Security Framework 3.0
- Agents - Tools MCP Server 13.18: Tool Poisoning
- Agents - Tools MCP Client 13.26: Malicious Server Connection