Skip to content

feat(gallery): galleries published as OCI artifacts - #12167

Merged
mudler merged 5 commits into
masterfrom
feat/oci-galleries
Sep 21, 2026
Merged

mudler merged 5 commits into
masterfrom
feat/oci-galleries

Conversation

@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator

A gallery can now live in a container registry: url: oci://host/repo:tag.
LocalAI pulls the artifact, verifies the publisher signature before unpacking
anything, and reads index.yaml from the unpacked tree. Entries may refer to
their base configuration by a path inside the same artifact, so a gallery and
the configs it needs travel as one object.

This is what makes a self-contained, privately distributed gallery possible.
Today an index must be a URL on a web server and every entry must name an
absolute URL, so a gallery cannot be shipped as a unit.

What is here

  1. pkg/oci.PullArtifact: a generic ORAS artifact pull that lays layers out
    by their org.opencontainers.image.title. A registry is remote input and
    this writes files, so the whole manifest is validated before a byte is
    fetched: a title that is absolute, empty or escapes the destination is
    refused, an unexpected artifactType is refused, and layer count and total
    size are capped. Blobs are size-limited and digest-verified while being
    written, so a lying registry cannot exceed the bound.
  2. oci:// gallery fetch, with mirrors, cooldown and last-known-good intact.
    With a verification policy: resolve the tag to a digest, verify the
    signature on that digest, pull that same digest. The pull stages in a temp
    directory and is renamed into the cache only once the tree is complete and
    the index parses, so an interrupted pull leaves nothing a later fetch would
    serve. Under LOCALAI_REQUIRE_BACKEND_INTEGRITY an oci:// gallery with
    no policy is refused rather than warned about, which is what the flag
    already does for backend installs.
  3. Relative entry URLs, resolved against the gallery root (the unpacked
    artifact for oci://, the index URL's directory otherwise) and confined to
    it by the same guard the puller uses. An entry that climbs out is dropped
    with a logged reason rather than failing the whole gallery.

Behaviour change to be aware of

Strict integrity mode now refuses an oci:// gallery without a verification
block at listing time, not only at install. It is called out in a warning
box in the docs.

Tests

Ginkgo, against an in-process registry throughout: the tree with subdirectories
and its digest, .. and absolute and missing titles, wrong artifactType, size
and layer caps, index read, cache hit with zero registry traffic, an
unverifiable artifact refused with nothing cached and zero blob fetches,
verification pinned to the digest rather than the tag, strict-mode refusal, a
failed pull leaving nothing servable, relative entries resolving for OCI and
HTTP galleries, and an escaping entry dropped.

Signature verification sits behind a test seam so the suite does not reach the
public Sigstore mirror. An end-to-end check against a really signed artifact is
worth doing once #12165 lands, since that PR fixes the discovery path this
would exercise.

Docs updated in the same change: docs/content/features/model-gallery.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_012ELNaYBdvugwkePWCQokNX

Galleries published to an OCI registry arrive as an ORAS artifact whose
layers carry their tree path in org.opencontainers.image.title. Nothing in
pkg/oci could read a non-image manifest, so add PullArtifact: it lays the
layers out under a destination directory and returns the manifest digest so
callers can pin and verify signatures on it.

A registry is remote input and this writes files, so the manifest is
validated before any byte is fetched. A title that is absolute, escapes the
destination or is missing is refused, an artifact of an unexpected
artifactType is refused, and both the layer count and the total size are
capped with caller-settable limits.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-5 [Claude Code]
A gallery whose URL is oci://host/repo:tag is now pulled as an ORAS
artifact and its index.yaml read from the unpacked tree, alongside the
existing HTTP and file sources. Mirrors, the per-candidate timeout, the
failure cooldown and the last known good copy work the same for both
schemes, so a gallery can mirror an OCI primary with an HTTP fallback.

When the gallery declares a verification policy the artifact reference is
resolved to its manifest digest, the publisher signature is checked
against that digest, and only then is the same digest pulled. Nothing
unverified is written to disk or parsed. With strict integrity mode on,
an OCI gallery without a verification policy is refused instead of
warned about, which is what the flag already does for backend installs.

The pull lands in a staging directory that is renamed into the cache only
once the whole tree is on disk and the index reads back as an index, so a
failed or interrupted pull leaves nothing a later fetch would serve. A
fetch that finds a fresh cache entry does not contact the registry.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-5 [Claude Code]
A gallery published as a self-contained tree, which is what an OCI
gallery is, names its base configs by their place in that tree. Such a
url reached the HTTP client verbatim and no entry of the gallery could
be installed.

A scheme-less entry url now resolves against the root the index came
from: the unpacked artifact for an oci:// gallery, and the directory of
the index URL otherwise. The relative path may not climb out of that
root, reusing the guard the artifact puller already applies to layer
titles, and an entry that does is dropped from the listing rather than
failing the whole gallery. An entry that names its own scheme is
untouched, so .ref indirection and local github: resolution behave as
before.

Reading an entry of an OCI gallery also needs the unpacked artifact
directory as its trusted root, since the cache is deliberately a sibling
of the models directory the downloader otherwise confines a file read
to.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:claude-opus-5 [Claude Code]
Comment thread pkg/oci/artifact.go Fixed
Comment thread pkg/oci/artifact.go Fixed
Create artifact files with owner-only permissions to satisfy G302.
Document the existing path validation for the G304 scanner finding.
Check the permissions of downloaded layers in the artifact test.

Assisted-by: Codex:gpt-6 gosec
Use root-relative filesystem operations to reject escapes through existing
directory symlinks. Retain owner-only permissions and add a symlink escape
regression test, without suppressing the path traversal finding.

Assisted-by: Codex:gpt-6
@mudler

mudler commented Sep 21, 2026

Copy link
Copy Markdown
Owner

@localai-org-maint-bot prepare a followup so our CI publishes as well galleries, and let's add it as a fallback

@mudler
mudler merged commit 237cf45 into master Sep 21, 2026
70 of 71 checks passed
@mudler
mudler deleted the feat/oci-galleries branch September 21, 2026 07:23
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator Author

Prepared the follow-up in #12182. It publishes signed model and backend gallery artifacts, bundles repository-local base configurations, and adds OCI after the existing HTTP and GitHub fallbacks. Gallery artifact verification stays separate from backend image verification. Targeted config, gallery, and packaging tests plus go vet pass. Registry publication remains untested locally; the workflow uses the existing Quay repository and credentials.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants