Bug: split-layer catalog load errors are silently swallowed (false "0 controls")
Pre-existing bug in internal/policy, found while reviewing PR #711 (it predates the PR).
resolveSplitGraph treats any failure to load the control-catalog layer as "no catalog" and continues with zero controls, no warning. A corrupt or unreadable catalog is indistinguishable from one that's legitimately absent.
Why it's silent
LoadLayerByMediaType returns one plain error for four different conditions, and the caller only checks == nil:
// internal/policy/loader.go (error returns)
:79 manifest resolve failed fmt.Errorf("policy %s@%s: %w", ...)
:86 blob fetch failed (I/O) fmt.Errorf("failed to fetch layer %s: %w", ...)
:91 blob read failed (I/O) fmt.Errorf("failed to read layer %s: %w", ...)
:97 layer genuinely absent fmt.Errorf("layer with media type %s not found ...")
// internal/policy/resolver.go:180 (resolveSplitGraph)
_, catalogLoadErr := r.loader.LoadLayerByMediaType(policyID, version, MediaTypeCatalog)
if catalogLoadErr == nil { /* parse + count controls */ }
// no else: on ANY error the catalog is silently skipped
Only the :97 case is benign. The I/O cases should surface. PR #711's extractSplitMetadata (:208) copies the same pattern.
Impact
Reproduce
Cache a split-layer policy with a catalog, corrupt or truncate the catalog blob, then run complyctl scan (or doctor): zero controls, no error.
Fix
- Give
LoadLayerByMediaType a sentinel ErrLayerNotFound (wrapped for errors.Is) for the :97 case only.
- In
resolveSplitGraph (and extractSplitMetadata), branch: not-found → zero controls (fine); any other error → warn or return it.
- Tests: absent catalog → 0 controls, no error; fetch/read error → surfaced.
Not the same as PR #711's finding #2
Two opposite catalog bugs live in this code:
| Catalog condition |
Today |
Should be |
Owner |
| Present but invalid |
fatal, drops all metadata |
warn, ControlCount = 0 |
PR #711 (A2) |
| Load fails (this issue) |
silently ControlCount = 0 |
distinguish absent vs I/O error |
this issue |
References
Base f61e943: resolver.go:180-181, loader.go:59-101. PR head 4cad011d duplicates at resolver.go:208-210. From the PR #711 review (alongside finding #2).
Related issues: #714, #716
Bug: split-layer catalog load errors are silently swallowed (false "0 controls")
Pre-existing bug in
internal/policy, found while reviewing PR #711 (it predates the PR).resolveSplitGraphtreats any failure to load the control-catalog layer as "no catalog" and continues with zero controls, no warning. A corrupt or unreadable catalog is indistinguishable from one that's legitimately absent.Why it's silent
LoadLayerByMediaTypereturns one plainerrorfor four different conditions, and the caller only checks== nil:Only the
:97case is benign. The I/O cases should surface. PR #711'sextractSplitMetadata(:208) copies the same pattern.Impact
scan: a policy that defines controls can be assessed as if it defines none, with no signal. For a compliance tool, a corrupt catalog reading as "nothing to check" is the worst failure class.list(PR feat: surface Gemara bundle metadata in list and get commands Body: #711): showsCONTROLS = 0, which looks legitimate and hides the corruption.Reproduce
Cache a split-layer policy with a catalog, corrupt or truncate the catalog blob, then run
complyctl scan(ordoctor): zero controls, no error.Fix
LoadLayerByMediaTypea sentinelErrLayerNotFound(wrapped forerrors.Is) for the:97case only.resolveSplitGraph(andextractSplitMetadata), branch: not-found → zero controls (fine); any other error → warn or return it.Not the same as PR #711's finding #2
Two opposite catalog bugs live in this code:
ControlCount = 0ControlCount = 0References
Base
f61e943:resolver.go:180-181,loader.go:59-101. PR head4cad011dduplicates atresolver.go:208-210. From the PR #711 review (alongside finding #2).Related issues: #714, #716