Research

Fingerprinting AI Agent Skills: The Missing Identity Layer for Agent Plugins

Why AI agent skills need their own fingerprint: a content-based ID that stays stable across packaging, platforms and marketplaces
Michal Salát's photo
Michal Salát
Threat Intelligence Director
Published
March 17, 2026
Read time
18 Minutes
Fingerprinting AI Agent Skills: The Missing Identity Layer for Agent Plugins
Written by
Michal Salát
Threat Intelligence Director
Published
March 17, 2026
Read time
18 Minutes
Fingerprinting AI Agent Skills: The Missing Identity Layer for Agent Plugins
    Share this article

    The problem: Same skill, different hash

    As we developed the Agent Trust Hub (ATH)—our unified platform for advancing trustworthy AI—we built the skill analyzer, a risk assessment engine designed to evaluate the safety and integrity of skills contributed by the community. However, throughout this process, we encountered a fundamental challenge: it proved impossible to uniquely identify a skill across sources, and verifying the true identity of a skill’s author was equally unfeasible. This realization led us to propose a new skill ID standard, aiming to provide a reliable, content-based identifier that can solve these identification and trust issues for AI skills.

    To understand why, you need to know what a skill is. AI agents are extended through “skills” — directories containing a SKILL.md prompt file alongside supporting resources like scripts, documentation, and configuration. These skills live in GitHub repositories or skill marketplaces such as skills.sh, Gen ATH and ClawHub, and they work like plugins: install one and your AI agent gains new capabilities.

    my-skill/
      SKILL.md           # The prompt — what the agent does
      lib/
        helper.py        # Supporting script
        config.json      # Configuration
      templates/
        output.md        # Output template
      README.md          # Documentation

    When distributed, the directory gets packaged into a ZIP file. But the ZIP is transport packaging. The meaningful artifact is the directory tree and everything in it. A change to any file, any filename, any directory in the tree produces a different skill. You need to fingerprint the whole thing as a unit.

    Why a simple hash doesn’t work

    The obvious answer is “hash it.” SHA-256 the artifact and use the digest as an identifier. This works well for single files — the security industry has used PE hashes, package checksums and file digests for decades. But a skill is a directory, not a file.

    You could hash the ZIP. But ZIPs are non-deterministic. The same directory tree produces different ZIP files depending on which tool created them, what compression level was used, and when. ZIP metadata includes timestamps — if you ZIP the same files a minute apart, the bytes differ. Different ZIP libraries order entries differently. Some tools store modification timestamps to the second; others truncate to two-second granularity. The ZIP format itself allows multiple valid representations of the same content.

    Then there are cross-platform problems. A skill author on Windows produces paths with backslashes (lib\helper.py); on Unix, the same path uses forward slashes (lib/helper.py). macOS HFS+ stores filenames in NFD Unicode normalization; Linux and Windows typically use NFC. The same accented character — é — might be one codepoint or two depending on the OS that created the ZIP.

    ZIP packaging conventions vary too. Some tools wrap all files in a top-level directory named after the archive (my-skill/SKILL.md); others dump files at the root (SKILL.md). Download the same skill from GitHub’s “Download ZIP” button and from a CI pipeline, and you’ll likely get different wrapper directory names — or no wrapper at all.

    Hashing the ZIP bytes gives you a transport identifier, not a content identifier. Two ZIPs containing byte-for-byte identical files will produce different hashes. The same skill, downloaded from two sources, will look like two different skills.

    What we need is an identifier that represents the logical content of the skill directory — one that changes if and only if the actual files or structure change.

    Single binary hashing vs. skill directory hashing
    Single binary hashing vs. skill directory hashing

    Designing the Skill ID

    The algorithm draws on a well-tested idea: git’s tree hashing. Git solved the “fingerprint a directory tree” problem decades ago by hashing a sorted list of entries where each entry records the type, name and content hash of a file or subdirectory. We adapted this approach for skill packages with normalization steps that neutralize the differences between archive formats, packaging tools and operating systems.

    Here’s the algorithm, step by step.

    1. Extract the file tree from the archive

    Read every entry in the archive — ZIP, tar.gz, tar.bz2, whatever the transport format happens to be. For each entry, record whether it’s a file or a directory and read the file content. Discard all archive metadata: timestamps, permissions, compression settings. Only the file tree survives.

    2. Normalize paths

    Each path goes through four normalizations:

    • Backslash to forward slash — lib\helper.py becomes lib/helper.py
    • Unicode NFC normalization — the combining sequence e + \u0301 (NFD, common on macOS) becomes the precomposed \u00e9 (NFC)
    • Collapse redundant separators and dot components — foo//bar/./baz.txt becomes foo/bar/baz.txt
    • Strip leading ./ and / — ./SKILL.md becomes SKILL.md

    After normalization, a file zipped on Windows with backslash paths produces the same normalized path as one zipped on Unix with forward slashes. A filename created on macOS in NFD matches the same filename created on Linux in NFC.

    3. Strip the wrapper directory

    If every entry lives under a single top-level directory (and there are no loose files at the root), strip that prefix. This handles the common case where packaging tools wrap contents in a directory named after the archive.

    A skill packaged as my-skill/SKILL.md produces the same Skill ID as one packaged as SKILL.md directly, or as totally-different-name/SKILL.md. The wrapper is a packaging artifact, not part of the skill’s identity.

    4. Exclude the signature file

    Remove any entry whose path is exactly skill.sig. This file is reserved for the skill signature (more on that later) and must not participate in the Skill ID computation. The signature lives inside the skill directory but is invisible to the identity algorithm — the same principle as Authenticode’s excluded region in a PE binary.

    5. Hash each file

    Compute the SHA-256 digest of each file’s raw content individually. Directory entries get the SHA-256 of empty bytes. These per-file hashes become useful later for caching.

    6. Build sorted tree entries

    Construct one entry per file and directory using null-byte delimiters:

    <type>\0<path>\0<sha256_hex>\n

    Where <type> is either file or dir. Sort entries by path.

    The null bytes prevent boundary collisions. Without them, a file named ab with hash cd... and a file named a with hash bcd... could produce the same concatenated string. The null delimiters make each field boundary unambiguous — same principle git uses with its tree entry format.

    7. Hash the tree

    Feed the sorted, concatenated entries into a final SHA-256 hash. The resulting 64-character hex digest is the Skill ID.

    Here’s what the core logic looks like, stripped to its essentials:

    # Exclude the signature file
    entries = [(p, d) for p, d in entries if p != "skill.sig"]

    file_hashes = {}
    tree_entries = []

    for path, is_dir in entries:
        if is_dir:
            content_hash = sha256(b"").hexdigest()
            tree_entries.append(("dir", path, content_hash))
        else:
            content_hash = sha256(file_contents[path]).hexdigest()
            file_hashes[path] = content_hash
            tree_entries.append(("file", path, content_hash))

    # Sort by path for deterministic ordering
    tree_entries.sort(key=lambda e: e[1])

    # Hash the tree
    skill_hasher = sha256()
    for entry_type, path, content_hash in tree_entries:
        line = f"{entry_type}\0{path}\0{content_hash}\n".encode()
        skill_hasher.update(line)

    skill_id = skill_hasher.hexdigest()

    The function returns both the Skill ID and the dictionary of per-file hashes. One call, two useful outputs.

    A concrete example

    A skill with two files — SKILL.md containing # My Skill\nDo something useful.\n and lib/helper.py containing def helper():\n    return True\n — produces Skill ID 8cc13e05527df88d6c53ab0534bf43aea3b8f7456ac135a0d78f859bc9e6bd85. Wrap those same files in my-skill/ and the ID is identical. Add a .DS_Store and it changes to 847c22d50591cfabeca26859f1295c903cd134e0b26cd146a9ea7283ad3241d0.

    Anatomy of a Skill ID — from directory tree to fingerprint
    Anatomy of a Skill ID — from directory tree to fingerprint

    Properties that matter

    The Skill ID has one defining property: it changes if and only if meaningful content changes.

    Same files, different archive timestamps? Same ID. Same files, different entry order in the archive? Same ID. Same files, wrapped in a directory called my-skill/ vs. beta-release/ vs. no wrapper at all? Same ID. Windows backslashes vs. Unix forward slashes? Same ID. macOS NFD filenames vs. Linux NFC filenames? Same ID.

    Change one byte of one file? Different ID. Rename a file? Different ID. Add an empty file? Different ID. Change directory structure? Different ID.

    The ID is deterministic — same input, same output. It’s content-addressable, derived solely from the files themselves. And it inherits SHA-256’s collision resistance.

    One deliberate design choice: OS metadata files like .DS_StoreThumbs.dbdesktop.ini, and __MACOSX/ resource forks are included in the hash, not filtered out. This is the conservative stance — defense over convenience. If a .DS_Store file appears inside a skill ZIP, the Skill ID reflects it. The alternative — silently ignoring certain filenames — introduces a filtering policy that would need ongoing maintenance and could mask unintended content. If a skill author’s build process leaks OS metadata into the ZIP, the Skill ID will catch it. That seems preferable to pretending those files don’t exist.

    What you can build with a stable ID

    A content-addressable identifier for skills unlocks the same workflows that file hashes enabled for executables, and the security industry has been refining those workflows for decades.

    Allowlisting

    Hash-based allowlisting is one of the oldest patterns in endpoint security: compute the hash of a known-good binary, store it and skip re-scanning when the same hash appears again. Skill IDs enable the same pattern. When a skill has been manually reviewed and cleared — say, a first-party office document conversion skill that happens to compile C code at runtime — its Skill ID goes into an override table. Any future submission matching that ID gets the pre-approved result immediately, no analysis pipeline needed. Change one byte and the override no longer applies, forcing a fresh review.

    Content-addressable storage

    The Skill ID serves as a natural storage key. Upload a skill ZIP to object storage keyed by its Skill ID, and you get automatic deduplication — the same skill submitted by a hundred users is stored once. This is the same principle behind git objects and Docker image layers: address content by its hash, store it once, reference it everywhere.

    Per-file caching

    The per-file hashes returned alongside the Skill ID enable granular caching of expensive analysis steps. An LLM-based security analysis of a single file might cost real money and real time. If a skill is updated but only one file changes, the per-file hashes let you reuse cached analysis for every unchanged file and re-analyze only the delta. The composite Skill ID changes (because one file changed), but most of the analysis work is already done.

    Detection flagging

    Individual file hashes connect directly to existing reputation and detection systems. If the SKILL.md file in a skill contains references to known-malicious URLs, you can set a detection flag on that file’s SHA-256 hash — the same killbit mechanism the security industry uses for flagging malicious executables. The file hash becomes the bridge between the skill analysis system and established threat intelligence infrastructure.

    Version tracking

    Every content change produces a new Skill ID. This creates natural version boundaries without requiring any explicit versioning scheme. If the ID is the same, the skill hasn’t changed. If it differs from what you saw last time, something is different — no version [AR1] [MS2] parsing, no changelog inspection, no trust in a self-reported version number. The content itself is the version.

    The parallel to traditional security is direct. PE files get a SHA-256 hash. NuGet and npm packages get checksums. Container images get content digests. Skill IDs serve the same role for a new artifact type — one that happens to be a directory tree instead of a single file.

    How we use it

    The Skill ID gates our analysis pipeline at Gen Threat Labs. Same ID means cached results — skip everything. New ID triggers a fresh analysis pass: static content extraction, URL reputation checks, AV scanning and AI-powered security review.

    Skill analysis pipeline — Skill ID gates the intake
    Skill analysis pipeline — Skill ID gates the intake

    Signing skills: From identity to authenticity

    The Skill ID answers “what is this skill?” — but not “who published it?” or “has it been tampered with since publication?” Identity is necessary but not sufficient. We also need authenticity.

    The approach borrows directly from Authenticode signing of Windows PE files. In a PE binary, the Authenticode signature is embedded inside the file itself, in a region that the hash algorithm knows to skip. The signature is physically present in the file but excluded from the hash calculation. This lets the binary carry its own proof of authenticity without that proof interfering with its identity.

    The skill equivalent is the skill.sig file, which lives inside the skill directory but the Skill ID algorithm skips it (step 4 above). Same principle, different artifact type.

    The skill.sig file uses a CMS SignedData envelope (PKCS #7 / RFC 5652), the same signature format Authenticode uses. The envelope contains the digital signature, the signer’s full certificate chain (end-entity certificate through intermediate CAs), and optionally an RFC 3161 timestamp countersignature from a trusted Timestamping Authority. The signed content isn’t the bare Skill ID but a versioned payload string: SKILL_ID:1:sha256:<skill_id>. The version and algorithm fields provide forward compatibility; if the digest algorithm or normalization rules ever change, the verifier knows exactly which rules produced the enclosed ID.

    Embedding the full certificate chain is what makes the skill self-verifiable. The signature, the chain, and the identity all travel together in the same artifact. No external key lookup is needed at verification time. A marketplace can verify on ingest. An agent runtime can verify before execution. An enterprise security team can verify before deployment.

    Verification follows the same pattern as Authenticode: compute the Skill ID while excluding skill.sig, parse the CMS envelope, confirm the signed payload matches the computed ID, build the certificate chain up to a trusted root, validate expiry and the code-signing Extended Key Usage, check revocation via CRL or OCSP and verify the cryptographic signature. If a timestamp countersignature is present and the signer’s certificate has expired but was valid at the countersigned time, the signature is still accepted.

    The pattern enables several trust models. Author signing: the skill author obtains a code-signing certificate and signs the Skill ID, proving authorship. Marketplace attestation: a marketplace countersigns skill IDs it has vetted, adding a layer of review. Private CA: enterprises operate their own certificate authority and issue signing certificates to approved authors, restricting execution to internally-approved skills. Revocation: when a signing key is compromised, the CA publishes the certificate on its CRL, and verifiers reject all skills signed with that certificate unless a valid timestamp countersignature proves the signature predates the revocation. Each model builds on the same primitive: a CMS envelope over the signed payload, excluded from the ID itself.

    Looking ahead

    A stable, cross-platform identifier for skills opens several directions worth pursuing.

    SHA-256 hashes are the lingua franca of malware intelligence — any vendor, any platform, same hash for the same file. Skill IDs could serve the same role for skill reputation. A skill flagged as malicious on one marketplace could be recognized on another or by an independent scanning service, using nothing but the Skill ID. No API integration required.

    The per-file hashes already make incremental analysis possible in principle. When a skill is updated, diff the per-file hash dictionaries: unchanged hashes reuse cached analysis, changed hashes trigger re-analysis. For LLM-based analysis (the most expensive stage in the pipeline) this could cut costs and latency substantially on minor updates.

    Skill ID is a companion to AARTS, our standard for runtime agent action transparency. AARTS defines how agent actions are intercepted and evaluated at runtime; Skill ID defines how skills are identified and authenticated before they’re loaded. Together they cover both runtime behavior and supply chain identity.

    Conclusion

    Skills are code — natural-language code, but code nonetheless. And like all code that gets distributed, shared, and executed, skills need an identity before the security industry’s established workflows can apply to them.

    The Skill ID is deliberately unoriginal. SHA-256 for content hashing. Git-style tree entries for directory fingerprinting. Null-byte delimiters for boundary safety. Path normalization for cross-platform consistency. Wrapper stripping for transport independence. Every piece has been proven elsewhere; we assembled them for a new artifact type.

    The value isn’t in the hash. It’s in what the hash enables: allowlisting, deduplication, per-file caching, detection flagging, version tracking and cryptographic signing. These are the same workflows the security industry built around file hashes over the past two decades. Skills just needed their own version of the fingerprint.

    Marketplace operators can implement Skill ID for verifiable identity; agent runtimes can verify IDs before loading skills. The spec and reference implementation are on GitHub.

    As part of the broader Agent Trust Hub vision, Skill ID lays the groundwork for an AI ecosystem where skills can be identified, authenticated and governed with the same rigor we expect from every other critical software layer.

    Michal Salát
    Threat Intelligence Director
    Follow us for more