Visitar URL original
fix(chart): normalize StampModTimes timestamp to UTC/truncate + Chart.lock reproducibility test by anurag6569201 · Pull Request #9 · ShipWright-Review/helm · GitHub
Skip to content

fix(chart): normalize StampModTimes timestamp to UTC/truncate + Chart.lock reproducibility test - #9

Open
anurag6569201 wants to merge 1 commit into
qa/agent-helm-helm/pr-09-32485/basefrom
qa/agent-helm-helm/pr-09-32485/head
Open

anurag6569201 wants to merge 1 commit into
qa/agent-helm-helm/pr-09-32485/basefrom
qa/agent-helm-helm/pr-09-32485/head

Conversation

@anurag6569201

Copy link
Copy Markdown

Closes helm#32396

What this PR does

Chart.StampModTimes (added in helm#32162) assigns the epoch time as-is. This PR adds a t.UTC().Truncate(time.Second) normalization step before stamping.

Why it matters

The two normalizations serve distinct purposes:

  • .UTC() is the actual reproducibility fix: a time.Time with a non-UTC location produces a generated: value with a timezone offset (e.g. "2023-11-14T22:13:20+03:00") that varies across machines even when the same epoch value is passed.
  • .Truncate(time.Second) enforces SOURCE_DATE_EPOCH semantics: the spec defines the epoch as an integer number of seconds, so an SDK caller passing a sub-second time.Time would produce a generated: timestamp that does not match that integer. It also keeps the YAML generated: value and the tar entry modtime aligned, since tar headers are second-granularity.

Example without this fix:

# generated: "2023-11-14T22:13:20+03:00"  <- timezone-dependent, non-reproducible

With this fix:

# generated: "2023-11-14T19:13:20Z"  <- always UTC, always reproducible

Changes

pkg/chart/v2/chart.go - normalize t before stamping:

func (ch *Chart) StampModTimes(t time.Time) {
    t = t.UTC().Truncate(time.Second)
    ...
}

pkg/action/testdata/charts/chart-with-lock/ - new test fixture: a minimal chart with a Chart.lock whose generated: predates any SOURCE_DATE_EPOCH.

pkg/action/package_test.go - TestRunWithSourceDateEpochStampsLockGenerated: packages the fixture with SourceDateEpoch set to a non-UTC, sub-second time.Time and asserts:

  1. The Chart.lock tar entry modtime equals the normalized epoch.
  2. The marshaled generated: field in the YAML body equals the normalized UTC timestamp.

Notes

This is the follow-up scoped to exactly the two items from helm#32396. Everything else from the original helm#32173 is already present in main via helm#32162.

Source merge-base: 7e641d30a9355b43a9729d04c2771215ed926899
Source head: e92316751375bafd4621232bb39a1238a22e91c4

@shipwright-agent

Copy link
Copy Markdown

✅ Shipwright · Approve

Recommendation: approve PR #9 · Tier T1
Checks: 0 total · 0 needing attention

Next step: ready to merge.

Findings (3)

  • HIGH The test asserts the generated: field via require.Contains(t, string(raw), wantGenerated) where wantGenerated is epoch.Format(time.RFC3339). · pkg/action/package_test.go:196
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH StampModTimes now mutates the caller's time.Time via t.UTC().Truncate() before assigning to ch.ModTime and Lock.Generated. · pkg/chart/v2/chart.go:190
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • LOW The new test fixture chart-with-lock/Chart.lock contains a hardcoded digest of all zeros (sha256:0000...). · pkg/action/testdata/charts/chart-with-lock/Chart.lock:2
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Fireworks usage: 8,056 input · 440 output · 8,496 total tokens · $0.0021 · 9s · 0 fix iteration(s)

Open the Shipwright check for full evidence and the audit bundle. Use /shipwright rerun to verify again.

if hdr.Name != wantPath {
continue
}
found = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The test asserts the generated: field via require.Contains(t, string(raw), wantGenerated) where wantGenerated is epoch.Format(time.RFC3339).

Impact: The test asserts the generated: field via require.Contains(t, string(raw), wantGenerated) where wantGenerated is epoch.Format(time.RFC3339). This is a substring match against the entire marshaled YAML. If the lock file contains another field (e.g., a dependency's generated timestamp or a comment) that happens to contain the same RFC3339 string, the test could pass even when Lock.Generated is not actually stamped. A…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Comment thread pkg/chart/v2/chart.go
// non-reproducible across machines even when the same SOURCE_DATE_EPOCH is used.
func (ch *Chart) StampModTimes(t time.Time) {
t = t.UTC().Truncate(time.Second)
ch.ModTime = t

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

StampModTimes now mutates the caller's time.Time via t.UTC().Truncate() before assigning to ch.ModTime and Lock.Generated.

Impact: StampModTimes now mutates the caller's time.Time via t.UTC().Truncate() before assigning to ch.ModTime and Lock.Generated. If the same *time.Time pointer is shared across multiple charts or reused after this call, the caller's original value is silently altered to UTC/truncated, which could break subsequent logic that expects the original local-zone or sub-second value. The test itself works around this by pre-compu…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

@@ -0,0 +1,3 @@
dependencies: []
digest: sha256:0000000000000000000000000000000000000000000000000000000000000

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · LOW

The new test fixture chart-with-lock/Chart.lock contains a hardcoded digest of all zeros (sha256:0000...).

Impact: The new test fixture chart-with-lock/Chart.lock contains a hardcoded digest of all zeros (sha256:0000...). While this is test data and not a real secret, it establishes a pattern of placeholder digests that could mask a future regression where digest computation is accidentally stubbed or skipped in production code paths. Consider using a realistic digest or documenting why the zero digest is safe for this test.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

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.

1 participant