Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
---
version: v9.0.2
version: v9.0.3
changelog:
- date: 2026-07-02
version: v9.0.3
changes:
- type: bug
text: "Add `recover` guards to SDK-owned goroutines (subscribe worker, request workers, job enqueue) so panics fail the request instead of crashing the host process."
- type: bug
text: "Replace panic-based `CheckUUID` with `ValidateUUID` and validate the UUID in `buildURL`, surfacing invalid UUIDs as handled errors; `CheckUUID` is kept but deprecated."
- type: bug
text: "Harden subscribe response parsing against malformed payloads with checked type assertions that announce a parse error instead of panicking."
- type: bug
text: "Harden Access Manager grant response parsing to validate all fields and return descriptive errors, and fix TTL parsing to accept JSON numbers."
- type: improvement
text: "Return a single generic error on LegacyCryptor decryption failures to prevent padding-oracle-style information disclosure."
- type: improvement
text: "Change redaction of `SecretKey` to show only first 8 characters and total length of the key."
- date: 2026-06-23
version: v9.0.2
changes:
Expand Down Expand Up @@ -849,7 +864,7 @@ sdks:
distribution-type: package
distribution-repository: GitHub
package-name: Go
location: https://github.com/pubnub/go/releases/tag/v9.0.2
location: https://github.com/pubnub/go/releases/tag/v9.0.3
requires:
-
name: "Go"
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## v9.0.3
July 02 2026

#### Fixed
- Add `recover` guards to SDK-owned goroutines (subscribe worker, request workers, job enqueue) so panics fail the request instead of crashing the host process.
- Replace panic-based `CheckUUID` with `ValidateUUID` and validate the UUID in `buildURL`, surfacing invalid UUIDs as handled errors; `CheckUUID` is kept but deprecated.
- Harden subscribe response parsing against malformed payloads with checked type assertions that announce a parse error instead of panicking.
- Harden Access Manager grant response parsing to validate all fields and return descriptive errors, and fix TTL parsing to accept JSON numbers.

#### Modified
- Return a single generic error on LegacyCryptor decryption failures to prevent padding-oracle-style information disclosure.
- Change redaction of `SecretKey` to show only first 8 characters and total length of the key.

## v9.0.2
June 23 2026

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9.0.2
9.0.3
24 changes: 22 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,28 @@ func (c *Config) GetUserId() UserId {
return UserId(c.UUID)
}

// secretKeyRedactPrefixLen is the number of leading characters preserved when
// partially redacting a SecretKey for logging (e.g. "sec-c-YT… (len=54)").
const secretKeyRedactPrefixLen = 8

// redactPrefix returns a partially redacted representation of a sensitive value:
// the first prefixLen characters followed by an ellipsis and the total length,
// e.g. "sec-c-YT… (len=54)".
//
// An empty value yields an empty string. To avoid leaking short secrets, any
// value whose length does not exceed prefixLen is fully masked with "***".
func redactPrefix(value string, prefixLen int) string {
if value == "" {
return ""
}
if len(value) <= prefixLen {
return "***"
}
return fmt.Sprintf("%s\u2026 (len=%d)", value[:prefixLen], len(value))
}

// GetLogString returns a formatted string representation of the Config for logging purposes.
// Sensitive fields (SecretKey, CipherKey) are masked with ***.
// Sensitive fields (SecretKey, CipherKey) are masked.
func (c *Config) GetLogString() string {
c.RLock()
defer c.RUnlock()
Expand Down Expand Up @@ -203,7 +223,7 @@ func (c *Config) GetLogString() string {
}`,
c.PublishKey,
c.SubscribeKey,
maskIfNotEmpty(c.SecretKey),
redactPrefix(c.SecretKey, secretKeyRedactPrefixLen),
maskIfNotEmpty(c.AuthKey),
c.Origin,
c.UUID,
Expand Down
27 changes: 10 additions & 17 deletions crypto/legacy_cryptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
)

Expand Down Expand Up @@ -58,12 +57,18 @@ func (c *legacyCryptor) Encrypt(message []byte) (*EncryptedData, error) {
}

func (c *legacyCryptor) Decrypt(encryptedData *EncryptedData) (r []byte, e error) {
defer func() {
if rec := recover(); rec != nil {
r, e = nil, errors.New("decryption error")
}
}()

iv := make([]byte, aes.BlockSize)
data := encryptedData.Data

if c.randomIv {
if len(data) < aes.BlockSize {
return nil, fmt.Errorf("length of data to decrypt should be at least %d", aes.BlockSize)
return nil, errors.New("decryption error")
}

iv = data[:aes.BlockSize]
Expand All @@ -72,26 +77,14 @@ func (c *legacyCryptor) Decrypt(encryptedData *EncryptedData) (r []byte, e error
iv = []byte(valIV)
}

if len(data)%16 != 0 {
return nil, fmt.Errorf("length of data to decrypt should be divisible by block size")
if len(data)%aes.BlockSize != 0 {
return nil, errors.New("decryption error")
}

decrypter := cipher.NewCBCDecrypter(c.block, iv)
//to handle decryption errors
defer func() {
if rec := recover(); rec != nil {
r, e = nil, fmt.Errorf("decrypt error: %s", rec)
}
}()

decrypted := make([]byte, len(data))
decrypter.CryptBlocks(decrypted, data)
val, err := unpadPKCS7(decrypted)
if err != nil {
return nil, fmt.Errorf("decrypt error: %s", err)
}

return val, nil
return unpadPKCS7(decrypted)
}

func (c *legacyCryptor) EncryptStream(reader io.Reader) (*EncryptedStreamData, error) {
Expand Down
10 changes: 9 additions & 1 deletion endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ func defaultQuery(uuid string, telemetryManager *TelemetryManager) *url.Values {
v := &url.Values{}

v.Set("pnsdk", "PubNub-Go/"+Version)
utils.CheckUUID(uuid)
v.Set("uuid", uuid)

for queryName, queryParam := range telemetryManager.OperationLatency() {
Expand All @@ -161,6 +160,15 @@ func buildURL(o endpoint) (*url.URL, error) {
return &url.URL{}, err
}

// Validate the client UUID for every endpoint that sends it (those built
// via defaultQuery). Returning an error here lets callers handle an invalid
// UUID gracefully instead of panicking inside SDK-owned goroutines.
if query.Has("uuid") {
if err := utils.ValidateUUID(query.Get("uuid")); err != nil {
return &url.URL{}, err
}
}

if v := o.tokenManager().GetToken(); v != "" && query.Get("auth") == "" {
query.Set("auth", v)
} else if v := o.config().AuthKey; v != "" && query.Get("auth") == "" {
Expand Down
12 changes: 10 additions & 2 deletions enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,17 @@ type PNPushEnvironment string

// PNLogLevel constants define the available log levels
const (
// PNLogLevelTrace is the enum when the log level is trace
// PNLogLevelTrace is the enum when the log level is trace.
//
// Security warning: like Debug, the Trace level may expose sensitive fields
// (tokens, auth parameters, request URLs) and must not be enabled in
// production environments. See PNLogLevelDebug for details.
PNLogLevelTrace PNLogLevel = 1 + iota
// PNLogLevelDebug is the enum when the log level is debug
// PNLogLevelDebug is the enum when the log level is debug.
//
// Security warning: the Debug log level may expose sensitive fields (tokens,
// auth parameters, request URLs). Debug is intended for developer
// troubleshooting only and must not be enabled in production environments.
PNLogLevelDebug
// PNLogLevelInfo is the enum when the log level is info
PNLogLevelInfo
Expand Down
Loading
Loading