fix: address post-merge review findings from PR #62 - #63
Conversation
Extract validation policy logic from cmd/wasm/validate.go into internal/certstore/validate.go — removes //go:build constraint so checks are reachable by go test. WASM file is now a thin JS wrapper. Add tests for CheckExpiration, CheckKeyStrength, CheckSignature, and CheckTrustChain covering policy thresholds (RSA <2048 = fail, MD5/SHA1 = fail/warn, expired/not-yet-valid, nil roots, self-signed). Fix progressTotal overflow in AIA resolution: recompute total each depth round to include newly-discovered intermediates, preventing completed from exceeding total mid-loop. Propagate context through RunValidation with ctx.Err() check between root pool load and validation checks, honoring the 30s WASM timeout. Fix stale changelog refs: d8b9759/837e5e8 (pre-rebase) → 392878a (squash merge SHA). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses four post-merge review findings from PR #62 by fixing a progress bar overflow bug, adding context cancellation checks, updating stale changelog commit references, and extracting WASM validation logic into testable internal packages.
Changes:
- Fixed AIA resolution progress tracking by recalculating
progressTotaleach depth round to include newly-discovered intermediates - Added context cancellation check in
RunValidationbetween Mozilla root pool load and validation checks to honor WASM timeout constraints - Updated CHANGELOG.md to replace pre-rebase commit SHAs with the squash merge SHA
392878a - Extracted certificate validation logic from
cmd/wasm/validate.gointointernal/certstore/validate.gowith comprehensive table-driven tests
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| internal/certstore/validate.go | New file containing extracted validation logic (CheckExpiration, CheckKeyStrength, CheckSignature, CheckTrustChain) now testable outside WASM build constraints |
| internal/certstore/validate_test.go | New comprehensive table-driven tests for all validation functions covering policy thresholds and edge cases |
| internal/certstore/aia.go | Fixed progress bar overflow by recalculating progressTotal each round and moving processed map before loop |
| cmd/wasm/validate.go | Reduced to thin JS interop wrapper by delegating to internal/certstore.RunValidation |
| CHANGELOG.md | Updated commit references from pre-rebase SHAs to squash merge SHA 392878a |
Comments suppressed due to low confidence (3)
internal/certstore/validate_test.go:126
- The substring matching logic is unnecessarily complex. Lines 121-122 attempt to check if
check.Detailstarts withtt.detail, but if that fails, it falls back tocontainsSubstringwhich does a full substring search. This creates redundant logic - if you want substring matching, usestrings.Containsdirectly. If you want prefix matching, usestrings.HasPrefix. The current implementation is confusing and harder to maintain.
if check.Detail != "" && tt.detail != "" {
if len(check.Detail) < len(tt.detail) || check.Detail[:len(tt.detail)] != tt.detail {
if !containsSubstring(check.Detail, tt.detail) {
t.Errorf("CheckKeyStrength() detail = %q, want substring %q", check.Detail, tt.detail)
}
}
}
internal/certstore/validate_test.go:235
- The custom
containsSubstringandfindSubstringhelper functions unnecessarily reimplementstrings.Containsfrom the standard library. The logic incontainsSubstringis overly complex and the implementation offindSubstringduplicates whatstrings.Containsalready does. Replace both with direct calls tostrings.Contains(s, substr), which is the established pattern throughout the codebase.
func containsSubstring(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && findSubstring(s, substr))
}
func findSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
internal/certstore/validate_test.go:111
- Test setup code silently ignores errors from
rsa.GenerateKey,ecdsa.GenerateKey, anded25519.GenerateKey. While these functions rarely fail in practice, the coding guidelines (ERR-5) require that errors never be silently ignored. In test setup code, errors should be handled witht.Fatal()or similar to fail the test early if key generation fails.
{
name: "RSA 2048 passes",
cert: func() *x509.Certificate {
key, _ := rsa.GenerateKey(rand.Reader, 2048)
return &x509.Certificate{PublicKey: &key.PublicKey}
}(),
status: "pass",
detail: "RSA 2048-bit",
},
{
name: "RSA 1024 fails",
cert: func() *x509.Certificate {
key, _ := rsa.GenerateKey(rand.Reader, 1024) //nolint:gosec // intentionally weak for test
return &x509.Certificate{PublicKey: &key.PublicKey}
}(),
status: "fail",
detail: "RSA 1024-bit",
},
{
name: "ECDSA P-256 passes",
cert: func() *x509.Certificate {
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
return &x509.Certificate{PublicKey: &key.PublicKey}
}(),
status: "pass",
detail: "ECDSA",
},
{
name: "Ed25519 passes",
cert: func() *x509.Certificate {
pub, _, _ := ed25519.GenerateKey(rand.Reader)
return &x509.Certificate{PublicKey: pub}
}(),
status: "pass",
detail: "Ed25519",
},
}
Replace custom containsSubstring/findSubstring helpers with strings.Contains from the standard library. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Code reviewBugs
certkit/internal/certstore/aia.go Lines 117 to 121 in e011c27 A cert whose AIA issuer fetch fails is still added to One fix: maintain a separate CLAUDE.md violationsCL-1 (MUST): Missing changelog entries for this PR's production changes — Lines 9 to 20 in e011c27 The PR updates existing SHA references in
Per CL-1: "a commit that touches production code (not just tests) always needs one." ERR-5 (MUST): Silently ignored errors in certkit/internal/certstore/validate_test.go Lines 76 to 112 in e011c27 The anonymous closures that initialize key, _ := rsa.GenerateKey(rand.Reader, 2048)
key, _ := rsa.GenerateKey(rand.Reader, 1024)
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
pub, _, _ := ed25519.GenerateKey(rand.Reader)If any key generation fails, the closure returns Per ERR-5: "Never silently ignore errors." T-12 (MUST): Two separate certkit/internal/certstore/validate_test.go Lines 157 to 219 in e011c27
Per T-12: "One parametric test over N inputs, not N copy-paste tests." |
- Fix AIA progressTotal double-counting persistently-unresolved certs by tracking unique certs in a totalSeen set instead of recomputing from processed + queue (which overlap) - Restructure TestCheckKeyStrength to generate keys inside subtests with proper t.Fatal error handling (ERR-5) - Consolidate TestCheckTrustChain_NilRoots and _SelfSigned into a single table-driven TestCheckTrustChain (T-12) - Add missing changelog entries for PR #63 production changes (CL-1) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use certID (serial+AKI) instead of SubjectKeyId for totalSeen and processed maps — SKI is not unique across cross-signed certs - Add TestResolveAIA_ProgressNoDuplicateCounting asserting completed never exceeds total when fetches fail across depth rounds - Fix changelog: progressTotal fix refs #64 not #63, add [#64] link Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address post-merge review findings from PR #63 - Fix AIA progressTotal double-counting persistently-unresolved certs by tracking unique certs in a totalSeen set instead of recomputing from processed + queue (which overlap) - Restructure TestCheckKeyStrength to generate keys inside subtests with proper t.Fatal error handling (ERR-5) - Consolidate TestCheckTrustChain_NilRoots and _SelfSigned into a single table-driven TestCheckTrustChain (T-12) - Add missing changelog entries for PR #63 production changes (CL-1) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use certID for progress tracking and add progress test - Use certID (serial+AKI) instead of SubjectKeyId for totalSeen and processed maps — SKI is not unique across cross-signed certs - Add TestResolveAIA_ProgressNoDuplicateCounting asserting completed never exceeds total when fetches fail across depth rounds - Fix changelog: progressTotal fix refs #64 not #63, add [#64] link Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use certID in early-exit progress path The early-exit branch (when all AIA URLs are already seen) still used rec.SKI while the normal path used certID(rec.Cert). This mismatch could reintroduce the completed > total overflow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: fix progress test to exercise multi-round scenario The previous test used an always-failing fetcher, so the loop exited after one round (fetched == 0). Restructure with two leaves: one whose AIA fetch succeeds (keeping the loop alive) and one that always fails (re-entering the queue in round 2). This actually exercises the double-counting fix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: assert total stability in progress test The previous assertions (completed <= total, final == 100%) were tautological — they pass even with the old buggy formula. Assert that total stays at exactly 2 across all ticks, which fails with the old len(processed)+len(queue) formula that inflated to 3. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update commits-and-prs rule with PR workflow lessons Add sections on pre-commit hook recovery, resolving review threads via GraphQL, merge checklist, and changelog ref attribution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Fixes 4 issues flagged by claude-review on PR #62 after it was merged:
progressTotaleach depth round to include newly-discovered intermediates, preventingcompleted > total(which caused progress bar >100% mid-loop)RunValidationnow checksctx.Err()between root pool load and validation checks, honoring the 30s WASM timeoutd8b9759/837e5e8replaced with squash merge SHA392878aCheckExpiration,CheckKeyStrength,CheckSignature,CheckTrustChainfromcmd/wasm/validate.go(behind//go:build js && wasm) intointernal/certstore/validate.go(reachable bygo test). WASM file is now a thin JS interop wrapper. Added table-driven tests covering policy thresholds.Test plan
go test -race -count=1 ./...passes (includes new validate_test.go)GOOS=js GOARCH=wasm go vet ./cmd/wasm/ && go build -o /dev/null ./cmd/wasm/🤖 Generated with Claude Code