Skip to content

bundle: cap raw TlogEntries length before per-entry parse#630

Merged
kommendorkapten merged 3 commits into
sigstore:mainfrom
tonghuaroot:harden-tlog-entries-cap
May 27, 2026
Merged

bundle: cap raw TlogEntries length before per-entry parse#630
kommendorkapten merged 3 commits into
sigstore:mainfrom
tonghuaroot:harden-tlog-entries-cap

Conversation

@tonghuaroot

Copy link
Copy Markdown
Contributor

Follow-up to #567 — moves the existing maxAllowedTlogEntries = 32 cap earlier in the parse path so the bound is enforced on the raw decoded slice length before each entry is individually parsed.

Today the constant is consulted in pkg/verify/tlog.go:VerifyTlogEntry, which means (*Bundle).TlogEntries() (and the validate() call that runs during Bundle.UnmarshalJSON) iterate and call tlog.ParseTransparencyLogEntry for every element of VerificationMaterial.TlogEntries before the count check fires. Moving the check to the start of TlogEntries() keeps the cap intact and keeps the parse step's work proportional to the configured limit rather than to the bundle's declared entry count.

Also lifts the constant (and the analogous maxAllowedTimestamps cap) into a new pkg/limits package so pkg/bundle/bundle.go and pkg/verify/{tlog,tsa}.go reference the same source of truth. Values are unchanged (32 in both cases).

Summary

  • Add pkg/limits with exported MaxAllowedTlogEntries and MaxAllowedTimestamps (both 32, matching today's behavior).
  • Add an early count check at the top of (*Bundle).TlogEntries() so the cap fires before the per-entry parse loop.
  • Update pkg/verify/tlog.go and pkg/verify/tsa.go to reference the shared constants.
  • Add a regression test in pkg/bundle/bundle_test.go asserting the early return surfaces the count-based error (entries are intentionally bare so per-entry parse would otherwise error first).

Test plan

  • go test ./pkg/bundle/... ./pkg/verify/... ./pkg/limits/... — all existing tests pass; new TestTlogEntriesCountCap and TestTlogEntriesCountAtCap pass.
  • go vet ./pkg/bundle/... ./pkg/verify/... ./pkg/limits/... clean.
  • No public API change — the pkg/verify constants were unexported; the new shared constants are additive.

Notes for reviewers:

  • This is intentionally a smaller, more focused change than fix(bundle): cap tlogEntries during JSON parse #567: same direction on centralizing the constants that @kommendorkapten endorsed in that thread, without changing the cap value or adding new methods to SignedEntity.
  • The bound here runs after protojson.Unmarshal, so it does not cap allocations during JSON decoding itself; callers should still enforce a bundle byte-size limit at ingestion (unchanged from current guidance).

Move the existing maxAllowedTlogEntries = 32 cap earlier in the parse
path so the bound is enforced on the raw decoded slice length before
each entry is individually parsed.

Previously the constant was consulted only in pkg/verify/tlog.go inside
VerifyTlogEntry, which meant (*Bundle).TlogEntries() (and the validate()
call that runs during Bundle.UnmarshalJSON) would iterate and call
tlog.ParseTransparencyLogEntry for every element of
VerificationMaterial.TlogEntries before the count check fired.

Moving the check to the top of TlogEntries() keeps the cap intact and
keeps the parse step's work proportional to the configured limit
rather than to the bundle's declared entry count.

Also lifts the constant (and the analogous maxAllowedTimestamps cap)
into a new pkg/limits package so pkg/bundle and pkg/verify reference
the same source of truth. Values are unchanged.

This is a follow-up to the direction discussed in sigstore#567.

Signed-off-by: tonghuaroot <tonghuaroot@gmail.com>
@tonghuaroot tonghuaroot requested a review from a team as a code owner May 23, 2026 00:57
Comment thread internal/limits/limits.go
// MaxAllowedTlogEntries is the upper bound on the number of transparency
// log entries a bundle may carry. The value matches the historical cap
// enforced inside VerifyTlogEntry.
const MaxAllowedTlogEntries = 32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As I mentioned in the other PR, these shouldn't be publicly exported values.

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 call — moved the package from pkg/limits/ to internal/limits/ in 463da5a. The constants remain uppercase since cross-package access requires it, but the internal/ location keeps them off the public Go API surface so external importers cannot depend on them. The 4 import paths in pkg/bundle/{bundle,bundle_test}.go and pkg/verify/{tlog,tsa}.go were updated to point at the new path; TestTlogEntriesCountCap and TestTlogEntriesCountAtCap pass.

Move the per-bundle upper bounds package from pkg/limits/ to
internal/limits/. The constants remain uppercase since cross-package
access requires it, but the internal/ location keeps them off the
public Go API surface as requested in PR review.

The single source of truth and the parse/verify symmetry are preserved;
only the import path changes.

Signed-off-by: tonghuaroot <tonghuaroot@gmail.com>

@Hayden-IO Hayden-IO left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left some comments on the code comments. These comments are fairly typical for LLM output in that they are either not needed or overly descriptive, so please review that they are meaningful and in line with Go best practices.

Comment thread internal/limits/limits.go
// between the bundle parse path and the verify path. Keeping a single
// source of truth avoids drift between the two layers and lets the parse
// step short-circuit on counts that the verify step would later reject.
package limits

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This package comment doesn't need to describe this being a single source of truth.

Comment thread internal/limits/limits.go Outdated

// MaxAllowedTlogEntries is the upper bound on the number of transparency
// log entries a bundle may carry. The value matches the historical cap
// enforced inside VerifyTlogEntry.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This doesn't need to mention a "historical cap", same as below, as that isn't a meaningful description.

Comment thread pkg/bundle/bundle.go Outdated
// before iterating and parsing each one. The same cap is later
// re-checked in pkg/verify, so enforcing it here keeps the parse
// step's work proportional to the configured limit instead of to
// the bundle's declared entry count.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code is self-describing, not sure this needs a comment

Comment thread pkg/bundle/bundle_test.go Outdated
}

func TestTlogEntriesCountCap(t *testing.T) {
// Build a slice of entries that exceeds the shared cap. The individual

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The test code is self-describing, this comment isn't adding anything.

Comment thread pkg/bundle/bundle_test.go Outdated
}

func TestTlogEntriesCountAtCap(t *testing.T) {
// At exactly the cap the count check must not fire. The slice here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same here

Signed-off-by: tonghuaroot <tonghuaroot@gmail.com>
@tonghuaroot

Copy link
Copy Markdown
Contributor Author

Thanks @Hayden-IO. Pushed 34db922 trimming the redundant comments per the five threads:

  • internal/limits/limits.go:19 — collapsed the package doc to one sentence; dropped the "single source of truth" framing.
  • internal/limits/limits.go:23 (and :29) — dropped the "matches the historical cap enforced inside Verify*" sentence on both constants.
  • pkg/bundle/bundle.go:309 — removed the five-line block above the count check; the conditional is self-describing.
  • pkg/bundle/bundle_test.go:1110 (TestTlogEntriesCountCap) and :1139 (TestTlogEntriesCountAtCap) — removed the comment blocks at the top of each test.

go vet clean and the two regression tests still pass on the trimmed source.

@kommendorkapten kommendorkapten 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!

@kommendorkapten kommendorkapten merged commit 7e8ee0f into sigstore:main May 27, 2026
12 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.

3 participants