Module Security
Module packages carry publisher and integrity metadata so installation, bootstrap, and runtime discovery verify trust before enabling a module.
Trust model
- Manifest metadata:
module-package.yamlmay includepublisher(name, email, attributes) andintegrity(checksum, optional signature). - Checksum verification: Verification computes a deterministic hash of the full module payload (all module files, with manifest canonicalization that excludes
integrityitself). Supported algorithms:sha256,sha384,sha512inalgo:hexformat. - Signature verification: If
integrity.signatureis present and a public key is configured, signature validation proves provenance over the same full payload. - Publisher trust gate: Non-official publishers require one-time explicit trust (interactive confirmation or
--trust-non-official/SPECFACT_TRUST_NON_OFFICIAL). - Denylist gate: Modules listed in denylist are blocked before install/bootstrap regardless of source.
Integrity flow
- Discovery reads
module-package.yamland parsesintegrity.checksum. - At install/bootstrap/verification time, the tool hashes the full module payload and compares it to
integrity.checksum. - On mismatch, the module is skipped and a security warning is logged.
- Other modules continue to register; one failing trust does not block the rest.
Signing automation
- Script:
scripts/sign-module.shsigns one or moremodule-package.yamlmanifests. - Payload scope: Signing covers all files under the module directory (not only the manifest).
- Encrypted key support: Passphrase can be provided with:
--passphrase(local only; avoid shell history in CI)--passphrase-stdin(recommended for secure piping)SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE
- Key sources:
--key-fileSPECFACT_MODULE_PRIVATE_SIGN_KEY(PEM content)SPECFACT_MODULE_PRIVATE_SIGN_KEY_FILE
- Version guard: Changed module contents must have a bumped module version before signing. Override exists only for controlled local cases via
--allow-same-version. - Changed-only release mode:
scripts/sign-modules.py --changed-only --base-ref <git-ref> --bump-version <patch|minor|major>auto-selects modules with payload changes, bumps versions when unchanged, and signs only those modules. - Version decoupling: module versions are semver-managed per module payload and do not need to track CLI package version.
- CI secrets:
SPECFACT_MODULE_PRIVATE_SIGN_KEYSPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE
- Verification command:
scripts/verify-modules-signature.py --require-signature --payload-from-filesystem --enforce-version-bump--version-check-base <git-ref>can be used in CI PR comparisons.
Public key and key rotation
- Store trusted public key in:
resources/keys/module-signing-public.pem
- Optional fallback path:
src/specfact_cli/resources/keys/module-signing-public.pem
- Rotate keys by:
- generating a new key pair,
- updating trusted public key in repository,
- re-signing affected modules with incremented versions,
- running signature verification and version-bump checks in CI.
Versioned dependencies
Manifest may declare versioned module and pip dependencies via module_dependencies_versioned and pip_dependencies_versioned (each entry: name, version_specifier). These are parsed and stored for installation-time resolution while keeping legacy module_dependencies / pip_dependencies lists backward compatible.