AID-H-030.001: Skill Metadata & Manifest Honesty Validation
Attack surface: Unlike code-level or behavioral analysis (addressed by sibling sub-techniques), this sub-technique focuses on the metadata layer as an independent attack surface. Attackers exploit it through:
- Brand impersonation and typosquatting
- Permission understating and risk-tier spoofing
- Steganographic content injection (hidden Unicode, base64, ASCII control characters)
Observed attack patterns: The Snyk ToxicSkills research (February 2026) documented skills named "Google Calendar Integration" and "Solana Wallet Tracker" - none affiliated with the named brands. The
toxicskills-goof repository demonstrated skills hiding malicious instructions via zero-width Unicode and base64-encoded strings in SKILL.md files - invisible to human reviewers but parsed by the agent's prompt compiler.Obfuscation detection techniques (Shannon entropy, recursive decoding, Unicode normalization) are borrowed from
AID-D-001.001 and applied to the skill metadata context.Pillars: app
Lifecycle: validation, operation
Scope boundary
Owns deception-oriented validation of skill names, descriptions, publisher claims, permission summaries, risk tiers, and hidden metadata content. It consumes a structurally valid manifest and reports whether its claims are honest.
AID-H-018.007: AID-H-030.001 checks whether skill metadata and permission claims are deceptive or understated; AID-H-018.007 defines the permission schema and enforces the approved manifest during execution. Metadata honesty analysis does not grant or enforce permissions.
AID-H-024.003: AID-H-030.001 checks brand and publisher claims inside a skill artifact; AID-H-024.003 detects ambiguous or typosquatted tool names during consuming-side resolution. Artifact metadata deception and runtime resolution ambiguity occur at different admission points.
Implementation guidance
- AID-H-030.001-G001: Detect brand impersonation and typosquatting in skill name, author, and description fields.
- AID-H-030.001-G002: Cross-validate risk_tier declarations against permission manifest scope; flag inconsistencies.
- AID-H-030.001-G003: Scan metadata text fields for steganographic content (zero-width Unicode, base64, ASCII smuggling).
AID-H-030.001-G001
Concept:
Attackers publish skills with names mimicking trusted brands ("Google Calendar Integration", "AWS Helper") to capture installs from users searching for official tools. Detection should not rely on a single token split. Instead normalize Unicode, tokenize across punctuation and whitespace, compare multiple tokens against a known-brand database, and combine lexical similarity with publisher-domain evidence.
# File: skill_admission/brand_check.py
import re
import unicodedata
from rapidfuzz import fuzz
def normalize_tokens(text: str) -> list[str]:
text = unicodedata.normalize("NFKC", text).lower()
return [t for t in re.split(r"[^a-z0-9]+", text) if t]
def extract_domain_part(email: str) -> str:
return (email.split("@", 1)[1] if "@" in email else email).lower()
def check_brand_impersonation(
skill_name: str, publisher_email: str, *,
known_brands: set[str], similarity_threshold: int,
) -> list[str]:
if (
not known_brands
or any(not isinstance(value, str) or not value for value in known_brands)
or type(similarity_threshold) is not int
or not 0 <= similarity_threshold < 100
):
raise ValueError("verified brand policy is invalid")
flags = []
tokens = normalize_tokens(skill_name)
publisher_domain = extract_domain_part(publisher_email)
for brand in sorted(known_brands):
if brand in tokens and brand not in publisher_domain:
flags.append(f"Brand '{brand}' appears in skill name but not in publisher domain")
for token in tokens:
score = fuzz.ratio(token, brand)
if similarity_threshold <= score < 100:
flags.append(f"Possible typosquat token '{token}' vs '{brand}' ({score}%)")
return sorted(set(flags))
Action: Run this check at publish-time (registry) and install-time (enterprise). Flags surface to reviewers with trust warnings; they do not auto-block because legitimate brand-related skills exist.
Production implementation
At metadata admission, normalize Unicode and publisher identifiers once, compare exact and confusable forms against the signed brand/publisher registry and policy distance rules, quarantine matches for evidence-backed review and deny unverifiable publisher claims.
Independent verification
A separately credentialed verifier independently reads back or replays raw/normalized metadata, registry version and match decision with homoglyph, zero-width, punctuation, transposition, legitimate-similar and registry-outage fixtures, runs positive, negative, boundary, stale-policy, partial-population and dependency-failure fixtures, and reconciles the complete eligible population without trusting a component-supplied verdict.
AID-H-030.001-G002
Concept:
A skill's self-declared risk_tier must be consistent with its permission manifest. Define deterministic rules: risk_tier: L0 (safe) is incompatible with shell: true, protected-resource write requests, or broad network egress. risk_tier: L1 is incompatible with protected-resource writes. Protected identity/state resources should be represented canonically as reserved logical resource IDs, not raw filenames.
# File: skill_admission/risk_tier_validator.py
PROTECTED_LOGICAL_IDS = {
"agent.identity.soul", # commonly backed by SOUL.md
"agent.memory.primary", # commonly backed by MEMORY.md
"agent.identity.agents_config", # commonly backed by AGENTS.md
}
def validate_risk_consistency(manifest: dict) -> list[str]:
flags = []
tier = manifest.get("risk_tier", "L1")
if tier == "L0":
if manifest.get("shell", False):
flags.append("L0 incompatible with shell: true")
if manifest.get("network", {}).get("allow", []):
flags.append("L0 incompatible with external network access")
if tier in ("L0", "L1"):
writes = set(manifest.get("files", {}).get("write", []))
protected_writes = writes.intersection(PROTECTED_LOGICAL_IDS)
if protected_writes:
flags.append(f"{tier} incompatible with protected-resource write: {sorted(protected_writes)}")
return flags
Action: Integrate into the admission pipeline policy engine. Inconsistencies are blocking for L0/L1 tiers; flagging for L2/L3.
Production implementation
At manifest admission, parse the closed permission schema, compute minimum risk tier from the signed permission-to-risk matrix, reject under-declaration, unknown or wildcard capability and bind the effective tier to the exact skill artifact digest.
Independent verification
A separately credentialed verifier independently reads back or replays manifest capabilities, matrix computation and registry result with omitted, nested, wildcard, conflicting, over/under-tier and policy-drift fixtures, runs positive, negative, boundary, stale-policy, partial-population and dependency-failure fixtures, and reconciles the complete eligible population without trusting a component-supplied verdict.
AID-H-030.001-G003
Concept:
Attackers hide malicious instructions in skill metadata using invisible characters, encoded blobs, or control-byte smuggling. This is a deterministic metadata admission check, not a semantic classifier: normalize the text, scan it for obfuscation markers, and route suspicious metadata to review before the skill is published or installed.
Step 1: Scan each metadata field
Reuse the canonicalize() and shannon_entropy() helpers from AID-D-001.001, but run them across every metadata field that can reach the prompt compiler.
# File: skill_admission/metadata_stego_scan.py
from __future__ import annotations
import base64
import math
import re
from typing import Any
ZERO_WIDTH_RE = re.compile(r"[\u200B-\u200D\uFEFF]")
BIDI_CONTROL_RE = re.compile(r"[\u202A-\u202E\u2066-\u2069]")
ASCII_CONTROL_RE = re.compile(r"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]")
BASE64_CANDIDATE_RE = re.compile(r"\b[A-Za-z0-9+/]{24,}={0,2}\b")
def shannon_entropy(text: str) -> float:
if not text:
return 0.0
counts = {char: text.count(char) for char in set(text)}
length = len(text)
return -sum((count / length) * math.log2(count / length) for count in counts.values())
def looks_like_base64(token: str) -> bool:
if len(token) % 4 != 0:
return False
try:
base64.b64decode(token, validate=True)
return True
except Exception:
return False
def scan_metadata_text(field_name: str, text: str) -> list[dict[str, Any]]:
findings = []
if ZERO_WIDTH_RE.search(text):
findings.append({"field": field_name, "type": "zero_width_unicode"})
if BIDI_CONTROL_RE.search(text):
findings.append({"field": field_name, "type": "bidirectional_control"})
if ASCII_CONTROL_RE.search(text):
findings.append({"field": field_name, "type": "ascii_control_characters"})
for token in BASE64_CANDIDATE_RE.findall(text):
if looks_like_base64(token) and shannon_entropy(token) >= 4.5:
findings.append({"field": field_name, "type": "embedded_base64", "sample": token[:24]})
if len(text) >= 20 and shannon_entropy(text) >= 5.0:
findings.append({"field": field_name, "type": "high_entropy_text"})
return findings
Step 2: Bundle results into an admission verdict
Review routing should happen at the bundle level so one suspicious field can hold the whole metadata record for inspection.
# File: skill_admission/scan_metadata_bundle.py
from metadata_stego_scan import scan_metadata_text
TEXT_FIELDS = ("name", "description", "readme", "skill_markdown")
def scan_skill_metadata(metadata: dict) -> dict:
findings = []
for field in TEXT_FIELDS:
value = metadata.get(field)
if isinstance(value, str) and value.strip():
findings.extend(scan_metadata_text(field, value))
return {
"status": "review" if findings else "clean",
"findings": findings,
}
Step 3: Fail closed for hidden-content signals
Do not silently normalize and continue. Preserve the original bytes for reviewer evidence and stop admission until the metadata has been cleared or rewritten.
Action: Apply this scan to skill name, description, README, and all SKILL.md prose sections at admission time. Zero-width, bidi-control, embedded-base64, or control-character findings should route the skill to review rather than being auto-cleaned and accepted.
Production implementation
At the isolated text decoder, enforce one canonical Unicode/encoding profile and policy-derived byte/expansion bounds, surface zero-width/bidi/control characters and strictly decoded Base64 or ASCII-smuggling candidates, retain original and normalized digests and quarantine ambiguous content.
Independent verification
A separately credentialed verifier independently reads back or replays raw bytes, decoded/normalized text and finding offsets with Unicode confusable, zero-width, bidi, nested encoding, invalid padding, expansion-limit and benign multilingual fixtures, runs positive, negative, boundary, stale-policy, partial-population and dependency-failure fixtures, and reconciles the complete eligible population without trusting a component-supplied verdict.
Open-source tools
- RapidFuzz
- python-Levenshtein
- Open Policy Agent (OPA)
- ICU
- confusable-homoglyphs (homoglyph detection for registry names)
- Cisco AI Skill Scanner (Apache-2.0; best-effort scanner, not certification)
- Snyk Agent Scan (Snyk verification API required; sandbox untrusted MCP stdio discovery)
Commercial / hosted tools
- Snyk Agent Scan Skill Inspector (vendor-hosted)
- CertiK Skill Scanner
- Socket for Skills
Threat mappings
MITRE ATLAS
- AML.T0010 AI Supply Chain Compromise
- AML.T0011 User Execution
- AML.T0110 AI Agent Tool Poisoning
- AML.T0115 Publish Poisoned AI Artifacts
- AML.T0110.000 AI Agent Tool Poisoning: Definition and Instructions
- AML.T0010.005 AI Supply Chain Compromise: AI Agent Tool
- AML.T0011.002 User Execution: Poisoned AI Agent Tool
- AML.T0074 Masquerading (metadata validation detects skill-level brand impersonation and typosquatting)
- AML.T0060 Publish Hallucinated Entities (typosquat detection catches hallucination-squatted skill names)
- AML.T0115.002 Publish Poisoned AI Artifacts: AI Agent Tools (metadata honesty checks flag poisoned tools with misleading metadata)
- AML.T0111 AI Supply Chain Reputation Inflation (metadata and trust-signal validation reduce reliance on deceptive reputation cues)
MAESTRO
- Compromised Agent Registry (L7) (metadata validation detects skills with anomalous publisher profiles from compromised registries)
OWASP LLM Top 10 2026
- LLM01:2026 Prompt Injection
OWASP ML Top 10 2023
- ML06:2023 AI Supply Chain Attacks (metadata validation is a supply-chain gate for skill artifacts)
OWASP Top 10 for Agentic Applications 2026
- ASI04:2026 Agentic Supply Chain Vulnerabilities (metadata honesty validation is a supply-chain trust gate for skills)
NIST Adversarial Machine Learning 2025
- N/A (metadata honesty validation is skill-ecosystem-specific; no direct NIST AML mapping)
Cisco Integrated AI Security and Safety Framework
- AISubtech-9.3.1 Malicious Package / Tool Injection
- AISubtech-9.3.2 Dependency Name Squatting (Tools / Servers)
- AITech-1.2 Indirect Prompt Injection
- AITech-9.2 Detection Evasion (screens hidden or misleading skill publication metadata before admission)
- AITech-9.3 Dependency / Plugin Compromise
Google Secure AI Framework 2.0 - Risks
- IIC: Insecure Integrated Component (metadata validation identifies insecure or deceptive skill components before integration)
Databricks AI Security Framework 3.0
- Agents - Tools MCP Server 13.21: Supply Chain Attacks (metadata validation detects supply-chain manipulation in skill metadata)
- Agents - Tools MCP Server 13.18: Tool Poisoning (metadata honesty checks flag tools with deceptive metadata)