Repository navigation
The registry SERVES sealed publications — GET /api/plugins/bundles/prebuilt/<identity>/<source> (#2483 part 1) - #2487
Conversation
…hat a sealed source directory may serve
…ebuilt/<identity>/<source> Step 2 of the plugin build contract: a downstream repo INSTALLS its upstream from the upstream's sealed publication. Until now the only reader of prebuilt-bundles/<identity>/<source>/ was an Azure OIDC identity whose federated credentials live in the Entra tenant — none for `pull_request`, so a gate could not fetch on the one event it exists for (AADSTS700213, measured today). The portal already mounts the share (PreWarm:PrebuiltBundleRoot). Serving it here puts the read behind the SAME authenticator and grant model as every other bundle route: who may fetch which source is a whole-source PluginGrantEntry (`<source>/*`) on a mesh node — auditable, listable, revocable — never a rule in a cloud tenant nobody can query. The decision is recorded on the entitlement ledger like every other bundle decision. Serves ONLY what the `_complete` seal lists (PublishedBundleCatalogue.SealedBundlesOf — the boot seeder's own rule, now exposed): an unsealed directory is 404, a file present on disk but not listed is 404, a segment that is not a bare name is 400 before any disk read. A consumer can never be handed a set the portal itself would decline. Pinned by PrebuiltPublicationTest: the sealed set to a whole-source grant; 403 without it; 404 on a torn directory; 404 on an unsealed file; path walk refused. This is #2483 part 1. Part 2 (the mesh verifies GitHub's OIDC token as a build principal) removes the instance key too; part 1 already removes Azure from the gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Test Results (shard 3)1 001 tests 997 ✅ 17m 8s ⏱️ Results for commit 1856a30. |
Test Results (shard 5)4 483 tests 4 377 ✅ 10m 11s ⏱️ Results for commit 1856a30. |
Test Results (shard 2)1 483 tests 1 483 ✅ 6m 19s ⏱️ Results for commit 1856a30. |
Test Results (shard 0)962 tests 962 ✅ 6m 33s ⏱️ Results for commit 1856a30. |
Test Results (shard 4) 10 files 10 suites 8m 37s ⏱️ Results for commit 1856a30. |
Test Results (shard 1) 8 files 8 suites 7m 39s ⏱️ Results for commit 1856a30. |
Test Results 52 files 52 suites 56m 28s ⏱️ Results for commit 1856a30. |
There was a problem hiding this comment.
Pull request overview
Adds registry endpoints to serve sealed prebuilt plugin publications from the portal (list bundles + download a bundle) under the existing instance-key authentication and whole-source grant model, so downstream gates can fetch publications without Azure Files credentials.
Changes:
- Adds
GET /api/plugins/bundles/prebuilt/{identity}/{source}and.../{bundle}endpoints and wires them intoMapPluginBundles. - Exposes
PublishedBundleCatalogue.PublishedRootConfigKeyandSealedBundlesOf(...)as the shared “only serve what is sealed” rule. - Introduces
PrebuiltPublicationTestto pin authorization + sealed/torn behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| test/Memex.Portal.Shared.Test/PrebuiltPublicationTest.cs | Adds an integration test covering sealed-set listing/download, grant enforcement, and torn/unsealed refusal. |
| src/MeshWeaver.PluginCatalog/PublishedBundleCatalogue.cs | Adds a config-key alias and exposes SealedBundlesOf(...) to reuse the boot seeder’s “sealed publication” rule. |
| memex/Memex.Portal.Shared/Api/PluginBundleEndpoints.cs | Adds and maps the new /prebuilt/... endpoints enforcing whole-source grants and seal-based serving. |
Suppressed comments (1)
memex/Memex.Portal.Shared/Api/PluginBundleEndpoints.cs:264
IsBareNamecurrently only rejects '/', '\', ':' and '.'/'..'. That still allows segments containing percent-escapes (e.g...%2Fplugins) and other invalid filename characters, which can weaken the "refuse before any disk read" guarantee and can cause unexpected path/IO exceptions. Consider rejecting%and anyPath.GetInvalidFileNameChars().
private static bool IsBareName(string segment) =>
segment.Length > 0
&& segment.IndexOfAny(['/', '\\', ':']) < 0
&& segment != "." && segment != "..";
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /// <summary> | ||
| /// The bundle names one source's SEALED publication lists, or null when the publication is | ||
| /// torn (no sentinel, or a listed bundle absent). This is THE rule for "what may be served | ||
| /// from this directory" — the registry's prebuilt route reads through it so a consumer can | ||
| /// never be handed a torn set the boot seeder itself would refuse. |
| if (PrebuiltDecision(http, identity, source, caller) is { } refused) | ||
| return refused; | ||
| var directory = PrebuiltDirectory(http, identity, source); | ||
| var sealed_ = directory is null ? null : PublishedBundleCatalogue.SealedBundlesOf(directory, Log(http)); | ||
| if (sealed_ is null || !sealed_.Contains(bundle, StringComparer.OrdinalIgnoreCase)) | ||
| return NoSuchBundle(); | ||
| var path = Path.Combine(directory!, bundle); | ||
| return Results.File(path, "application/zip", fileDownloadName: bundle); |
| // and a path segment that is not a bare name is refused before any disk read | ||
| var walk = await Get(app, $"/api/plugins/bundles/prebuilt/{Identity}/..%2F{Source}", grantedKey); | ||
| Assert.NotEqual(HttpStatusCode.OK, walk.StatusCode); |
Stacked on #2478 (base branch = its branch; retarget to
mainonce it lands). Closes the Azure half of #2483.What
GET /api/plugins/bundles/prebuilt/{identity}/{source}→ the sealed publication's bundle names.GET /api/plugins/bundles/prebuilt/{identity}/{source}/{bundle}→ the bundle.Behind the same authenticator and grant model as every other bundle route: the caller needs a whole-source grant (
<source>/*) — aPluginGrantEntryon a mesh node, auditable and revocable — and the decision lands on the entitlement ledger like every other bundle decision.Why
The only reader of
prebuilt-bundles/<identity>/<source>/was an Azure OIDC identity whose federated credentials live in Entra, none forpull_request— so a gate cannot fetch on the one event it exists for (AADSTS700213, measured this morning on SocialMedia#84 / Reinsurance#100). The portal already mounts the share; serving it moves the rule into the mesh.Serves only what is sealed
PublishedBundleCatalogue.SealedBundlesOf— the boot seeder's own torn-publication rule, now exposed. Unsealed directory → 404. File on disk but not in the seal → 404. Segment that is not a bare name → 400 before any disk read.Verified
PrebuiltPublicationTestpins all of it: sealed set to a whole-source grant, 403 without it, 404 on a torn directory, 404 on an unsealed file, path walk refused. The 25 existing bundle-endpoint tests are unchanged and green (MapPrebuiltsits inside the shared 401 filter).Next
#2481 rewires
upstream-seedfromaz storageto this route with an instance key — Azure leaves the gate entirely. Part 2 of #2483 (the mesh verifies GitHub's OIDC token directly) then removes the key too.🤖 Generated with Claude Code