* 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>
Presets
Presets are stackable, priority-ordered collections of template and command overrides for Spec Kit. They let you customize both the artifacts produced by the Spec-Driven Development workflow (specs, plans, tasks, checklists, constitutions) and the commands that guide the LLM in creating them — without forking or modifying core files.
How It Works
When Spec Kit needs a template (e.g. spec-template), it walks a resolution stack:
.specify/templates/overrides/— project-local one-off overrides.specify/presets/<preset-id>/templates/— installed presets (sorted by priority).specify/extensions/<ext-id>/templates/— extension-provided templates.specify/templates/— core templates shipped with Spec Kit
If no preset is installed, core templates are used — exactly the same behavior as before presets existed.
Template resolution happens at runtime — although preset files are copied into .specify/presets/<id>/ during installation, Spec Kit walks the resolution stack on every template lookup rather than merging templates into a single location.
For detailed resolution and command registration flows, see ARCHITECTURE.md.
Command Overrides
Presets can also override the commands that guide the SDD workflow. Templates define what gets produced (specs, plans, constitutions); commands define how the LLM produces them (the step-by-step instructions).
Unlike templates, command overrides are applied at install time. When a preset includes type: "command" entries, the commands are registered into all detected agent directories (.claude/commands/, .gemini/commands/, etc.) in the correct format (Markdown or TOML with appropriate argument placeholders). When the preset is removed, the registered commands are cleaned up.
Quick Start
# Search available presets
specify preset search
# Install a preset from the catalog
specify preset add healthcare-compliance
# Install from a local directory (for development)
specify preset add --dev ./my-preset
# Install with a specific priority (lower = higher precedence)
specify preset add healthcare-compliance --priority 5
# List installed presets
specify preset list
# See which template a name resolves to
specify preset resolve spec-template
# Get detailed info about a preset
specify preset info healthcare-compliance
# Remove a preset
specify preset remove healthcare-compliance
Stacking Presets
Multiple presets can be installed simultaneously. The --priority flag controls which one wins when two presets provide the same template (lower number = higher precedence):
specify preset add enterprise-safe --priority 10 # base layer
specify preset add healthcare-compliance --priority 5 # overrides enterprise-safe
specify preset add pm-workflow --priority 1 # overrides everything
Presets override by default, they don't merge. If two presets both provide spec-template with the default replace strategy, the one with the lowest priority number wins entirely. However, presets can use composition strategies to augment rather than replace content.
Composition Strategies
Presets can declare a strategy per template to control how content is combined. The name field identifies which template to compose with in the priority stack, while file points to the actual content file (which can differ from the convention path templates/<name>.md):
provides:
templates:
- type: "template"
name: "spec-template"
file: "templates/spec-addendum.md"
strategy: "append" # adds content after the core template
| Strategy | Description |
|---|---|
replace (default) |
Fully replaces the lower-priority template |
prepend |
Places content before the resolved lower-priority template, separated by a blank line |
append |
Places content after the resolved lower-priority template, separated by a blank line |
wrap |
Content contains {CORE_TEMPLATE} placeholder (or $CORE_SCRIPT for scripts) replaced with the lower-priority content |
Supported combinations:
| Type | replace |
prepend |
append |
wrap |
|---|---|---|---|---|
| template | ✓ (default) | ✓ | ✓ | ✓ |
| command | ✓ (default) | ✓ | ✓ | ✓ |
| script | ✓ (default) | — | — | ✓ |
Multiple composing presets chain recursively. For example, a security preset with prepend and a compliance preset with append will produce: security header + core content + compliance footer.
Catalog Management
Presets are discovered through catalogs. By default, Spec Kit uses the official and community catalogs:
Note
Community presets are independently created and maintained by their respective authors. Maintainers only verify that catalog entries are complete and correctly formatted — they do not review, audit, endorse, or support the preset code itself. Review preset source code before installation and use at your own discretion.
# List active catalogs
specify preset catalog list
# Add a custom catalog
specify preset catalog add https://example.com/catalog.json --name my-org --install-allowed
# Remove a catalog
specify preset catalog remove my-org
Creating a Preset
See scaffold/ for a scaffold you can copy to create your own preset.
- Copy
scaffold/to a new directory - Edit
preset.ymlwith your preset's metadata - Add or replace templates in
templates/ - Test locally with
specify preset add --dev . - Verify with
specify preset resolve spec-template
Environment Variables
| Variable | Description | Default |
|---|---|---|
SPECKIT_PRESET_CATALOG_URL |
Override the full catalog stack with a single URL (replaces all defaults) | Built-in default stack |
GH_TOKEN / GITHUB_TOKEN |
GitHub token for authenticated requests to GitHub-hosted URLs (raw.githubusercontent.com, github.com, api.github.com, codeload.github.com). Required when your catalog JSON or preset ZIPs are hosted in a private GitHub repository. |
None |
Example: Using a private GitHub-hosted catalog
# Authenticate with a token (gh CLI, PAT, or GITHUB_TOKEN in CI)
export GITHUB_TOKEN=$(gh auth token)
# Search a private catalog added via `specify preset catalog add`
specify preset search my-template
# Install from a private catalog
specify preset add my-template
The token is attached automatically to requests targeting GitHub domains. Non-GitHub catalog URLs are always fetched without credentials.
Configuration Files
| File | Scope | Description |
|---|---|---|
.specify/preset-catalogs.yml |
Project | Custom catalog stack for this project |
~/.specify/preset-catalogs.yml |
User | Custom catalog stack for all projects |
Future Considerations
The following enhancements are under consideration for future releases:
- Structural merge strategies — Parsing Markdown sections for per-section granularity (e.g., "replace only ## Security").
- Conflict detection —
specify preset lint/specify preset doctorfor detecting composition conflicts.