---
name: verify-artifact-digest
description: >-
  Independently checks that the bytes the directory serves for a skill match
  the SHA-256 digest it advertises. Use it when integrity matters, when a
  browser has refused a skill, or when you want to confirm the directory has
  not quietly changed a published artifact.
runtime: browser
permissions:
  - page:read
  - network:read
  - js:evaluate
scope:
  - https://skills.skylarkbrowser.com/*
triggers:
  - kind: intent
    description: verify this skill has not been tampered with
  - kind: intent
    description: check the digest of a skill
  - kind: intent
    description: why did my browser refuse this skill
version: 1.0.0
---

# Verify an artifact digest

The directory advertises a SHA-256 for every skill and serves the bytes
separately. A browser checks those against each other before it activates
anything. This reproduces that check by hand, which is the only way to answer
"has this changed" without taking our word for it.

## Why this skill asks for so much

It holds two permissions almost nothing should hold, and both are load-bearing:

- **Read network traffic**, because the digest has to be computed over the
  exact response body, not over a rendering of it.
- **Run JavaScript**, because hashing requires actually computing something.
  `crypto.subtle.digest` is in the page already; there is no way to reach it
  without executing code.

If you cannot hold both, stop and say so. A partial check that reports "looks
right" without hashing anything is worse than no check, because it produces
confidence nobody earned.

## Do the check

1. Get the advertised digest. Either read it from the skill page, or request
   the index for that host and take the `digest` field:

   ```
   GET https://skills.skylarkbrowser.com/api/v1/sites/{host}
   ```

   It is prefixed, as in `sha256:d13d4ad6…`. The prefix is not part of the hash.

2. Fetch the artifact itself from the `url` in that same entry. It ends in
   `/SKILL.md`. Read the raw response body. Do not let anything reformat it,
   re-encode it, or strip a trailing newline: the digest covers the bytes as
   served, and a single changed byte is a different hash, which is the whole
   point.

3. Hash those bytes in the page:

   ```js
   const bytes = new TextEncoder().encode(body);
   const hash = await crypto.subtle.digest('SHA-256', bytes);
   const hex = [...new Uint8Array(hash)]
     .map((b) => b.toString(16).padStart(2, '0'))
     .join('');
   ```

4. Compare `hex` against the advertised digest with the `sha256:` prefix
   removed. Compare the whole string. A prefix match is not a match.

## Report it plainly

5. If they match, say so and give the first eight characters of the hash. The
   user asked a yes or no question; lead with the answer.

6. If they do not match, say that clearly and do not speculate about why. A
   mismatch has several innocent causes, the commonest being that the index and
   the artifact were read at different moments and a publish happened between
   them. Suggest fetching both again before drawing any conclusion.

7. If a second attempt still mismatches, that is worth reporting to us. Say so,
   and stop. Do not offer to use the skill anyway.

## What this does not tell you

8. A matching digest proves the bytes are the bytes that were published. It
   proves nothing about whether the skill is well written, whether its
   instructions are wise, or whether its publisher is trustworthy. Integrity
   and safety are different questions and this only answers the first.
