oras-project/oras-go

Support timestamp clamping in file.Store tar packing for SOURCE_DATE_EPOCH reproducible builds

Open

#1,252 opened on Jul 24, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)Go (112 forks)auto 404
help wantedv3

Repository metrics

Stars
 (270 stars)
PR merge metrics
 (PR metrics pending)

Description

What would you like to be added?

Allow content/file.Store to pack directory tarballs with timestamps clamped to a caller-supplied time, so consumers (e.g. the oras CLI) can implement SOURCE_DATE_EPOCH-style reproducible builds.

Today the store exposes only a boolean:

// TarReproducible controls if the tarballs generated
// for the added directories are reproducible.
// When specified, some metadata such as change time
// will be removed from the files in the tarballs. Default value: false.
TarReproducible bool

(content/file/file.go)

and tarDirectory hard-zeroes the timestamps:

if removeTimes {
    header.ModTime = time.Time{}
    header.AccessTime = time.Time{}
    header.ChangeTime = time.Time{}
}

(content/file/utils.go)

This can only produce 0001-01-01 entries — it cannot express "use timestamp T" or "clamp to T", which is what the reproducible-builds.org SOURCE_DATE_EPOCH spec requires.

Proposed requirements

  1. Add a caller-supplied clamp/override time to file.Store, e.g.:

    // TarModTime, when non-nil, sets the upper bound for timestamps in tarballs
    // generated for added directories. Any entry with a modification time later
    // than TarModTime is clamped down to TarModTime; entries older than it are
    // left unchanged. Access and change times are set to the same clamped value.
    TarModTime *time.Time
    

    (Field name/shape open to discussion — a time.Time plus a bool, or an options struct, would work too.)

  2. Clamping rule (per the SDE spec — this is an upper bound, not a hard overwrite): for each entry, modTime = min(originalModTime, clamp). Entries already older than the clamp keep their real, already-reproducible time; only newer entries are rewritten. Access/change times should follow the same clamped value (or be zeroed) so they don't leak either.

  3. Backward compatibility. The existing TarReproducible bool behavior (zero all times) must be preserved for current users. Options:

    • Keep TarReproducible as-is (zero-out) and add the new clamp field as an independent, higher-fidelity alternative; or
    • Treat TarReproducible == true with no clamp set as the existing zeroing behavior, and apply clamping when the clamp field is set.

    Please pick whichever keeps the API cleanest; the constraint is only that default (false/nil) behavior is unchanged.

  4. Thread the value through tarDirectory (content/file/utils.go) so the clamp is applied per header, replacing/augmenting the current removeTimes branch.

Non-goals

  • oras-go should not read the SOURCE_DATE_EPOCH environment variable itself — that's the consuming CLI's responsibility. The library only needs to accept an explicit time. Reading env vars implicitly in a general-purpose library is surprising and out of scope.
  • The manifest created annotation already supports reproducibility: PackManifest honors a caller-supplied org.opencontainers.image.created (ocispec.AnnotationCreated) value, so no change is needed there.

Acceptance / tests

  • Packing the same directory twice with the same clamp time yields a byte-identical tarball digest, including across machines where file mtimes differ but are ≤ clamp.
  • An entry with mtime newer than the clamp is rewritten to the clamp; an entry older than the clamp keeps its original mtime.
  • Existing TarReproducible behavior is unchanged when the new field is unset.

Why is this needed?

The oras CLI wants to honor SOURCE_DATE_EPOCH for oras push / oras attach (oras-project/oras#1464, oras-project/oras#2039). The manifest side can be done entirely in the CLI, but the tarball side is blocked here: the current TarReproducible boolean can only zero timestamps, which is both semantically wrong (0001-01-01) and unable to clamp to an epoch. This enhancement unblocks a correct, spec-compliant implementation in the CLI.

Are you willing to submit PRs to contribute to this feature?

  • Yes, I am willing to implement it.

Contributor guide