Lack of proper validation in immudb
Description
immudb is a database with built-in cryptographic proof and verification. immudb client SDKs use server's UUID to distinguish between different server instance so that the client can connect to different immudb instances and keep the state for multiple servers. SDK does not validate this uuid and can accept any value reported by the server. A malicious server can change the reported UUID tricking the client to treat it as a different server thus accepting a state completely irrelevant to the one previously retrieved from the server. This issue has been patched in version 1.4.1. As a workaround, when initializing an immudb client object a custom state handler can be used to store the state. Providing custom implementation that ignores the server UUID can be used to ensure that even if the server changes the UUID, client will still consider it to be the same server.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
immudb client SDKs lack UUID validation, allowing a malicious server to impersonate another instance and break client trust guarantees.
Root
Cause
The immudb client SDK uses a server-provided UUID to distinguish between different immudb instances. This UUID is not validated by the SDK, meaning a server can report any arbitrary value. [1][2] This lack of validation is the fundamental flaw; the client blindly trusts the server's identity claim.
Attack
Vector
An attacker controlling a malicious immudb server can change the reported UUID to mimic a different, legitimate server. The client, having previously cached a state for that legitimate server's UUID, will accept the malicious server's reported state as valid, even though it is unrelated to the trusted state. [2] No additional authentication is needed beyond the ability to present a different UUID during the connection handshake.
Impact
By tricking the client into treating the malicious server as a trusted one, the attacker can cause the client to accept a completely fabricated or divergent state. This breaks immudb's core tamper-evident and verification guarantees, potentially leading to incorrect data verification and loss of integrity for the client's data history. [1][2]
Mitigation
The vulnerability is patched in immudb SDK version 1.4.1. [3] The fix includes server identity validation in the client cache. [4] As a workaround, a custom state handler that ignores the server UUID can be implemented to ensure the client always treats the server as the same instance. [2]
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/codenotary/immudbGo | < 1.4.1 | 1.4.1 |
Affected products
2- Range: < 1.4.1
Patches
1cade04756ff3fix(pkg/client/cache): Validate server's identity
4 files changed · +85 −44
pkg/client/client.go+21 −2 modified@@ -592,9 +592,28 @@ func NewImmuClient(options *Options) (*immuClient, error) { } stateProvider := state.NewStateProvider(serviceClient) - uuidProvider := state.NewUUIDProvider(serviceClient) + serverUUID, err := state.NewUUIDProvider(serviceClient).CurrentUUID(context.Background()) + if err != nil { + return nil, logErr(l, "Unable to get server uuid: %s", err) + } + + stateCache := cache.NewFileCache(c.Options.Dir) + if !c.Options.DisableIdentityCheck { + err = stateCache.ServerIdentityCheck( + fmt.Sprintf("%s:%d", c.Options.Address, c.Options.Port), + serverUUID, + ) + if err != nil { + return nil, logErr(l, "Unable to validate server identity: %s", err) + } + } - stateService, err := state.NewStateService(cache.NewFileCache(options.Dir), l, stateProvider, uuidProvider) + stateService, err := state.NewStateServiceWithUUID( + stateCache, + l, + stateProvider, + serverUUID, + ) if err != nil { return nil, logErr(l, "Unable to create state service: %s", err) }
pkg/client/options.go+24 −16 modified@@ -59,27 +59,30 @@ type Options struct { StreamChunkSize int // Maximum size of a data chunk in bytes for streaming operations (directly affects maximum GRPC packet size) HeartBeatFrequency time.Duration // Duration between two consecutive heartbeat calls to the server for session heartbeats + + DisableIdentityCheck bool // Do not validate server's identity } // DefaultOptions ... func DefaultOptions() *Options { return &Options{ - Dir: ".", - Address: "127.0.0.1", - Port: 3322, - HealthCheckRetries: 5, - MTLs: false, - Auth: true, - MaxRecvMsgSize: 4 * 1024 * 1024, //4Mb - Config: "configs/immuclient.toml", - DialOptions: []grpc.DialOption{grpc.WithInsecure()}, - PasswordReader: c.DefaultPasswordReader, - Metrics: true, - PidPath: "", - LogFileName: "", - ServerSigningPubKey: "", - StreamChunkSize: stream.DefaultChunkSize, - HeartBeatFrequency: time.Minute * 1, + Dir: ".", + Address: "127.0.0.1", + Port: 3322, + HealthCheckRetries: 5, + MTLs: false, + Auth: true, + MaxRecvMsgSize: 4 * 1024 * 1024, //4Mb + Config: "configs/immuclient.toml", + DialOptions: []grpc.DialOption{grpc.WithInsecure()}, + PasswordReader: c.DefaultPasswordReader, + Metrics: true, + PidPath: "", + LogFileName: "", + ServerSigningPubKey: "", + StreamChunkSize: stream.DefaultChunkSize, + HeartBeatFrequency: time.Minute * 1, + DisableIdentityCheck: false, } } @@ -216,6 +219,11 @@ func (o *Options) WithHeartBeatFrequency(heartBeatFrequency time.Duration) *Opti return o } +func (o *Options) WithDisableIdentityCheck(disableIdentityCheck bool) *Options { + o.DisableIdentityCheck = disableIdentityCheck + return o +} + // String converts options object to a json string func (o *Options) String() string { optionsJSON, err := json.Marshal(o)
pkg/client/options_test.go+28 −25 modified@@ -18,6 +18,8 @@ package client import ( "testing" + + "github.com/stretchr/testify/require" ) func TestOptions(t *testing.T) { @@ -43,30 +45,31 @@ func TestOptions(t *testing.T) { WithUsername("some-username"). WithPassword("some-password"). WithDatabase("some-db"). - WithStreamChunkSize(4096) + WithStreamChunkSize(4096). + WithDisableIdentityCheck(true) + + require.Equal(t, op.LogFileName, "logfilename") + require.Equal(t, op.PidPath, "pidpath") + require.True(t, op.Metrics) + require.Equal(t, op.Dir, "clientdir") + require.Equal(t, op.Address, "127.0.0.1") + require.Equal(t, op.Port, 4321) + require.Equal(t, op.HealthCheckRetries, 3) + require.True(t, op.MTLs) + require.Equal(t, op.MTLsOptions.Servername, "localhost") + require.Equal(t, op.MTLsOptions.Certificate, "no-certificate") + require.Equal(t, op.MTLsOptions.ClientCAs, "no-client-ca") + require.Equal(t, op.MTLsOptions.Pkey, "no-pkey") + require.True(t, op.Auth) + require.Equal(t, op.MaxRecvMsgSize, 1<<20) + require.Equal(t, op.Config, "configfile") + require.Equal(t, op.TokenFileName, "tokenfile") + require.Equal(t, op.Username, "some-username") + require.Equal(t, op.Password, "some-password") + require.Equal(t, op.Database, "some-db") + require.Equal(t, op.StreamChunkSize, 4096) + require.True(t, op.DisableIdentityCheck) + require.Equal(t, op.Bind(), "127.0.0.1:4321") + require.NotEmpty(t, op.String()) - if op.LogFileName != "logfilename" || - op.PidPath != "pidpath" || - !op.Metrics || - op.Dir != "clientdir" || - op.Address != "127.0.0.1" || - op.Port != 4321 || - op.HealthCheckRetries != 3 || - !op.MTLs || - op.MTLsOptions.Servername != "localhost" || - op.MTLsOptions.Certificate != "no-certificate" || - op.MTLsOptions.ClientCAs != "no-client-ca" || - op.MTLsOptions.Pkey != "no-pkey" || - !op.Auth || - op.MaxRecvMsgSize != 1<<20 || - op.Config != "configfile" || - op.TokenFileName != "tokenfile" || - op.Username != "some-username" || - op.Password != "some-password" || - op.Database != "some-db" || - op.StreamChunkSize != 4096 || - op.Bind() != "127.0.0.1:4321" || - len(op.String()) == 0 { - t.Fatal("Client options fail") - } }
pkg/client/session.go+12 −1 modified@@ -63,9 +63,20 @@ func (c *immuClient) OpenSession(ctx context.Context, user []byte, pass []byte, } }() + stateCache := cache.NewFileCache(c.Options.Dir) + if !c.Options.DisableIdentityCheck { + err = stateCache.ServerIdentityCheck( + fmt.Sprintf("%s:%d", c.Options.Address, c.Options.Port), + resp.GetServerUUID(), + ) + if err != nil { + return err + } + } + stateProvider := state.NewStateProvider(serviceClient) - stateService, err := state.NewStateServiceWithUUID(cache.NewFileCache(c.Options.Dir), c.Logger, stateProvider, resp.GetServerUUID()) + stateService, err := state.NewStateServiceWithUUID(stateCache, c.Logger, stateProvider, resp.GetServerUUID()) if err != nil { return errors.FromError(fmt.Errorf("unable to create state service: %v", err)) }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-6cqj-6969-p57xghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-39199ghsaADVISORY
- github.com/codenotary/immudb/commit/cade04756ff3f0a3b9e8d24149062744574adf5dghsaWEB
- github.com/codenotary/immudb/releases/tag/v1.4.1ghsaWEB
- github.com/codenotary/immudb/security/advisories/GHSA-6cqj-6969-p57xghsaWEB
- pkg.go.dev/vuln/GO-2022-1118ghsaWEB
News mentions
0No linked articles in our index yet.