Commit 4934c9e
oci/plugins: Phase 1 OCI artifact package for plugins (THV-0077) (#136)
* oci/plugins: add Phase 1 OCI artifact package for plugins
Add the `oci/plugins` package mirroring `oci/skills` file-for-file,
built on the shared `oci/artifact` primitives from Phase 0. It packages
a Claude Code plugin directory (`.claude-plugin/plugin.json` bundling
commands, agents, skills, hooks, MCP/LSP server configs) into a
reproducible, content-addressable multi-platform OCI artifact, and
provides ORAS push/pull and a local store rooted at `toolhive/plugins`.
- mediatypes.go: ArtifactTypePlugin "dev.toolhive.plugins.v1";
dev.toolhive.plugins.* labels/annotations including the plugin-specific
.components inventory and .requires; PluginConfig +
PluginConfigFromImageConfig.
- interfaces.go: RegistryClient (Push/Pull), PluginPackager.
- packager.go: index -> per-platform manifest -> config + single
plugin.tar.gz layer, SOURCE_DATE_EPOCH-aware for reproducible digests.
- registry.go: ORAS push/pull + Docker credential auth.
- store.go: local OCI store under xdg.DataHome.
- mocks/ via go:generate mockgen.
Tests cover the exit gates (packager determinism, config round-trip,
store put/get) plus end-to-end package -> push -> pull -> extract
integration tests. Coverage 73.6%.
Part of stacklok/toolhive#5525
Closes #131
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* oci/plugins,skills: address review feedback (security + determinism)
Addresses jhrozek's review on PR #136.
Blockers (plugins):
- B1: validate digest before deriving blob path in Store.deleteBlob, plus a
store-root prefix guard, preventing path traversal / arbitrary file deletion.
- B2: Lstat + size-check the plugin manifest before reading it, rejecting a
symlinked manifest (TOCTOU) and avoiding oversized allocation.
- B3: Pull tags the local store under the full OCI reference instead of the
bare tag, preventing cross-plugin tag collisions in the shared store.
Other findings (plugins):
- validatePluginDir: absolute-path guard (the post-Clean ".." check was inert).
- fetchContent: bound local reads with io.LimitReader(MaxBlobSize).
- gzip member header now honours opts.Epoch.
- normalize zero-value PackageOptions.Epoch so config/tar/gzip agree.
- maxPluginTotalSize lowered to 95 MB so tar overhead can't exceed the
extraction-time MaxDecompressedSize limit.
- preserve file modes through packaging (executable scripts stay executable).
- retry.DefaultClient for transient registry errors.
- DeleteBuild/DeleteTag/Tag serialized under a mutex.
- json.Marshal of OCI labels/annotations now panics on the (invariant) error.
Tests (plugins): ported TestStore_DeleteBuild, added an index-branch case,
a deleteBlob invalid-digest guard, and a file-mode preservation test.
Coverage 73.6% -> 81.7%.
Parity fixes applied to oci/skills for findings marked "same in skills":
B1, B3, fetchContent bound, gzip epoch, total-size limit, retry client.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>1 parent ddbfe47 commit 4934c9e
19 files changed
Lines changed: 3735 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| 77 | + | |
77 | 78 | | |
78 | 79 | | |
79 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
0 commit comments