Support timestamp clamping in file.Store tar packing for SOURCE_DATE_EPOCH reproducible builds
#1,252 opened on Jul 24, 2026
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
-
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.Timeplus a bool, or an options struct, would work too.) -
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. -
Backward compatibility. The existing
TarReproducible boolbehavior (zero all times) must be preserved for current users. Options:- Keep
TarReproducibleas-is (zero-out) and add the new clamp field as an independent, higher-fidelity alternative; or - Treat
TarReproducible == truewith 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. - Keep
-
Thread the value through
tarDirectory(content/file/utils.go) so the clamp is applied per header, replacing/augmenting the currentremoveTimesbranch.
Non-goals
- oras-go should not read the
SOURCE_DATE_EPOCHenvironment 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
createdannotation already supports reproducibility:PackManifesthonors a caller-suppliedorg.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
TarReproduciblebehavior 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.