My First Open Source Contribution: Fixing Grype OCI Scans in Artifact Keeper
Author
Ramy Bouchareb
Date Published

Introduction
I recently made my first open source contribution, and it started from something very practical: I was building a Proof of Concept around Artifact Keeper to evaluate how it could fit into the infrastructure at my job.
While testing the flow for storing and scanning OCI/Docker images, I ran into a scan failure that did not look like a local setup issue. After digging into it, I found that the behavior matched an existing open issue in Artifact Keeper.
The issue was reported as artifact-keeper/artifact-keeper#2053 . Since I was already trying to understand the problem for the PoC, I decided to contribute a fix upstream instead of only working around it locally.
In short, Grype scans for Docker/OCI images stored inside Artifact Keeper could fail with an authentication error.
The interesting part was not just the error itself. The fix required understanding how Artifact Keeper stores OCI images, how Grype accepts scan targets, and why routing a scanner through the same authenticated registry endpoint used by external clients can be fragile.
The Problem
Artifact Keeper can store OCI/Docker images in a local Docker repository. For example, an image might be pushed as:
localhost:8080/docker-local/alpine:3.20
When Artifact Keeper triggered a Grype scan for that image, the scanner was invoked with a registry target similar to:
registry:localhost:8080/docker-local/alpine:3.20
That caused Grype to make an HTTP request back into Artifact Keeper's registry API:
GET /v2/docker-local/alpine/manifests/3.20
But that endpoint requires authentication. Since Grype was not given Artifact Keeper credentials for that internal registry request, the scan failed with:
UNAUTHORIZED: authentication required
Why This Was Subtle
At first glance, it looks like an authentication bug: "just pass credentials to Grype."
But that is not necessarily the best fix.
Artifact Keeper already owns the image and stores the manifest and blobs locally. Making Grype call back into Artifact Keeper's authenticated registry API means the scanner is treated like an external client, even though the data is already available inside the system.
That creates several problems:
- It couples internal scanning to public registry authentication behavior.
- It requires some form of scanner credential handling.
- It risks leaking or logging sensitive tokens if implemented carelessly.
- It fails even though the artifact is already present in local storage.
The better direction was to avoid the internal registry route when possible.
How Grype Can Scan Images
Grype supports several source types. Some common examples are:
registry:<image>
oci-dir:<path>
oci-archive:<path>
docker-archive:<path>
sbom:<path>dir:<path>
The existing path used registry:<image>, which forced Grype through the authenticated /v2/... endpoint.
The fix direction was to use:
oci-dir:<path>
That means Artifact Keeper can build a temporary local OCI image layout in the scan workspace, then point Grype at that local directory.
OCI Layout in Brief
An OCI image layout is a filesystem representation of an image. At a high level, it looks like this:
oci-layout
index.json
blobs/
sha256/
<manifest-digest>
<config-digest> <layer-digest>
For Grype, this is enough to inspect the image without pulling it from a registry.
Suggested diagram: registry scan path versus local OCI layout scan path.
Reproducing the Bug
The reproduction flow was:
- Start Artifact Keeper from current source using Docker Compose.
- Create a local Docker repository named
docker-local. - Push a small image, such as
alpine:3.20. - Trigger a Grype scan for the stored artifact.
- Watch backend logs for Grype errors.
The failing log looked like this:
Grype OCI registry scan target: registry:localhost:8080/docker-local/alpine:3.20
GET /v2/docker-local/alpine/manifests/3.20UNAUTHORIZED: authentication required
That confirmed the scanner was going through the internal registry endpoint and hitting auth.
The Fix Direction
The fix was to materialize a temporary OCI layout in the scan workspace.
Instead of this:
Grype -> registry:localhost:8080/docker-local/alpine:3.20
-> Artifact Keeper /v2/... endpoint -> authentication failure
Artifact Keeper now prefers this:
Artifact Keeper storage -> temporary OCI layout -> Grype oci-dir:<path>
The flow becomes:
- Detect that the artifact is an OCI image.
- Find the manifest digest.
- Look up referenced config and layer blobs from Artifact Keeper's OCI metadata tables.
- Write a temporary OCI layout into the scan workspace.
- Invoke Grype with
oci-dir:<layout-path>. - Clean up the temporary workspace.
- Fall back to
registry:only when local blob metadata is genuinely unavailable.
Key Implementation Pieces
The first implementation focused on single-arch image manifests.
The important code path was in:
backend/src/services/grype_scanner.rs
The scanner now builds a temporary layout containing:
oci-layoutindex.json- the image manifest blob
- referenced config blobs
- referenced layer blobs
The local blob references come from tables such as:
manifest_blob_refsoci_blobs
The scanner target also needed access to repository-local context:
backend/src/services/scanner_service.rs
ScanTarget was extended so scanners can inspect local artifact state without going back through the public registry route.
Multi-Arch Images: The Important Follow-Up
The maintainer review caught an important real-world case: multi-arch images.
Images like alpine:3.20 are commonly stored as image indexes. An image index points to one or more platform-specific child manifests.
That matters because manifest_blob_refs intentionally has no rows for index manifests. The actual config and layer blobs belong to the platform child manifest.
So for an image index, the scanner needs to:
- Resolve the index to a platform child manifest.
- Load the child manifest body.
- Build the local OCI layout from the child manifest's blob references.
- Point
index.jsonat the child digest.
The maintainers pushed a follow-up commit on top of my branch to close that multi-arch gap while keeping my commits intact.
This was a useful lesson: a fix can be correct for a narrow reproduction and still miss the common production case.
Validation
The final behavior was validated against real Grype.
For the fixed flow, backend logs showed:
Grype OCI local layout scan target: oci-dir:/scan-workspace/grype-oci/<artifact-id>
Grype OCI local layout scan complete for alpine:3.20Scan grype completed
Most importantly, the logs no longer showed:
registry:localhost:8080/docker-local/alpine:3.20UNAUTHORIZED: authentication required
The maintainers also verified:
- single-arch image scans complete through
oci-dir: - multi-arch index scans complete through
oci-dir: - registry fallback still exists when local metadata is unavailable
- formatting, clippy, tests, and duplication checks pass
What I Learned
This contribution taught me a lot about how real open source work happens.
Some of the biggest lessons:
- Reproducing the bug is as important as writing the fix.
- The best fix is often not the most obvious one.
- Internal service flows should avoid unnecessary auth boundaries when the data is already local.
- Tests need to cover the real production shape, not only the simplest case.
- Maintainer review can improve both correctness and code quality.
I also got more practice with:
- Rust service code
- OCI image storage concepts
- Docker registry behavior
- Grype scanner sources
- SQL-backed metadata lookup
- CI feedback loops
Final Thoughts
This was my first open source contribution, and it was a good one because it was not just a typo fix or documentation change. It involved understanding a real bug, reproducing it, testing a fix, and iterating with maintainers.
The final solution helps Artifact Keeper scan locally stored OCI images more reliably by avoiding unnecessary authenticated registry calls and using a local OCI layout instead.
Thanks to the Artifact Keeper maintainers for the review, guidance, and follow-up work to get the fix ready for the 1.2.3 milestone.
Useful Links
- Artifact Keeper repository
- Issue #2053: Grype OCI scan fails because internal registry endpoint requires authentication
- Grype repository
- OCI Image Layout Specification

How pod QoS classes, the scheduler's blind spot, and vertical autoscaling work together illustrated with real overcommitment data from a prod cluster.