Skip to content

[architect] Marketplace integrity is opt-out: an index omitting schemaVersion installs plugins with no checksum #213

Description

@hanthor-hive-agent

Architecture Finding

Type: interface-violation / anti-pattern (trust boundary)
Affected area: pkg/plugin/marketplace.go, marketplace/schema-v2.json

ADR-0007 makes the marketplace the trust boundary for third-party code: "Marketplaces are signed/checksummed indexes of standalone binaries… provenance, version pinning, compatibility ranges, immutable artifacts, explicit permission consent." The schema encodes that — schema-v2.json requires publisher, license, pluginApi, and a 64-hex sha256 on every platform build.

But every integrity requirement in the code is gated behind one field the index publisher controls:

// fetchSource — the runtime path for every marketplace source
if idx.SchemaVersion == "" {
    idx.SchemaVersion = "v1"        // marketplace.go:295
}

Entry.Validate() then wraps the whole integrity block in if e.SchemaVersion == MarketplaceAPIV2 (marketplace.go:351), so a v1 entry is never required to carry a digest. Install repeats the pattern:

if e.SchemaVersion == MarketplaceAPIV2 && len(b.SHA256) != 64 {   // :453
    return fmt.Errorf("marketplace v2 requires a SHA-256 digest")
}
...
if b.SHA256 != "" && !strings.EqualFold(sum, b.SHA256) {          // :486
    return fmt.Errorf("checksum mismatch for %s (got %s)", e.Name, sum)
}

For a v1 index b.SHA256 is "", so the second guard is also skipped. The result: an index that simply omits schemaVersion gets its binaries downloaded, chmod 0755'd, and installed into plugin.Dir() with no integrity verification of any kind. The security tier is chosen by the party being verified.

Two supporting gaps:

  • The strict validator that does require v2 — ValidateIndexFile (marketplace.go:220, if idx.SchemaVersion != MarketplaceAPIV2 { return err }) — is reachable only from the publisher-side corral marketplace validate subcommand (cmd/plugin.go:185). It never runs on a fetched index.
  • Source.Trusted (marketplace.go:78) is set true for the built-in source (:123) and read nowhere in the codebase. The first-party/third-party distinction the ADR's model rests on is declared but not implemented.

Transport is fine — validateSourceURL enforces HTTPS for both index and artifact URLs.

Impact

A third-party marketplace — added via corral marketplace add or CORRAL_MARKETPLACE_URLS — opts out of every artifact guarantee by deleting one line from its index. Nothing in the CLI output distinguishes an unverified v1 install from a checksummed v2 one, so an operator has no way to tell which trust level they just accepted. The artifact host is also not constrained to the publisher's origin, so a v1 index can point url anywhere and the swap is undetectable by construction.

This is the standard downgrade-by-omission shape: a security control that is opt-in for the untrusted producer is not a control. It also blocks the ADR's stated goal — "external contribution possible without granting third-party code the main process's trust" — because today external contribution and full process trust are the same thing.

Recommendation

  1. Make v2 the floor for network-fetched indexes: have fetchSource reject an index without schemaVersion: corral.marketplace/v2, i.e. reuse the ValidateIndexFile rule on the fetch path instead of defaulting to "v1". If a v1 grace period is needed, gate it on an explicit --allow-unverified / Source.Trusted=false opt-in that prints what is being given up, rather than on silence.
  2. Delete the SchemaVersion == MarketplaceAPIV2 conditions in Validate and Install once (1) lands — with a v2 floor, the digest requirement is unconditional and the two skip paths disappear.
  3. Either implement Source.Trusted (it is the natural place to express "this publisher may ship without a signature") or remove the field, so the trust model in the code matches the one in ADR-0007.
  4. Add a conformance test asserting that an index with no schemaVersion fails to install — the current tests cover the v2 happy path and signature verification, not the downgrade.

Filed by architect agent (ACMM L6 — full mode)

— hive: agent=architect backend=claude model=claude-opus-5 claude=2.1.226

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/architectApproved by a Hive merger/owner for auto-merge on green CIarchitectureApproved by a Hive merger/owner for auto-merge on green CIhive/hive-keen-minkApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions