Open the datumctl plugin marketplace to third-party catalogs - #224
Merged
Merged
Conversation
Add the named-catalog registry, per-catalog index cache, optional catalog-level manifest header, and managed (enterprise) config that underpin third-party plugin catalogs. The default catalog stays the reserved, official, zero-config catalog; existing single-catalog behavior and old headerless manifests are preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
Add add/list/remove/update/validate subcommands for plugin catalogs, a one-time third-party trust prompt (with --yes bypass), enterprise allow-list enforcement on add, managed-catalog protection on remove, and a manifest linter for publishers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
Install now resolves a bare plugin name across every registered catalog (default first) and asks the user to qualify the name on a collision instead of guessing; catalog/name installs from a named catalog while owner/repo[@Version] stays a direct GitHub install. Search spans all catalogs with a per-plugin catalog column and trust badge and a --index scope, and the installed list shows each plugin's originating catalog and trust badge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
Add a type-to-filter browser over every registered catalog that shows each plugin's catalog and trust badge, lets the user inspect a plugin's details, and installs the selected plugin in place. Reuses the huh picker patterns and falls back to a clear message when no terminal is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
…ting A bare-name allow-list entry no longer authorizes a remote catalog source: because the local catalog name is user-chosen, allowing it to green-light any host let a trusted name be pointed at an attacker host. Name entries now authorize local sources only; remote sources must match a host pattern. Managed pre-seeded catalogs also skip reserved and malformed names. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
Catalog and index-installed plugins are now stored and dispatched under their plain name (e.g. ipam), recognized via the install record rather than a datumctl- filename prefix, so milo-os services install cleanly as datumctl plugins. The datumctl- prefix is retained only for manually PATH-installed binaries, and a generic name is trusted as a plugin only from the integrity-checked managed directory — never from a bare PATH binary. Legacy datumctl-<name> installs in the managed dir still work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
…naries Add the milo-<command> plugin naming convention so portable milo-os platform plugins (e.g. milo-ipam) are discovered on PATH alongside datumctl-<command> plugins; a bare generic name on PATH is still never treated as a plugin. Archive extraction now prefers the prefixed plugin binary and only falls back to the bare name last, so an archive that bundles a service binary sharing the bare name installs the plugin, not the service. Trust resolution and the untrusted-plugin messages handle both prefixes. Users still run the plugin as 'datumctl <command>'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
…st counts Rename the reserved official catalog from "default" to the product-facing "datum" everywhere it is displayed and addressed, while keeping "default" as a hidden alias so existing config, install records, and default/<plugin> addressing keep working. Reserve datum, default, and official so third parties cannot register them. 'plugin index list' now does a best-effort, concurrent, short-timeout refresh of any uncached catalog so plugin counts populate consistently (the official catalog shows its count without a prior search); a catalog that is genuinely unreachable shows a clear "—" marker and the listing still succeeds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
Update the OfficialCatalog doc comment to match its name after the default->datum rename, and switch the DATUMCTL_TRUSTED_PLUGINS parser to strings.SplitSeq. No functional change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwL2R5VupNJo3C35iLjZnS
Contributor
Author
|
Paired with the product vision and design rationale in datum-cloud/enhancements#783 — that proposal explains the why; this PR is a working spike of how it feels in the hand. |
Collapse the duplicate trust check in root.go onto the single shared pluginstore.IsTrusted implementation (DATUMCTL_TRUSTED_PLUGINS env var or a plugins.json trusted entry whose path + SHA256 match), removing a second divergent copy that could silently lose the empty-SHA256 fail-closed guard. Make VerifyManagedPluginIntegrity fail closed: a generic-named managed binary with no install record, an empty recorded SHA256, or an unreadable manifest is now rejected instead of being executed. Legacy datumctl-<name> installs that predate records still run, and any binary with a recorded hash is still verified. The integrity gate is now also applied on the completion path, which previously exec'd managed binaries with credentials on every tab-press with no verification. Drop t.Parallel() from the two completion tests that mutate global os.Args so the package is clean under the race detector. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Third-party catalogs control the host they serve, so manifest and archive fetches now re-validate every redirect hop: each must stay https (no TLS downgrade) and may not resolve to a loopback, private, link-local, or unspecified address (no SSRF / internal port scan). Previously only the initial URL was checked and http.DefaultClient followed cross-scheme, cross-host redirects blindly. Bound every fetch and extraction: manifests and checksums are capped, as are archive downloads, and each archive entry is size-limited during decompression. SHA256 only covers the compressed bytes, so without this a small archive could gunzip/unzip to many gigabytes and exhaust memory. The manifest cap also bounds the YAML parser against alias-expansion bombs. Create the catalog cache directory 0700 to match the rest of the store. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Route an install argument by stripping any @Version suffix first, then deciding catalog/name vs owner/repo on the remainder, so catalog/name@ver installs the pinned version from the catalog instead of being misrouted to GitHub, and a bare name@version resolves across catalogs at that version. The version is now threaded through to the install instead of dropped. A third-party 'index add' in a non-interactive shell without --yes now fails loudly instead of silently succeeding without adding the catalog, so a CI script that forgot --yes is not misled; an explicit "no" still aborts cleanly. The trust prompt's wording matches the actual hard block. Wrap command help in the shared templates helpers, align the search and list columns, keep empty-result listings on stdout, render the collision hint as aligned columns, and drop internal artifacts (plugins.json, --plugin-manifest, api_version) from user-facing help text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Installing a plugin runs the freshly downloaded binary once with --plugin-manifest to read its metadata. That probe now executes with a minimal, built-from-scratch environment (PATH, HOME, and temp-dir hints, plus the Windows equivalents) instead of inheriting the user's full environment, so Datum credentials, helper hooks, and cloud tokens are not exposed to a third-party binary during install. Using an allow-list rather than a deny-list means newly added sensitive variables stay excluded by default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tHub by owner An organization's plugin catalog allow-list was only checked when a catalog was first added, so a catalog added before a policy was published kept working forever. The allow-list is now applied every time the catalog registry loads: a catalog whose source is no longer permitted is disabled and excluded from search, browse, install, and resolution, with a clear warning explaining why. Disabled catalogs are still listed (marked disabled) and can still be removed, and the official and organization- managed catalogs are never disabled. With no allow-list configured nothing changes. Allow-list entries can now scope GitHub sources by owner or repository (github.com/<owner>/*, github.com/<owner>, or github.com/<owner>/<repo>) instead of authorizing every GitHub repository at once. A plain raw.githubusercontent.com host entry no longer blanket-authorizes GitHub, so the scoping is meaningful; local-only and non-GitHub host rules are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scotwells
marked this pull request as ready for review
June 30, 2026 23:14
…nding screen The no-argument landing now reflects the plugins a user has added: each installed plugin is listed as a runnable `datumctl <command>` verb badged with the catalog it came from, and a new "Extend datumctl" entry plus a few tips point at `datumctl plugin browse`. The plugin list is read locally and best-effort, so it never slows down or breaks the landing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 30, 2026
ecv
approved these changes
Jul 1, 2026
This was referenced Jul 1, 2026
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.
Pairs with the plugin marketplace enhancement: that PR carries the product vision and design rationale, this one is what it feels like in the hand. Best experienced hands-on — pull the branch, register a catalog, and try
search/browse/install.What this does
Today
datumctlcan install plugins from a single, Datum-operated catalog. This opens that up: anyone — a community author, an open-source project, or an enterprise platform team — can publish their own catalog, and users can register, search, browse, and install across all of them through the same experience they already know.You can drive the whole thing from the CLI:
The curated datum catalog stays the trusted, zero-configuration default — nothing changes for users who never add a catalog.
datumctl-third-party-marketplaces.mov
Why it matters
A plugin ecosystem only takes off when people other than Datum can contribute to it. This gives community authors a real front door, gives enterprises a home for internal tooling, and keeps the curated default trustworthy — so the marketplace can grow without every plugin having to flow through Datum's repo.
How it feels
validatecommand so authors can check a manifest before publishing, and allow-list guardrails that platform teams can scope down to specific publishers (including individual GitHub owners or repositories) and that keep applying as policy changes — not just at the moment a catalog is added.Try it
datumctl plugin index add acme <your-catalog>— see the one-time trust decision.datumctl plugin searchanddatumctl plugin browse— watch results span the datum catalog and yours together, each clearly badged.datumctl plugin install <name>— install across catalogs, pin a version, and see provenance indatumctl plugin list.Scope & status
The experience — command surface, trust prompts, and the search / browse / install flow — is ready for a hands-on look. The manifest format, catalog APIs, and the path to a real release are still open for discussion alongside the enhancement. One enterprise allow-list refinement (scoping GitHub publishers by owner/repo) changes how an existing allow-list is written; that will be covered in the release notes and docs when it ships.