🦾 Written with LLM assistance [claude-opus-4-6]
💪 Reviewed by a human before submission
Describe the bug
The SyncPolicy() and SyncComplypack() functions do not currently validate that digest strings from DefinitionVersion() are structurally valid OCI digests (e.g., sha256: followed by 64 hex characters). This works correctly today because production digests come from oras, which always returns well-formed values.
Because the DefinitionVersion() interface is public, a future PolicySource or ComplypackSource implementation could return malformed digests that would be persisted to state.json, potentially causing subtle cache corruption (e.g., a digest that can never match again, forcing perpetual re-sync).
Separately, test fixtures across the internal/cache package use placeholder digest strings like "sha256:abc123", "sha256:v1digest", and "sha256:fetchandstore111". These were sufficient for the string-equality comparisons the tests exercise, but they would not pass digest.Parse(). If digest validation is added to the sync path, these fixtures would need to be updated first.
To Reproduce
- Read
internal/cache/sync.go line 111: remoteDigest from DefinitionVersion() is passed through without structural validation
- Read
internal/cache/complypack_sync.go line 89: same pattern
- Read
internal/cache/complypack_sync_test.go: seedComplypack calls use placeholder strings like "sha256:v1digest" that would not pass digest.Parse() from opencontainers/go-digest
- Read
internal/cache/sync_test.go: SeedPolicy calls use "sha256:abc123", a placeholder that is not a structurally valid SHA256 hex string
Expected behavior
SyncPolicy() and SyncComplypack() validate the digest returned by DefinitionVersion() using digest.Parse() and return a wrapped error if invalid. Fails fast with a clear message
- Test fixtures use structurally valid digest strings (64-char hex for SHA256) so they pass
digest.Parse() and model production inputs
Additional context
Files requiring digest fixture updates (non-exhaustive):
internal/cache/complypack_sync_test.go - seedComplypack() calls
internal/cache/sync_test.go - SeedPolicy() calls
internal/cache/cachetest/mock_source.go - SeedPolicy(), SeedBundlePolicy() callers
internal/cache/state_test.go - inline digest strings
internal/cache/complypack_pipeline_test.go - inline digest strings
internal/cache/verify_test.go - inline digest strings
internal/cache/complypack_test.go - inline digest strings
internal/cache/complypack_source_test.go - inline digest strings
The brokenUnpackMock in complypack_sync_test.go line 786 already uses a valid 64-char hex digest, that's the correct pattern to follow.
The digest is currently used only for string equality comparison (cache skip) and state persistence/display. It is never used for content integrity verification in the sync path — that's handled by oras.Copy() and sigstore-go independently. This change is defense-in-depth: the current code is correct, but we are protecting against future changes that weaken the invariant.
The opencontainers/go-digest package is already imported in internal/cache/sync.go (used by classifyVersion()), so no new dependency is needed.
References
- NIST SP 800-53 Rev. 5, SI-10 (Information Input Validation)
- Recommends checking the validity and syntax of information inputs. Digest strings from
DefinitionVersion() are inputs to the sync path, so structural validation aligns with SI-10.
- NIST SP 800-53 Rev. 5, SA-8 (Security and Privacy Engineering Principles)
- Describes defense-in-depth as a core design principle. Adding validation, while the current code is already correct, is an example of this principle in practice.
- NIST SP 800-53 Rev. 5, SA-11 (Developer Testing and Evaluation)
- Recommends testing security-relevant functionality with representative data. Using production-realistic digest fixtures strengthens alignment with SA-11.
- NIST SP 800-218 (SSDF), PW.6.1
- Recommends validating all security-relevant inputs. Digest strings gate cache behavior and state persistence, making them a good candidate for this practice.
- NIST SP 800-218 (SSDF), PW.6.2
- Recommends handling errors in a secure manner. Returning early with a clear error on an invalid digest follows this guidance.
- NIST SP 800-218 (SSDF), PW.8.2
- Recommends testing with realistic inputs including boundary cases. Updating fixtures to use structurally valid digests brings tests closer to this standard.
- SLSA v1.0, Build L2 (Hosted Build Platform)
- Emphasizes that artifact identifiers should be verifiable. Ensuring digests are well-formed supports the ability to re-verify cache entries against their OCI source.
- SEI CERT C, ERR00-C
- Recommends a consistent and comprehensive error-handling policy. Validating digests at the sync boundary is an opportunity to strengthen that consistency.
Describe the bug
The
SyncPolicy()andSyncComplypack()functions do not currently validate that digest strings fromDefinitionVersion()are structurally valid OCI digests (e.g.,sha256:followed by 64 hex characters). This works correctly today because production digests come from oras, which always returns well-formed values.Because the
DefinitionVersion()interface is public, a futurePolicySourceorComplypackSourceimplementation could return malformed digests that would be persisted tostate.json, potentially causing subtle cache corruption (e.g., a digest that can never match again, forcing perpetual re-sync).Separately, test fixtures across the
internal/cachepackage use placeholder digest strings like"sha256:abc123","sha256:v1digest", and"sha256:fetchandstore111". These were sufficient for the string-equality comparisons the tests exercise, but they would not passdigest.Parse(). If digest validation is added to the sync path, these fixtures would need to be updated first.To Reproduce
internal/cache/sync.goline 111:remoteDigestfromDefinitionVersion()is passed through without structural validationinternal/cache/complypack_sync.goline 89: same patterninternal/cache/complypack_sync_test.go:seedComplypackcalls use placeholder strings like"sha256:v1digest"that would not passdigest.Parse()fromopencontainers/go-digestinternal/cache/sync_test.go:SeedPolicycalls use"sha256:abc123", a placeholder that is not a structurally valid SHA256 hex stringExpected behavior
SyncPolicy()andSyncComplypack()validate the digest returned byDefinitionVersion()usingdigest.Parse()and return a wrapped error if invalid. Fails fast with a clear messagedigest.Parse()and model production inputsAdditional context
Files requiring digest fixture updates (non-exhaustive):
internal/cache/complypack_sync_test.go-seedComplypack()callsinternal/cache/sync_test.go-SeedPolicy()callsinternal/cache/cachetest/mock_source.go-SeedPolicy(),SeedBundlePolicy()callersinternal/cache/state_test.go- inline digest stringsinternal/cache/complypack_pipeline_test.go- inline digest stringsinternal/cache/verify_test.go- inline digest stringsinternal/cache/complypack_test.go- inline digest stringsinternal/cache/complypack_source_test.go- inline digest stringsThe
brokenUnpackMockincomplypack_sync_test.goline 786 already uses a valid 64-char hex digest, that's the correct pattern to follow.The digest is currently used only for string equality comparison (cache skip) and state persistence/display. It is never used for content integrity verification in the sync path — that's handled by
oras.Copy()andsigstore-goindependently. This change is defense-in-depth: the current code is correct, but we are protecting against future changes that weaken the invariant.The
opencontainers/go-digestpackage is already imported ininternal/cache/sync.go(used byclassifyVersion()), so no new dependency is needed.References
DefinitionVersion()are inputs to the sync path, so structural validation aligns with SI-10.