Filing gate: ① a defect with a named position, a finding of class (b), a contract the implementation does not enforce.
What happens (measured, public door)
os plugin sign ARTIFACT --key KEY with an RSA private key (PKCS#8):
- exits 0 and prints
✓ Plugin signed;
- writes a sidecar whose signature string begins
ed25519:default: but carries an RSA-sized signature, 342 base64url characters where an Ed25519 key gives 86;
- passes its own self-verification, because the matching non-Ed25519 public key verifies it.
Why (read from source at origin/main)
packages/core/src/security/plugin-artifact-signature.ts states in its header that it is "the CANONICAL Ed25519 detached-signature contract", with "Algorithm: Ed25519 via node:crypto".
SIGNATURE_ALG is 'ed25519', and the format is ed25519:KEYID:SIG.
signPayload and verifyPayload both call node's sign / verify with a null algorithm. That is Ed25519 only when the key is Ed25519, and neither function checks the key's type.
- So the signer produces an RSA, EC or other signature, labels it
ed25519, and the verifier accepts it against a matching non-Ed25519 public key.
parseSignature trusts the label.
The label claims one algorithm, and the code honours whatever key it is given. The header also says it is byte-for-byte compatible with the cloud control plane's signing module, so a signature this side accepts may be one the other side refuses, or the reverse. That was not measured.
Consumers on main: packages/cli/src/commands/plugin/sign.ts (sign, then self-verify) and the runtime's artifact verification through packages/core/src/security/ (verifyPluginArtifact). packages/core/src/plugin-loader.ts reads the label through parseSignature.
For triage
- Is this a security-class finding? The verifier's acceptance is bounded by which public keys are trusted, so its weight depends on whether any trust path admits a non-Ed25519 key. That was not measured.
- Which lane owns
packages/core/src/security/?
- Direction, ⛔ not a ruling: declared means enforced.
signPayload refuses a private key that is not Ed25519, and verifyPayload refuses a public key that is not Ed25519. Both are loud, and the refusal names the key type. Pins: an RSA key is refused at os plugin sign, and an Ed25519 key signs and verifies as before.
Dedupe
MCP search_issues, repo-scoped, open and closed:
Dedupe words: plugin sign accepts RSA key; ed25519 label on non-Ed25519 signature; verifyPayload key type unchecked; plugin artifact signature algorithm not enforced.
Generated by Claude Code
Filing gate: ① a defect with a named position, a
findingof class (b), a contract the implementation does not enforce.reach:was measured at a public door by the The exit-signal family's text face: os package install, package publish and plugin sign re-report oclif's exit signal as an extra "EEXIT: 1" error line (the remainder of #21434) #21496 dev atf9a8eb889e(os-dev report, out-of-scope finding 2; PR fix(cli): package install, package publish and plugin sign print one error line per refusal; the exit-signal pin covers every command #21522's Acceptance notes).domain:cliseat,session_016GiHYRmLSNWTfbX9gVQkpz. ⛔ Not a claim.packages/core/src/security/, outside this lane.What happens (measured, public door)
os plugin sign ARTIFACT --key KEYwith an RSA private key (PKCS#8):✓ Plugin signed;ed25519:default:but carries an RSA-sized signature, 342 base64url characters where an Ed25519 key gives 86;Why (read from source at
origin/main)packages/core/src/security/plugin-artifact-signature.tsstates in its header that it is "the CANONICAL Ed25519 detached-signature contract", with "Algorithm: Ed25519 via node:crypto".SIGNATURE_ALGis'ed25519', and the format ised25519:KEYID:SIG.signPayloadandverifyPayloadboth call node'ssign/verifywith anullalgorithm. That is Ed25519 only when the key is Ed25519, and neither function checks the key's type.ed25519, and the verifier accepts it against a matching non-Ed25519 public key.parseSignaturetrusts the label.The label claims one algorithm, and the code honours whatever key it is given. The header also says it is byte-for-byte compatible with the cloud control plane's signing module, so a signature this side accepts may be one the other side refuses, or the reverse. That was not measured.
Consumers on
main:packages/cli/src/commands/plugin/sign.ts(sign, then self-verify) and the runtime's artifact verification throughpackages/core/src/security/(verifyPluginArtifact).packages/core/src/plugin-loader.tsreads the label throughparseSignature.For triage
packages/core/src/security/?signPayloadrefuses a private key that is not Ed25519, andverifyPayloadrefuses a public key that is not Ed25519. Both are loud, and the refusal names the key type. Pins: an RSA key is refused atos plugin sign, and an Ed25519 key signs and verifies as before.Dedupe
MCP
search_issues, repo-scoped, open and closed:Dedupe words: plugin sign accepts RSA key; ed25519 label on non-Ed25519 signature; verifyPayload key type unchecked; plugin artifact signature algorithm not enforced.
Generated by Claude Code