Insufficient randomness in github.com/Masterminds/goutils
Description
Randomly-generated alphanumeric strings contain significantly less entropy than expected. The RandomAlphaNumeric and CryptoRandomAlphaNumeric functions always return strings containing at least one digit from 0 to 9. This significantly reduces the amount of entropy in short strings generated by these functions.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
GoUtils' RandomAlphaNumeric and CryptoRandomAlphaNumeric always include a digit, reducing entropy and making short strings predictable.
Vulnerability
Description The vulnerability in the GoUtils library (CVE-2021-4238) affects the RandomAlphaNumeric and CryptoRandomAlphaNumeric functions. These functions are designed to generate random alphanumeric strings but contain a flaw: they always ensure that the generated string includes at least one digit from 0 to 9 [1]. This guarantee reduces the effective entropy of the output, especially for short strings, because the set of possible strings is smaller than what a truly random selection would produce.
Exploitation
An attacker can exploit this reduced entropy to more easily guess or brute-force tokens, passwords, or other secrets generated by these functions. The vulnerability does not require authentication; any system that relies on these functions for generating random strings (e.g., session tokens, recovery codes) is at risk. The attack surface includes applications that use GoUtils for security-sensitive randomness.
Impact
The impact is a significant reduction in security for any application depending on these functions for cryptographic or security purposes. Short strings become particularly predictable; for example, a 3-character string may only have a few hundred possibilities instead of tens of thousands. This could lead to account takeover, session hijacking, or bypassing of access controls.
Mitigation
The issue was fixed in commit f1923532a168b8203bfe956d8cd3b17ebece5982 [2]. The fix removes the logic that forces a digit into the string, relying solely on the underlying random generation. Users should update to a patched version (v1.1.1 or later) [4]. No workaround is available other than replacing the library with a secure random generator.
AI Insight generated on May 20, 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/Masterminds/goutilsGo | < 1.1.1 | 1.1.1 |
Affected products
5- osv-coords4 versionspkg:apk/chainguard/dynamic-localpv-provisionerpkg:apk/chainguard/dynamic-localpv-provisioner-fipspkg:apk/wolfi/dynamic-localpv-provisionerpkg:golang/github.com/masterminds/goutils
< 3.4.1-r3+ 3 more
- (no CPE)range: < 3.4.1-r3
- (no CPE)range: < 3.5.0-r0
- (no CPE)range: < 3.4.1-r3
- (no CPE)range: < 1.1.1
- github.com/Masterminds/goutils/github.com/Masterminds/goutilsv5Range: 0
Patches
2f1923532a168Merge pull request from GHSA-xg2h-wx96-xgxr
4 files changed · +76 −45
cryptorandomstringutils.go+2 −23 modified@@ -21,7 +21,6 @@ import ( "fmt" "math" "math/big" - "regexp" "unicode" ) @@ -99,27 +98,7 @@ Returns: error - an error stemming from an invalid parameter within underlying function, CryptoRandom(...) */ func CryptoRandomAlphaNumeric(count int) (string, error) { - if count == 0 { - return "", nil - } - RandomString, err := CryptoRandom(count, 0, 0, true, true) - if err != nil { - return "", fmt.Errorf("Error: %s", err) - } - match, err := regexp.MatchString("([0-9]+)", RandomString) - if err != nil { - panic(err) - } - - if !match { - //Get the position between 0 and the length of the string-1 to insert a random number - position := getCryptoRandomInt(count) - //Insert a random number between [0-9] in the position - RandomString = RandomString[:position] + string('0' + getCryptoRandomInt(10)) + RandomString[position + 1:] - return RandomString, err - } - return RandomString, err - + return CryptoRandom(count, 0, 0, true, true) } /* @@ -204,7 +183,7 @@ func CryptoRandom(count int, start int, end int, letters bool, numbers bool, cha if chars == nil { ch = rune(getCryptoRandomInt(gap) + int64(start)) } else { - ch = chars[getCryptoRandomInt(gap) + int64(start)] + ch = chars[getCryptoRandomInt(gap)+int64(start)] } if letters && unicode.IsLetter(ch) || numbers && unicode.IsDigit(ch) || !letters && !numbers {
cryptorandomstringutils_test.go+36 −0 modified@@ -1,6 +1,8 @@ package goutils import ( + "regexp" + "strconv" "testing" "unicode/utf8" ) @@ -74,3 +76,37 @@ func TestCryptoRandomAlphaNumeric(t *testing.T) { } } } + +func TestCryptoRandAlphaNumeric_FuzzOnlyNumeric(t *testing.T) { + + // Testing for a reported regression in which some versions produced + // a predictably small set of chars. + iters := 1000 + charlen := 0 + for i := 0; i < 16; i++ { + numOnly := 0 + charlen++ + for i := 0; i < iters; i++ { + out, err := CryptoRandomAlphaNumeric(charlen) + if err != nil { + t.Fatal("func failed to produce a random thinger") + } + if _, err := strconv.Atoi(out); err == nil { + numOnly++ + } + + m, err := regexp.MatchString("^[0-9a-zA-Z]+$", out) + if err != nil { + t.Fatal(err) + } + if !m { + t.Fatal("Character is not alphanum") + } + } + + if numOnly == iters { + t.Fatalf("Got %d numeric-only random sequences", numOnly) + } + } + +}
randomstringutils.go+2 −22 modified@@ -20,7 +20,6 @@ import ( "fmt" "math" "math/rand" - "regexp" "time" "unicode" ) @@ -75,12 +74,10 @@ func RandomNumeric(count int) (string, error) { /* RandomAlphabetic creates a random string whose length is the number of characters specified. -Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. +Characters will be chosen from the set of alphabetic characters. Parameters: count - the length of random string to create - letters - if true, generated string may include alphabetic characters - numbers - if true, generated string may include numeric characters Returns: string - the random string @@ -102,24 +99,7 @@ Returns: error - an error stemming from an invalid parameter within underlying function, RandomSeed(...) */ func RandomAlphaNumeric(count int) (string, error) { - RandomString, err := Random(count, 0, 0, true, true) - if err != nil { - return "", fmt.Errorf("Error: %s", err) - } - match, err := regexp.MatchString("([0-9]+)", RandomString) - if err != nil { - panic(err) - } - - if !match { - //Get the position between 0 and the length of the string-1 to insert a random number - position := rand.Intn(count) - //Insert a random number between [0-9] in the position - RandomString = RandomString[:position] + string('0'+rand.Intn(10)) + RandomString[position+1:] - return RandomString, err - } - return RandomString, err - + return Random(count, 0, 0, true, true) } /*
randomstringutils_test.go+36 −0 modified@@ -3,6 +3,8 @@ package goutils import ( "fmt" "math/rand" + "regexp" + "strconv" "testing" ) @@ -76,3 +78,37 @@ func ExampleRandomSeed() { // H_I;E // 2b2ca } + +func TestRandAlphaNumeric_FuzzOnlyNumeric(t *testing.T) { + + // Testing for a reported regression in which some versions produced + // a predictably small set of chars. + iters := 1000 + charlen := 0 + for i := 0; i < 16; i++ { + numOnly := 0 + charlen++ + for i := 0; i < iters; i++ { + out, err := RandomAlphaNumeric(charlen) + if err != nil { + t.Fatal("func failed to produce a random thinger") + } + if _, err := strconv.Atoi(out); err == nil { + numOnly++ + } + + m, err := regexp.MatchString("^[0-9a-zA-Z]+$", out) + if err != nil { + t.Fatal(err) + } + if !m { + t.Fatal("Character is not alphanum") + } + } + + if numOnly == iters { + t.Fatalf("Got %d numeric-only random sequences", numOnly) + } + } + +}
869801f20f9fRemove unnecessary checks on a value that is already definitely an alphanum
4 files changed · +78 −35
cryptorandomstringutils.go+2 −19 modified@@ -21,7 +21,6 @@ import ( "fmt" "math" "math/big" - "regexp" "unicode" ) @@ -102,24 +101,8 @@ func CryptoRandomAlphaNumeric(count int) (string, error) { if count == 0 { return "", nil } - RandomString, err := CryptoRandom(count, 0, 0, true, true) - if err != nil { - return "", fmt.Errorf("Error: %s", err) - } - match, err := regexp.MatchString("([0-9]+)", RandomString) - if err != nil { - panic(err) - } - - if !match { - //Get the position between 0 and the length of the string-1 to insert a random number - position := getCryptoRandomInt(count) - //Insert a random number between [0-9] in the position - RandomString = RandomString[:position] + string('0' + getCryptoRandomInt(10)) + RandomString[position + 1:] - return RandomString, err - } - return RandomString, err + return CryptoRandom(count, 0, 0, true, true) } /* @@ -204,7 +187,7 @@ func CryptoRandom(count int, start int, end int, letters bool, numbers bool, cha if chars == nil { ch = rune(getCryptoRandomInt(gap) + int64(start)) } else { - ch = chars[getCryptoRandomInt(gap) + int64(start)] + ch = chars[getCryptoRandomInt(gap)+int64(start)] } if letters && unicode.IsLetter(ch) || numbers && unicode.IsDigit(ch) || !letters && !numbers {
cryptorandomstringutils_test.go+37 −0 modified@@ -1,6 +1,8 @@ package goutils import ( + "regexp" + "strconv" "testing" "unicode/utf8" ) @@ -74,3 +76,38 @@ func TestCryptoRandomAlphaNumeric(t *testing.T) { } } } + +func TestCryptoRandAlphaNumeric_FuzzOnlyNumeric(t *testing.T) { + + // Testing for a reported regression in which some versions produced + // a predictably small set of chars. + iters := 1000 + charlen := 0 + for i := 0; i < 16; i++ { + numOnly := 0 + charlen++ + for i := 0; i < iters; i++ { + out, err := CryptoRandomAlphaNumeric(charlen) + println(out) + if err != nil { + t.Fatal("func failed to produce a random thinger") + } + if _, err := strconv.Atoi(out); err == nil { + numOnly++ + } + + m, err := regexp.MatchString("^[0-9a-zA-Z]+$", out) + if err != nil { + t.Fatal(err) + } + if !m { + t.Fatal("Character is not alphanum") + } + } + + if numOnly == iters { + t.Fatalf("Got %d numeric-only random sequences", numOnly) + } + } + +}
randomstringutils.go+2 −16 modified@@ -20,7 +20,6 @@ import ( "fmt" "math" "math/rand" - "regexp" "time" "unicode" ) @@ -75,12 +74,10 @@ func RandomNumeric(count int) (string, error) { /* RandomAlphabetic creates a random string whose length is the number of characters specified. -Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. +Characters will be chosen from the set of alphabetic characters. Parameters: count - the length of random string to create - letters - if true, generated string may include alphabetic characters - numbers - if true, generated string may include numeric characters Returns: string - the random string @@ -106,19 +103,8 @@ func RandomAlphaNumeric(count int) (string, error) { if err != nil { return "", fmt.Errorf("Error: %s", err) } - match, err := regexp.MatchString("([0-9]+)", RandomString) - if err != nil { - panic(err) - } - if !match { - //Get the position between 0 and the length of the string-1 to insert a random number - position := rand.Intn(count) - //Insert a random number between [0-9] in the position - RandomString = RandomString[:position] + string('0'+rand.Intn(10)) + RandomString[position+1:] - return RandomString, err - } - return RandomString, err + return RandomString[:count], nil }
randomstringutils_test.go+37 −0 modified@@ -3,6 +3,8 @@ package goutils import ( "fmt" "math/rand" + "regexp" + "strconv" "testing" ) @@ -76,3 +78,38 @@ func ExampleRandomSeed() { // H_I;E // 2b2ca } + +func TestRandAlphaNumeric_FuzzOnlyNumeric(t *testing.T) { + + // Testing for a reported regression in which some versions produced + // a predictably small set of chars. + iters := 1000 + charlen := 0 + for i := 0; i < 16; i++ { + numOnly := 0 + charlen++ + for i := 0; i < iters; i++ { + out, err := RandomAlphaNumeric(charlen) + println(out) + if err != nil { + t.Fatal("func failed to produce a random thinger") + } + if _, err := strconv.Atoi(out); err == nil { + numOnly++ + } + + m, err := regexp.MatchString("^[0-9a-zA-Z]+$", out) + if err != nil { + t.Fatal(err) + } + if !m { + t.Fatal("Character is not alphanum") + } + } + + if numOnly == iters { + t.Fatalf("Got %d numeric-only random sequences", numOnly) + } + } + +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-xg2h-wx96-xgxrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-4238ghsaADVISORY
- github.com/Masterminds/goutils/commit/869801f20f9f1e7ecdbdb6422049d8241270d5e1ghsaWEB
- github.com/Masterminds/goutils/commit/f1923532a168b8203bfe956d8cd3b17ebece5982ghsaWEB
- github.com/Masterminds/goutils/releases/tag/v1.1.1ghsaWEB
- github.com/Masterminds/goutils/security/advisories/GHSA-xg2h-wx96-xgxrghsaWEB
- pkg.go.dev/vuln/GO-2022-0411ghsaWEB
News mentions
0No linked articles in our index yet.