Skip to content

[sec-check] Workflow marketplace: non-admin bundles launder model provenance 'curated' through hub publish and spoke import #17763

Open

Description

@hivecommons-hive

Security Finding

Severity: medium
Type: unsafe-pattern (trust-tier policy bypass / provenance laundering)

The workflow marketplace (#17714) polices the envelope tier of a published bundle but not the model's own provenance, and importers prefer the model's provenance over the envelope tier. Together this lets any registered spoke ship a model that other hives store and display as curated ("Reviewed and vouched for by a human") without a hub admin ever vouching for it.

Hub side — src/pkg/hub/marketplace.go handleMarketplacePublish: a non-admin publisher (any spoke with a valid heartbeat bearer) may assert local, local-published or ingested-unreviewed in bundle.Tier; curated/built-in are rejected with 403. But bundle.Model.Spec.Meta.Provenance is never checked. It is covered by the bundle hash (bundle.CanonicalHash), so the hub cannot normalise it either; it stores the bundle verbatim with Tier=local-published and Model.Spec.Meta.Provenance=curated.

Spoke side — src/pkg/dashboard/api_workflows_marketplace.go importBundle:

if m.Spec.Meta.Provenance == "" {
    m.Spec.Meta.Provenance = workflowmodel.Provenance(b.Tier)
}
if m.Spec.Meta.Provenance == workflowmodel.ProvenanceBuiltIn || string(m.Spec.Meta.Provenance) == bundle.TierLocalPublished {
    m.Spec.Meta.Provenance = workflowmodel.ProvenanceLocal
}

Only an empty provenance is replaced by the hub-policed tier, and only built-in/local-published are downgraded. A model carrying curated sails through st.Save as curated. The same applies to POST /api/workflows/import (direct bundle upload) where the Tier field is attacker-chosen anyway.

bundle.Verify does not cross-check Tier against Model.Spec.Meta.Provenance, so neither side catches the disagreement. Existing tests (TestMarketplaceImportFromHub, hub marketplace_test.go) only exercise bundles where tier and provenance agree.

Impact

  • A registered spoke (heartbeat credential, no admin) publishes foo@1 with Tier=local-published and Model.Spec.Meta.Provenance=curated. The hub accepts (201). The marketplace index correctly shows local-published, but every hive that imports it gets a library entry badged curated — "Reviewed and vouched for by a human." (config-workflows-library.js), defeating the admin-only policy the hub documents for that tier.
  • Workflow models drive stage-tracker columns, label GC on rebind (✨ Workflow models: safe label garbage collection on rebind and model-defined human-gate replies #17710) and human-gate replies, so the trust badge is what an owner reads before binding a repo to the model. The flaw does not grant code execution; it launders a trust label.

Recommendation

  1. Spoke importBundle: derive provenance from the envelope tier whenever the model claims a tier higher than the bundle asserts — i.e. if Provenance is curated or built-in and b.Tier is not that same value, set it from b.Tier (then apply the existing built-in/local-published → local downgrade). Keep today's behaviour when the two agree so admin-published curated bundles still import as curated.
  2. Hub handleMarketplacePublish: when who.admin is false, reject (403) a bundle whose Model.Spec.Meta.Provenance is curated or built-in, mirroring the existing Tier check — the hub cannot rewrite the hash-covered model, so refusing is the only honest option.
  3. Tests: hub publish of a non-admin bundle with model provenance curated → 403; spoke import of a Tier=local-published bundle with model provenance curated → stored as local; admin-published curated bundle still imports as curated.

A PR implementing 1–3 follows.


Filed by sec-check agent (ACMM L6 — full mode)


🐝 Hive Agent: security | Instance: hosted-available-oke-11-placeholder-r05x | SHA: c95518e54

— hive: agent=sec-check backend=copilot model=claude-fable-5.1 copilot=1.0.88

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/sec-checkCreated or modified by the sec-check agenthelp wantedDenotes an issue that needs help from a contributor. Must meet "help wanted" guidelines.securitystage/040-covered-by-prAn open PR covers this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions