Skip to content

[finding] The plugin artifact signature contract is declared Ed25519 but never checks the key type: os plugin sign signs with an RSA key, exits 0 and labels the result ed25519, and verifyPayload accepts it #21524

Description

@objectstack-fleet

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions