* feat(extensions): verify catalog archive sha256 before install Extension and preset archives were downloaded over HTTPS and unpacked (with Zip-Slip protection) but their bytes were never checked against a known digest. Trust rested entirely on TLS and the integrity of the release host, so a tampered or swapped archive from a compromised third-party release would be installed silently. Maintainers do not audit extension code, so consumer-side integrity is the only available defence. Catalog entries may now pin an optional `sha256` digest. When present, the downloaded archive is verified before it is written to disk and installed; a mismatch aborts with a clear error. Entries without `sha256` keep working unchanged (a DEBUG line records that the download was unverified), so the change is backwards compatible. The check runs on both download paths (extensions and presets) via a single shared helper so the two stay in parity. - Add `verify_archive_sha256` helper in shared_infra (digest match, `sha256:` prefix, case-insensitive; DEBUG log when no digest declared) - Enforce it in ExtensionCatalog.download_extension and PresetCatalog.download_pack, before the archive is written to disk - Document the optional `sha256` field in the publishing guides - Tests: helper unit tests + matching/mismatch/no-digest on both paths Signed-off-by: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> Assisted-by: AI * fix(extensions): harden sha256 parsing and tidy download test mocks Follow-up to the review on #3080: - shared_infra.verify_archive_sha256: strip only a literal `sha256:` algorithm prefix (case-insensitive) instead of `split(':', 1)[-1]`, which silently dropped any prefix — so `md5:<64-hex>` was accepted as if it were a valid SHA-256. Validate that the declared value is exactly 64 hex characters and raise a clear error otherwise, and compare with `hmac.compare_digest` for a constant-time check. Add tests covering a malformed digest and a non-`sha256:` prefix (both previously accepted). - Download test helpers: configure the context-manager mock via `__enter__.return_value`/`__exit__.return_value` rather than assigning a `lambda s: s`, which is clearer and independent of the invocation arity. Assisted-by: AI Signed-off-by: Zied Jlassi (Architect AI) <6190550+zied-jlassi@users.noreply.github.com> * fix(extensions): reject a declared-but-empty sha256 instead of skipping verification verify_archive_sha256 skipped on any falsy expected value, so a present-but-empty digest (e.g. sha256: "" reached via ...get("sha256")) silently disabled the integrity check instead of surfacing the authoring error. Guard on expected is None so only an absent digest skips; blank/whitespace/bare-prefix values fall through to the 64-hex validation and are rejected. Adds a regression test. Signed-off-by: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> * docs(shared_infra): clarify _SHA256_HEX_RE accepts and normalizes uppercase The comment described the regex as matching '64 lowercase' hex characters, but verify_archive_sha256 lowercases the declared value (raw.lower()) before matching, so an uppercase digest is accepted and normalized rather than rejected. Clarify the comment to avoid misleading future readers. Addresses Copilot review feedback on shared_infra.py. Signed-off-by: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> * test(presets): cover the no-sha256 backwards-compatible path Address Copilot review: download_pack's optional sha256 verification was tested for match/mismatch but not the backwards-compatible path where a catalog entry has no sha256 (pack_info.get("sha256") is None). Add a no-sha256 test mirroring the extensions coverage so the helper never silently becomes mandatory for presets. Signed-off-by: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> --------- Signed-off-by: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> Signed-off-by: Zied Jlassi (Architect AI) <6190550+zied-jlassi@users.noreply.github.com>
8.7 KiB
Preset Publishing Guide
This guide explains how to publish your preset to the Spec Kit preset catalog, making it discoverable by specify preset search.
Table of Contents
- Prerequisites
- Prepare Your Preset
- Submit to Catalog
- Verification Process
- Release Workflow
- Best Practices
Prerequisites
Before publishing a preset, ensure you have:
- Valid Preset: A working preset with a valid
preset.ymlmanifest - Git Repository: Preset hosted on GitHub (or other public git hosting)
- Documentation: README.md with description and usage instructions
- License: Open source license file (MIT, Apache 2.0, etc.)
- Versioning: Semantic versioning (e.g., 1.0.0)
- Testing: Preset tested on real projects with
specify preset add --dev
Prepare Your Preset
1. Preset Structure
Ensure your preset follows the standard structure:
your-preset/
├── preset.yml # Required: Preset manifest
├── README.md # Required: Documentation
├── LICENSE # Required: License file
├── CHANGELOG.md # Recommended: Version history
│
├── templates/ # Template overrides
│ ├── spec-template.md
│ ├── plan-template.md
│ └── ...
│
└── commands/ # Command overrides (optional)
└── speckit.specify.md
Start from the scaffold if you're creating a new preset.
2. preset.yml Validation
Verify your manifest is valid:
schema_version: "1.0"
preset:
id: "your-preset" # Unique lowercase-hyphenated ID
name: "Your Preset Name" # Human-readable name
version: "1.0.0" # Semantic version
description: "Brief description (one sentence)"
author: "Your Name or Organization"
repository: "https://github.com/your-org/spec-kit-preset-your-preset"
license: "MIT"
requires:
speckit_version: ">=0.1.0" # Required spec-kit version
provides:
templates:
- type: "template"
name: "spec-template"
file: "templates/spec-template.md"
description: "Custom spec template"
replaces: "spec-template"
tags: # 2-5 relevant tags
- "category"
- "workflow"
Validation Checklist:
- ✅
idis lowercase with hyphens only (no underscores, spaces, or special characters) - ✅
versionfollows semantic versioning (X.Y.Z) - ✅
descriptionis concise (under 200 characters) - ✅
repositoryURL is valid and public - ✅ All template and command files exist in the preset directory
- ✅ Template names are lowercase with hyphens only
- ✅ Command names use dot notation (e.g.
speckit.specify) - ✅ Tags are lowercase and descriptive
3. Test Locally
# Install from local directory
specify preset add --dev /path/to/your-preset
# Verify templates resolve from your preset
specify preset resolve spec-template
# Verify preset info
specify preset info your-preset
# List installed presets
specify preset list
# Remove when done testing
specify preset remove your-preset
If your preset includes command overrides, verify they appear in the agent directories:
# Check Claude commands (if using Claude)
ls .claude/commands/speckit.*.md
# Check Copilot commands (if using Copilot)
ls .github/agents/speckit.*.agent.md
# Check Gemini commands (if using Gemini)
ls .gemini/commands/speckit.*.toml
4. Create GitHub Release
Create a GitHub release for your preset version:
# Tag the release
git tag v1.0.0
git push origin v1.0.0
The release archive URL will be:
https://github.com/your-org/spec-kit-preset-your-preset/archive/refs/tags/v1.0.0.zip
5. Test Installation from Archive
specify preset add --from https://github.com/your-org/spec-kit-preset-your-preset/archive/refs/tags/v1.0.0.zip
Submit to Catalog
Understanding the Catalogs
Spec Kit uses a dual-catalog system:
catalog.json— Official, verified presets (install allowed by default)catalog.community.json— Community-contributed presets (discovery only by default)
All community presets should be submitted to catalog.community.json.
1. Fork the spec-kit Repository
git clone https://github.com/YOUR-USERNAME/spec-kit.git
cd spec-kit
2. Add Preset to Community Catalog
Edit presets/catalog.community.json and add your preset.
⚠️ Entries must be sorted alphabetically by preset ID. Insert your preset in the correct position within the
"presets"object.
{
"schema_version": "1.0",
"updated_at": "2026-03-10T00:00:00Z",
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/presets/catalog.community.json",
"presets": {
"your-preset": {
"name": "Your Preset Name",
"description": "Brief description of what your preset provides",
"author": "Your Name",
"version": "1.0.0",
"download_url": "https://github.com/your-org/spec-kit-preset-your-preset/archive/refs/tags/v1.0.0.zip",
"sha256": "OPTIONAL: SHA-256 hex digest of the archive above; verified before install",
"repository": "https://github.com/your-org/spec-kit-preset-your-preset",
"license": "MIT",
"requires": {
"speckit_version": ">=0.1.0"
},
"provides": {
"templates": 3,
"commands": 1
},
"tags": [
"category",
"workflow"
],
"created_at": "2026-03-10T00:00:00Z",
"updated_at": "2026-03-10T00:00:00Z"
}
}
}
3. Update Community Presets Table
Add your preset to the Community Presets table on the docs site at docs/community/presets.md:
| Your Preset Name | Brief description of what your preset does | N templates, M commands[, P scripts] | — | [repo-name](https://github.com/your-org/spec-kit-preset-your-preset) |
Insert your row in alphabetical order by preset name (the first column of the table).
4. Submit Pull Request
git checkout -b add-your-preset
git add presets/catalog.community.json docs/community/presets.md
git commit -m "Add your-preset to community catalog
- Preset ID: your-preset
- Version: 1.0.0
- Author: Your Name
- Description: Brief description
"
git push origin add-your-preset
Pull Request Checklist:
## Preset Submission
**Preset Name**: Your Preset Name
**Preset ID**: your-preset
**Version**: 1.0.0
**Repository**: https://github.com/your-org/spec-kit-preset-your-preset
### Checklist
- [ ] Valid preset.yml manifest
- [ ] README.md with description and usage
- [ ] LICENSE file included
- [ ] GitHub release created
- [ ] Preset tested with `specify preset add --dev`
- [ ] Templates resolve correctly (`specify preset resolve`)
- [ ] Commands register to agent directories (if applicable)
- [ ] Commands match template sections (command + template are coherent)
- [ ] Added to presets/catalog.community.json
- [ ] Added row to docs/community/presets.md table
Verification Process
After submission, maintainers will review:
- Manifest validation — valid
preset.yml, all files exist - Template quality — templates are useful and well-structured
- Command coherence — commands reference sections that exist in templates
- Security — no malicious content, safe file operations
- Documentation — clear README explaining what the preset does
Once verified, verified: true is set and the preset appears in specify preset search.
Release Workflow
When releasing a new version:
- Update
versioninpreset.yml - Update CHANGELOG.md
- Tag and push:
git tag v1.1.0 && git push origin v1.1.0 - Submit PR to update
versionanddownload_urlinpresets/catalog.community.json
Best Practices
Template Design
- Keep sections clear — use headings and placeholder text the LLM can replace
- Match commands to templates — if your preset overrides a command, make sure it references the sections in your template
- Document customization points — use HTML comments to guide users on what to change
Naming
- Preset IDs should be descriptive:
healthcare-compliance,enterprise-safe,startup-lean - Avoid generic names:
my-preset,custom,test
Stacking
- Design presets to work well when stacked with others
- Only override templates you need to change
- Document which templates and commands your preset modifies
Command Overrides
- Only override commands when the workflow needs to change, not just the output format
- If you only need different template sections, a template override is sufficient
- Test command overrides with multiple agents (Claude, Gemini, Copilot)