Skip to content

feat(restore): add post-restore integrity validation#1170

Merged
corylanou merged 4 commits intomainfrom
feat/post-restore-integrity-check
Mar 8, 2026
Merged

feat(restore): add post-restore integrity validation#1170
corylanou merged 4 commits intomainfrom
feat/post-restore-integrity-check

Conversation

@corylanou
Copy link
Collaborator

Description

Adds optional integrity checking after database restores to catch corruption early, regardless of source.

Changes

  • IntegrityCheckMode type (None/Quick/Full): Controls whether and how PRAGMA integrity_check or PRAGMA quick_check runs after restore.
  • checkIntegrity() function: Runs the selected integrity check on the restored database. On failure, cleans up the corrupt file and returns an error.
  • Integrated into Restore() and RestoreV3(): Both code paths now support post-restore validation.
  • EnsureExists() defaults to IntegrityCheckQuick: K8s init containers calling EnsureExists() catch corrupt restores before the application starts.
  • -integrity-check CLI flag: Users can opt into validation via litestream restore -integrity-check quick|full.

Why

Even with the root cause fix for #1164 (PR #1166), integrity checking provides defense in depth against corruption from any source — disk errors, buggy storage backends, network corruption during transfer, etc. It's cheap insurance: quick_check is fast and catches most structural issues.

Related to #1164

How Has This Been Tested?

  • go test -race -count=1 ./... — all tests pass
  • New tests in replica_internal_test.go:
    • TestCheckIntegrity_Quick_ValidDB — valid DB returns no error
    • TestCheckIntegrity_Full_ValidDB — full mode on valid DB returns no error
    • TestCheckIntegrity_None_SkipsIntegrityCheckNone returns nil without opening DB
    • TestCheckIntegrity_CorruptDB — corrupted DB returns integrity check error
  • Pre-commit hooks pass (go-imports, go-vet, go-staticcheck)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (would cause existing functionality to not work as expected)

Checklist

  • My code follows the code style of this project (go fmt, go vet)
  • I have tested my changes (go test ./...)
  • I have updated the documentation accordingly (if needed)

Add IntegrityCheckMode type with None/Quick/Full modes and a
checkIntegrity() function that runs PRAGMA quick_check or
integrity_check on restored databases.

EnsureExists() now defaults to IntegrityCheckQuick so K8s init
containers catch corrupt restores before the application starts.

Adds -integrity-check CLI flag to the restore command for manual use.

Closes #1164
@github-actions
Copy link

github-actions bot commented Feb 26, 2026

PR Build Metrics

⚠️ Attention needed — vulnerabilities found

Check Status Summary
Binary size 35.82 MB (+8.0 KB / +0.02%)
Dependencies No changes
Vulnerabilities ⚠️ Issues found — expand details below
Go toolchain 1.24.13 (latest)
Module graph 1204 edges (0)

Binary Size

Size Change
Base (0b3facd) 35.82 MB
PR (9850c58) 35.82 MB +8.0 KB (+0.02%)

Dependency Changes

No dependency changes.

govulncheck Output

=== Symbol Results ===

Vulnerability #1: GO-2026-4603
    URLs in meta content attribute actions are not escaped in html/template
  More info: https://pkg.go.dev/vuln/GO-2026-4603
  Standard library
    Found in: html/template@go1.24.13
    Fixed in: html/template@go1.25.8
    Example traces found:
      #1: server.go:135:31: litestream.Start calls http.Server.Serve, which eventually calls template.Template.Execute
      #2: server.go:135:31: litestream.Start calls http.Server.Serve, which eventually calls template.Template.ExecuteTemplate

Vulnerability #2: GO-2026-4602
    FileInfo can escape from a Root in os
  More info: https://pkg.go.dev/vuln/GO-2026-4602
  Standard library
    Found in: os@go1.24.13
    Fixed in: os@go1.25.8
    Example traces found:
      #1: file/replica_client.go:101:23: file.ReplicaClient.LTXFiles calls os.File.Readdir
      #2: db.go:338:25: litestream.DB.MaxLTX calls os.ReadDir

Vulnerability #3: GO-2026-4601
    Incorrect parsing of IPv6 host literals in net/url
  More info: https://pkg.go.dev/vuln/GO-2026-4601
  Standard library
    Found in: net/url@go1.24.13
    Fixed in: net/url@go1.25.8
    Example traces found:
      #1: s3/replica_client.go:1570:21: s3.ParseURL calls url.Parse
      #2: server.go:135:31: litestream.Start calls http.Server.Serve, which eventually calls url.ParseRequestURI
      #3: heartbeat.go:55:30: litestream.HeartbeatClient.Ping calls http.Client.Do, which eventually calls url.URL.Parse

Vulnerability #4: GO-2026-4600
    Panic in name constraint checking for malformed certificates in crypto/x509
  More info: https://pkg.go.dev/vuln/GO-2026-4600
  Standard library
    Found in: crypto/x509@go1.24.13
    Fixed in: crypto/x509@go1.26.1
    Example traces found:
      #1: internal/resumable_reader.go:76:22: internal.ResumableReader.Read calls http.readWriteCloserBody.Read, which eventually calls x509.Certificate.Verify

Vulnerability #5: GO-2026-4599
    Incorrect enforcement of email constraints in crypto/x509
  More info: https://pkg.go.dev/vuln/GO-2026-4599
  Standard library
    Found in: crypto/x509@go1.24.13
    Fixed in: crypto/x509@go1.26.1
    Example traces found:
      #1: internal/resumable_reader.go:76:22: internal.ResumableReader.Read calls http.readWriteCloserBody.Read, which eventually calls x509.Certificate.Verify

Your code is affected by 5 vulnerabilities from the Go standard library.
This scan found no other vulnerabilities in packages you import or modules you
require.
Use '-show verbose' for more details.

Build Info

Metric Value
Build time 40s
Go version go1.24.13
Commit 9850c58

History (3 previous)

Commit Updated Status Summary
9126b09 2026-03-08 01:04 UTC 35.82 MB (+8.0 KB / +0.02%)
253a756 2026-03-07 22:48 UTC 35.82 MB (+8.0 KB / +0.02%)
980c7ef 2026-02-26 23:00 UTC 35.82 MB (+8.0 KB / +0.02%)

🤖 Updated on each push.

- Add context.Context parameter to checkIntegrity() so
  cancellation/timeout is respected during long-running PRAGMAs
- Use explicit switch on IntegrityCheckMode to reject unsupported
  values instead of silently falling through to quick_check
Copy link
Owner

@benbjohnson benbjohnson left a comment

Choose a reason for hiding this comment

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

lgtm but there's a spot where it can be cleaned up a little.

Use QueryRowContext() instead of QueryContext() with row iteration
since the integrity check returns a single result row.
@github-actions github-actions bot added the metrics: vulns-found govulncheck found vulnerabilities label Mar 7, 2026
…ellation

- Validate IntegrityCheckMode in Restore() and RestoreV3() before doing
  any restore work, preventing invalid modes from causing unnecessary
  restore followed by deletion
- Only delete restored DB on actual integrity failures, not on context
  cancellation or timeout, preserving valid restores interrupted by
  Ctrl+C or deadline
@corylanou corylanou merged commit f6ba05f into main Mar 8, 2026
24 checks passed
@corylanou corylanou deleted the feat/post-restore-integrity-check branch March 8, 2026 08:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

metrics: vulns-found govulncheck found vulnerabilities ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants