Current section
Files
Jump to
Current section
Files
README.md
# Regc
Regc validates OCI and Docker image metadata without pulling image layers.
It can check whether a tag points to valid manifest content, list the platforms
advertised by a multi-platform image, resolve one exact platform, and retrieve
its verified image configuration.
## Installation
Add `regc` to the dependencies in `mix.exs`:
```elixir
def deps do
[
{:regc, "~> 0.0.1"}
]
end
```
## Check a tag
Platform selection is optional. This makes the default safe for checking a
list of tags whose platforms are not known in advance:
```elixir
case Regc.check_image("registry.example.com/team/app:1.2.3") do
{:ok, image} ->
Enum.map(image.platforms, fn platform ->
{platform.os, platform.architecture, platform.variant}
end)
{:error, error} ->
{error.stage, error.code, error.message}
end
```
For an image index, this request fetches and validates only the root manifest.
It does not choose a child based on the machine running Regc:
```elixir
image.platform # => nil
image.selected_ref # => nil
image.manifest # => nil
image.config_descriptor # => nil
image.layer_descriptors # => []
```
The root metadata remains available through `image.root_ref` and
`image.root_manifest`.
If the tag points directly to a single-image manifest, that manifest is
selected automatically. Its platform is not known until its configuration is
inspected.
## Select a platform
Pass an OCI platform string to resolve an index to an exact image manifest:
```elixir
{:ok, image} =
Regc.check_image("registry.example.com/team/app:1.2.3",
platform: "linux/amd64"
)
image.root_ref.digest
image.selected_ref.digest
image.config_descriptor.digest
Enum.map(image.layer_descriptors, & &1.digest)
```
Platforms have the form `os/architecture` or
`os/architecture/variant`, such as `linux/amd64` or `linux/arm/v7`.
When an index distinguishes images by additional OCI platform qualifiers,
pass a map so selection remains exact:
```elixir
Regc.check_image(reference,
platform: %{
os: "windows",
architecture: "amd64",
os_version: "10.0.20348.2402",
os_features: ["win32k"]
}
)
```
The map also accepts `:variant` and `:features`. CPU `:features` can be
verified only when selecting a descriptor from an image index; requesting
them for a direct image manifest returns `:platform_features_unverifiable`.
If multiple manifests match the base OS and architecture but differ by
qualifiers, Regc returns `:ambiguous_platform` instead of choosing one
arbitrarily.
Use `platform: :local` only when selecting the OS and architecture of the
runtime machine is intentional:
```elixir
Regc.check_image(reference, platform: :local)
```
## Inspect the image configuration
`inspect_image/2` fetches, verifies, and decodes the selected image
configuration:
```elixir
{:ok, image} =
Regc.inspect_image("registry.example.com/team/app:1.2.3",
platform: "linux/amd64"
)
image.platform
image.config.created
image.config.os
image.config.architecture
image.config.raw["config"]["Cmd"]
```
An explicit platform is required when the tag points to an image index.
For a direct single-image manifest, the platform can be omitted; Regc reads it
from the verified configuration.
If only the creation timestamp is needed:
```elixir
{:ok, created} =
Regc.image_created("registry.example.com/team/app:1.2.3",
platform: "linux/amd64"
)
```
## Platform behavior
| Operation | Direct image manifest | Image index |
| --- | --- | --- |
| `check_image/2` without a platform | Selects the manifest; platform remains unknown | Validates the root and reports `image.platforms` |
| `check_image/2` with a platform | Selects the manifest; platform remains unknown until inspection | Resolves the matching child |
| `inspect_image/2` without a platform | Inspects the config and infers the platform | Returns `:platform_required` |
| `inspect_image/2` with a platform | Inspects and validates the config platform | Resolves, inspects, and validates the matching child |
## Errors
All public functions return `{:ok, result}` or
`{:error, %Regc.Oci.Error{}}`. Errors include the failing stage, a stable code, a
human-readable message, and whether retrying may succeed:
```elixir
case Regc.check_image(reference) do
{:ok, image} ->
image
{:error, error} ->
%{
stage: error.stage,
code: error.code,
message: error.message,
retryable?: error.retryable?
}
end
```
## References and transport
Docker-style shorthand references are expanded using Docker Hub defaults:
```text
redis:latest -> docker.io/library/redis:latest
bitnami/redis:7 -> docker.io/bitnami/redis:7
```
References must include a tag or SHA-256 digest. Registry-qualified references
remain supported:
```text
registry.example.com/team/app:1.2.3
registry.example.com/team/app@sha256:0123456789abcdef...
```
HTTPS is the default. Plain HTTP is accepted automatically for local
development registries:
```elixir
Regc.check_image("http://localhost:5000/team/app:dev")
```
For any other plaintext registry, both the `http://` scheme and
`allow_insecure: true` are required.
Redirects are followed within a bounded request chain. HTTPS targets are
accepted; plaintext targets still require `allow_insecure: true`, except when
both endpoints are local development hosts. Authorization, proxy
authorization, and cookie headers are removed when the redirect changes
origin. Redirects from public hosts to targets that resolve to non-global
addresses, including private, loopback, link-local, and special-use ranges,
are rejected, and the validated target addresses are pinned to the redirected
connection.
## Options
| Option | Default | Purpose |
| --- | --- | --- |
| `:platform` | omitted | Select `os/architecture[/variant]`, a qualifier map, or `:local` |
| `:max_manifest_bytes` | 4 MiB | Limit each downloaded manifest |
| `:max_config_bytes` | 4 MiB | Limit the downloaded image configuration |
| `:max_index_depth` | `8` | Limit nested index traversal during platform resolution |
| `:max_index_manifests` | `64` | Limit child manifests fetched while resolving nested indexes |
| `:max_redirects` | `5` | Limit HTTP redirects followed for one metadata request |
| `:connect_timeout` | 5 seconds | Limit connection establishment time |
| `:timeout` | 15 seconds | Limit total HTTP response time, including redirects |
| `:allow_insecure` | `false` | Permit explicit plain HTTP for a non-local registry |
| `:resolver` | built-in DNS | Use an arity-2 DNS resolver function |
| `:transport` | built-in HTTP | Use an arity-1 request function or a module implementing `Regc.Oci.Transport` |
| `:transport_options` | `[]` | Pass a keyword list to a transport module through `Regc.Oci.Options` |
### Custom transports
An arity-1 transport function receives a `%Regc.Oci.Transport.Request{}` and
returns `{:ok, response}` or `{:error, reason}`. A successful response can be a
`%Regc.Oci.Transport.Response{}` or a map with `:status`, `:headers`, and
`:body` fields.
For a reusable transport with configuration, implement the
`Regc.Oci.Transport` behaviour:
```elixir
defmodule MyTransport do
@behaviour Regc.Oci.Transport
alias Regc.Oci.{Options, Transport}
alias Regc.Oci.Transport.{Request, Response}
@impl Transport
def request(%Request{} = request, %Options{} = options) do
client = Keyword.fetch!(options.transport_options, :client)
# Perform one GET request with client, respecting request.max_body_bytes.
{:ok, %Response{status: 200, headers: %{}, body: "..."}}
end
end
Regc.check_image(reference,
transport: MyTransport,
transport_options: [client: client]
)
```
The resolver function receives `(host, family)`, where `family` is `:inet` or
`:inet6`, and returns `{:ok, addresses}` or `{:error, reason}`. Custom
transports that follow redirects or resolve hosts independently are responsible
for equivalent timeout, redirect, TLS, and network-target protections.
## Verification
Regc verifies:
- requested, registry-provided, and parent-descriptor manifest digests;
- parent-declared manifest sizes;
- image-config digest and size;
- media types and required manifest/config structure;
- exact platform selection and, during inspection, config-platform agreement when requested; and
- configured response-size and nested-index limits.
## Current scope
Supported:
- anonymous HTTPS registry access, including OCI bearer-token challenge flows
used by Docker Hub and GHCR;
- OCI image indexes and image manifests;
- Docker manifest lists and schema-2 image manifests;
- nested image indexes;
- SHA-256 content verification; and
- verified image-config retrieval, including inline config data.
Not currently supported:
- registry pulls requiring user-supplied credentials;
- image-layer downloads; or
- digest algorithms other than SHA-256.
## Development
The project targets Elixir `~> 1.20` and has no runtime package dependencies.
```shell
mix test
mix format --check-formatted
mix compile --warnings-as-errors
```