Skip to content

fix(oci): resolve artifact created time from image config - #6520

Merged
evankanderson merged 2 commits into
mindersec:mainfrom
somaz94:fix/oci-created-timestamp
Jun 23, 2026
Merged

fix(oci): resolve artifact created time from image config#6520
evankanderson merged 2 commits into
mindersec:mainfrom
somaz94:fix/oci-created-timestamp

Conversation

@somaz94

@somaz94 somaz94 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

GetArtifactVersions fell back to time.Now() when an image manifest lacked the
org.opencontainers.image.created annotation (a // FIXME: This is a hack), so
artifacts without that annotation were recorded as "created right now", silently
breaking age-based filtering and lifecycle policies.

This reads the real build time from the image config blob instead: it prefers the
manifest annotation and, when absent, falls back to the image config's Created
field (img.ConfigFile().Created). A zero or Unix-epoch timestamp is a legitimate
reproducible-build value and is preserved as-is rather than overwritten with the
current time.

The created-date resolution is extracted into a pure helper (resolveCreatedAt) so
it is unit-testable without a registry, and a getImage helper lets the same fetched
image serve both the manifest and the config lookup.

Note: for a multi-arch image index lacking the annotation, the config now resolves to
the default platform's build time (or surfaces a config-read error) where the old hack
always returned time.Now().

Fixes #6490

Testing

  • Added internal/providers/oci/oci_test.go: table-driven unit test for resolveCreatedAt
    (annotation present / invalid / absent→config / epoch preserved / zero preserved / config error).
  • go test ./internal/providers/oci/... — pass
  • go vet ./internal/providers/oci/... — clean
  • golangci-lint run ./internal/providers/oci/... — 0 issues

@CLAassistant

CLAassistant commented Jun 18, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

somaz94 added a commit to somaz94/somaz94 that referenced this pull request Jun 18, 2026
@somaz94
somaz94 marked this pull request as ready for review June 18, 2026 05:51
@somaz94
somaz94 requested a review from a team as a code owner June 18, 2026 05:51
@krrish175-byte

Copy link
Copy Markdown
Member

This is a much better approach than the previous time.Now() fallback. I really like how you extracted resolveCreatedAt to be testable without a registry, and passing img.ConfigFile as a getter to defer the config blob network call until it's actually needed is a very clean, idiomatic design. LGTM.

cc @evankanderson

@somaz94

somaz94 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @krrish175-byte! 🙏

Heads-up on CI for whoever picks this up: the test / Unit testing failure is unrelated to this change. The internal/providers/oci package (the only one this PR touches) passes cleanly — the 4 failures are pre-existing flakes elsewhere:

  • internal/events/natsTestNatsChannel: timing-based (timeout waiting for message 2, expected 4 messages, got 2)
  • internal/providers/github/webhookTestHandleGitHubWebHook/team_removed_from_repository: Expected value not to be nil

A re-run of that job should go green. Happy to rebase if preferred. cc @evankanderson

@coveralls

coveralls commented Jun 18, 2026

Copy link
Copy Markdown

Coverage Status

Coverage is 60.713%somaz94:fix/oci-created-timestamp into mindersec:main. No base build found for mindersec:main.

Comment thread internal/providers/oci/oci.go Outdated
Comment on lines +239 to +240
// configuration, which records the real build time. Unlike the previous
// time.Now() fallback, the resolved value always reflects the image itself: a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, it doesn't make sense to have comments referencing code that no longer exists after this PR.

The one exception (which this does not fall into) is if the current code has to maintain a certain behavior when working with data that might have been produced by old code. An example of this might be // older code did not fill in Foo, even though that's now enforced by Validate()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, removed. The doc comment no longer references the old time.Now() fallback, it just describes the current behavior (annotation first, then the image config Created field, with zero/epoch preserved). Pushed in 30c3425.

return createdAt, nil
}

cfg, err := configFile()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous code had comments about handling multi-architecture images. Have you tested this with an image like ghcr.io/mindersec/minder/server:latest?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested with ghcr.io/mindersec/minder/server:latest, which is a 2-platform index (linux/amd64 + linux/arm64).

remote.Image resolves the index down to a single-platform image (amd64 by default), so img.Manifest() and img.ConfigFile() both run against that concrete child image, not the index. That is the per-architecture config the old FIXME was after.

For that image the created annotation is present, so it returns 2026-04-23T11:01:57Z from the annotation. With the annotation missing it would read Created from the resolved child config. Same behavior on alpine:latest.

@evankanderson evankanderson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking that codepath! It looks like we'll probably throw an error for multi-architecture images that don't include amd64/linux, but I think that still puts us in a better position than today. (And we can find this conversation in the commit history if we wonder why we stopped at this point....)

@evankanderson

Copy link
Copy Markdown
Member

The security scan is failing on unrelated (new) Go x/crypto CVEs fixed in #6537, I'll ignore (bypass) that check.

Nice fix, very clean, just waiting for the rest of CI to go green.

@evankanderson
evankanderson merged commit 62679e9 into mindersec:main Jun 23, 2026
26 of 27 checks passed
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.

Fetch OCI image config to reliably determine artifact creation dates instead of relying on time.Now() fallback

5 participants