Conversation
publish-registry.yml fell back to the literal 'dev-fallback-key' when no signing secret was configured, so the published registry-signature.json was an HMAC anyone could recompute from the public workflow file. Remove the fallback so an unconfigured secret fails the job instead of shipping a meaningless signature. This is the repository owner's in-progress change, committed as-is; the follow-up commits make Ed25519 the only scheme. Refs: F101 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…c key Remove the shared-secret HMAC-SHA256 scheme and SKILLS_SIGNING_KEY from tools/sign_manifest.py: anyone able to verify an HMAC signature could also forge one, so it gave consumers no integrity guarantee. Ed25519 is now the only scheme; `--ed25519` stays accepted as a no-op because it is the documented publish command. Commit the pinned public key as keys/registry-ed25519.pub (the key Rho pins; the private half lives only in the SKILLS_ED25519_PRIVATE_KEY Actions secret). Add `verify --signature-file`, which implements the verifier side of the registry contract: algorithm must be ed25519, target must match, sha256 must equal the digest of the exact bytes, and the signature must verify over the 64-byte ASCII hex digest. `verify` falls back to the committed key when no key is supplied. The signature JSON's `target` is now the file's base name. Tests use throwaway keypairs only and pin the committed key's value so a key change cannot slip past Rho's pinned copy. BREAKING CHANGE: `sign`/`verify` no longer accept HMAC secrets or SKILLS_SIGNING_KEY; published signatures use algorithm "ed25519". Refs: F101 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/requirements.txt lists pyyaml, rich and cryptography with no versions or hashes, and publish-registry.yml installed it (after an unpinned `pip install --upgrade pip`) in the same job that received the signing key. A compromised PyPI release could have signed a malicious registry or exfiltrated the key. Add uv-generated, hash-locked install sets (Python 3.11, manylinux): - tools/requirements.lock: the full tools/requirements.txt closure for the build job (validate + generate registry). - tools/requirements-sign.lock (from requirements-sign.in): only cryptography, cffi and pycparser, for the job that holds the key. Both install with `pip install --require-hashes --no-deps`; verified in a clean Python 3.11 venv (pip check clean, validate_skill.py runs). Refs: F248 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Harden publish-registry.yml, the only workflow with contents:write and the signing secret: - Ed25519 only: the HMAC branch and SKILLS_SIGNING_KEY are gone, and the sign step fails with an error when SKILLS_ED25519_PRIVATE_KEY is unset. - Before anything is uploaded, verify registry-signature.json with the committed keys/registry-ed25519.pub (algorithm, target, sha256 and signature), so a wrong or rotated secret fails the run instead of breaking every Rho client. - Split into a secret-free, read-only build job (validate + generate) and a sign-and-publish job that installs only the hash-locked cryptography stack; both use `pip install --require-hashes --no-deps`. - Pin checkout, setup-python, upload-artifact and download-artifact to commit SHAs (the same SHAs the other workflows use), set persist-credentials: false, and drop `pip install --upgrade pip`. - Add a non-cancelling `publish-registry` concurrency group so two quick merges cannot publish a registry/signature pair from different commits. - Validate with the zero-warning budget like ci.yml, and trigger on keys/**, manifest-schema.toml and the workflow file too. tests/test_workflows.py pins these properties (SHA-pinned actions in every workflow, step order sign < verify < upload < publish, secret scoping, and exact-pin + hash coverage of both lock files). actionlint 1.7.7 is clean for this workflow. Refs: F101, F109, F248 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The registry publish job now installs tools/requirements.lock and tools/requirements-sign.lock; audit those exact, hash-pinned versions in the required CI job too (locally: "No known vulnerabilities found"). Refs: F248 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/REGISTRY.md said the registry is published "to the CDN"; it is published to the rolling `registry-latest` GitHub release. Document the two asset URLs, the pinned public key, the exact registry-signature.json format (message = the 64 ASCII bytes of the hex SHA-256), the fail-closed verification steps clients must follow, how to verify with this repo's tool or plain OpenSSL 3 (commands tested with a throwaway key), and key rotation ordering with Rho's pinned copy. Record the change in the CHANGELOG Security section, including that previously published hmac-sha256 signatures must not be trusted. Refs: F101, F115 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the published skill registry's signature mean something. The rolling
registry-latestrelease currently carries anhmac-sha256signature made with the literaldev-fallback-keyfrom the workflow file, so anyone can forge it. This PR:registry.jsonwith Ed25519 only (HMAC andSKILLS_SIGNING_KEYremoved fromtools/sign_manifest.py);keys/registry-ed25519.pub(the key Rho pins, spec §1.10);publish-registry.ymlfail whenSKILLS_ED25519_PRIVATE_KEYis unset, and verify its own signature with the committed public key before uploading;cryptographystack; pins every action to a commit SHA; adds a non-cancelling concurrency group; validates with the zero-warning budget;docs/REGISTRY.md.It also carries the owner's uncommitted work-in-progress fix to the workflow (first commit, unchanged).
Merge note: the
SKILLS_ED25519_PRIVATE_KEYsecret already exists on this repo (checked withgh api .../actions/secrets: the only secret listed). Merging touchestools/**, so the publish workflow runs on merge and replaces the forgeable asset. If the secret does not correspond tokeys/registry-ed25519.pub, the new verify step fails the run before anything is uploaded; that is intended.Findings addressed
registry-signature.jsonishmac-sha256, its sha256 matches the published bytes, andHMAC-SHA256('dev-fallback-key', sha256)equals the published signature. Fixed with Ed25519-only signing, the committed pinned key, fail-if-unset, and verify-before-upload. The newverify --signature-filerejects the currently published asset (algorithm must be 'ed25519', got 'hmac-sha256').contents: write. Actions are SHA-pinned; dependencies install fromtools/requirements.lock/tools/requirements-sign.lockwith--require-hashes --no-deps; the key-holding job installs onlycryptography/cffi/pycparser;pip install --upgrade pipis gone;persist-credentials: false. CI now also runspip-auditon both lock files.concurrency: {group: publish-registry, cancel-in-progress: false},--warning-budget, and path triggers forkeys/**,manifest-schema.tomland the workflow file. (Theci.ymlgithub.event.beforeguard is in the companion docs/tooling PR.)docs/REGISTRY.mdsaid the registry goes "to the CDN"; it goes to theregistry-latestGitHub release.Not reproduced / deferred
Verification
Run on this branch (macOS, Python 3.14 venv; CI uses 3.11):
make lintAll checks passed!)bash scripts/check-consumer-boundaries.shpython -m compileall -q categoriespython tools/validate_skill.py --all --warning-budget tools/validation_warning_budget.jsonpython tools/update_registry.py && python tools/update_registry.py --checkpython tools/check_version_sync.pypython tools/sync_marketplace.py --checkpython tools/check_references.pypython tools/check_self_contained.pypython tools/check_secrets.py --strictpython tools/check_shell_commands.py --strictpython -m pytest tests/ -q --cov --cov-fail-under=88make package-checkactionlint 1.7.7 .github/workflows/publish-registry.ymlci.ymlare pre-existing.pip-audit --require-hashes -r tools/requirements.lock -r tools/requirements-sign.lockNo known vulnerabilities foundpip install --require-hashes --no-deps -r tools/requirements.lockin a clean Python 3.11 venvpip checkclean;validate_skill.pyrunsSigning simulation with a throwaway keypair (never the real key): the unset-secret branch exits 1 with the
::error::message.sign registry.json --ed25519emits{target: "registry.json", algorithm: "ed25519", sha256 (64 hex, equal to shasum -a 256), signature (128 hex)}.verify --signature-file ... --key keys/registry-ed25519.pubfails for the throwaway signature (as it must) and passes with the throwaway public key. OpenSSL 3.6pkeyutl -verify -rawinindependently verifies the signature over the 64 ASCII hex bytes.New tests:
tests/test_sign_manifest.pycovers the contract: ASCII-hex message, tamper cases, pinned-key value, and no private key inkeys/.tests/test_workflows.pycovers SHA-pinned actions in every workflow, secret scoping, fail-if-unset, the step order sign < verify < upload < publish, hash-locked installs, and lock-file integrity.Follow-ups
registry-signature.jsonfail-closed; both sides test with throwaway keys.Publish Skill Registryrun onmainpasses the verify step, and check the new asset:gh release download registry-latest -p registry-signature.json, thenjq .algorithmshould print"ed25519".tools/*.lock.🤖 Generated with Claude Code