AID-H-030.001: Skill Metadata & Manifest Honesty Validation

Version 1.20261015 · Actionable sub-technique

Validate the authenticity, consistency, and honesty of skill metadata fields - name, description, author, publisher identity, permissions declaration, risk tier - to detect metadata manipulation targeting human judgment and automated trust decisions.

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:

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.
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

Cross-validate risk_tier declarations against permission manifest scope; flag inconsistencies.
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

Scan metadata text fields for steganographic content (zero-width Unicode, base64, ASCII smuggling).
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

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-030.001: Skill Metadata & Manifest Honesty Validation | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version