Signing and verification
haiku.skills supports identity-based signing via sigstore. You decide which OIDC identities (email addresses, GitHub Actions workflows) you trust, and sigstore's transparency log proves the signature is authentic. No key management required.
Installation
Signing requires the signing extra:
Or with pip:
Without this extra, skills load normally. Signing is entirely optional.
Signing a skill
From the CLI:
Or programmatically:
This triggers an OIDC browser flow to authenticate your identity, then writes SKILL.sigstore into the skill directory. In CI environments with ambient OIDC credentials (e.g. GitHub Actions), the browser flow is skipped automatically.
What gets signed
The hash covers all files in the skill directory except:
SKILL.sigstore(the bundle itself)__pycache__/directories and.pyc/.pyofilesnode_modules/directories- Hidden files and directories (starting with
.) - Files matching
.gitignorepatterns (walked up from the skill directory to the repository root)
Files are hashed in sorted order by relative path for determinism.
Verifying skills
From the CLI:
Programmatically, pass trusted identities to SkillRegistry or discover():
from pathlib import Path
from haiku.skills import SkillRegistry, TrustedIdentity
identities = [
TrustedIdentity(
identity="author@example.com",
issuer="https://accounts.google.com",
),
]
registry = SkillRegistry(trusted_identities=identities)
errors = registry.discover(paths=[Path("./skills")])
for name in registry.names:
skill = registry.get(name)
print(f"{skill.metadata.name}: verified={skill.verified}")
The lower-level verify_skill function can also be used directly:
from haiku.skills import TrustedIdentity, verify_skill
# Full verification (identity + integrity)
verify_skill(path, [TrustedIdentity(identity="...", issuer="...")])
# Integrity only
verify_skill(path, unsafe=True)
Verification policy
| Scenario | Result |
|---|---|
| No trusted identities provided | Skill loads, verified=False |
Identities provided, no SKILL.sigstore |
Skill loads, verified=False |
| Identities provided, valid bundle | Skill loads, verified=True |
| Identities provided, invalid bundle | SkillValidationError (skill rejected) |
Inspecting a bundle's signer
Use get_bundle_signer to extract the signer identity from an existing SKILL.sigstore bundle without performing full verification:
from pathlib import Path
from haiku.skills import get_bundle_signer
signer = get_bundle_signer(Path("./skills/my-skill"))
if signer:
print(f"Signed by {signer.identity} (issuer: {signer.issuer})")
Returns a TrustedIdentity if the bundle exists and can be parsed, None otherwise. Requires the signing extra.
GitHub Actions integration
Sigstore supports ambient OIDC credentials in GitHub Actions, no secrets needed. This workflow signs skills on push to main:
name: Sign Skills
on:
push:
branches: [main]
paths:
- 'skills/**/SKILL.md'
- 'skills/**/scripts/**'
permissions:
id-token: write # Required for sigstore OIDC
contents: write # Required to commit .sigstore bundles
jobs:
sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync --extra signing
- name: Sign changed skills
run: |
for skill_dir in skills/*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
uv run haiku-skills sign "$skill_dir"
fi
done
- name: Commit signatures
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add skills/**/SKILL.sigstore
git diff --cached --quiet || git commit -m "Sign skills [skip ci]"
git push
The GitHub Actions OIDC identity will look like:
- Identity:
https://github.com/your-org/your-repo/.github/workflows/sign.yml@refs/heads/main - Issuer:
https://token.actions.githubusercontent.com
Consumers verify against that workflow identity:
TrustedIdentity(
identity="https://github.com/your-org/your-repo/.github/workflows/sign.yml@refs/heads/main",
issuer="https://token.actions.githubusercontent.com",
)
FAQ
What if I don't use signing?
Unsigned skills load normally with verified=False.
What about entrypoint and MCP skills? Signing currently applies to filesystem-discovered skills only. Entrypoint skills are verified by your package manager. MCP skills connect to running servers where signing doesn't apply.
What happens if a signed skill is modified?
The directory hash will no longer match the bundle, and verification will fail with a SkillValidationError. The skill will not load when trusted identities are configured.