Skip to content

SecureValue API

The SecureValue type is used for securely storing sensitive data, providing memory locking, auto-zeroing, and masking functionality.

Thread Safety

All methods of SecureValue are thread-safe and can be used concurrently from multiple goroutines:

  • Read methods (String(), Bytes(), Length(), Masked()) use read locks, supporting concurrent reads
  • Close methods (Close(), Release()) use write locks, ensuring safe zeroing
  • Status checks (IsClosed(), IsMemoryLocked()) use atomic operations
go
secret := env.GetSecure("API_KEY")
if secret != nil {
    defer secret.Release()

    // Concurrent reads are safe
    go func() { fmt.Println(secret.Masked()) }()
    go func() { fmt.Println(secret.Length()) }()
}

WARNING

Close() and Release() should only be called once. Repeated calls are safe but no-ops.

Creation

NewSecureValue

go
func NewSecureValue(value string) *SecureValue

Creates a secure value wrapper.

Parameters:

  • value - string value to protect

Returns:

  • *SecureValue - secure value object

Behavior:

  • Uses an object pool to reduce allocations
  • Sets a GC finalizer for automatic zeroing
  • If memory locking is enabled, attempts to lock memory (silently ignored on failure)
go
secret := env.NewSecureValue("my-secret-password")
defer secret.Release()  // or Close()

NewSecureValueStrict

go
func NewSecureValueStrict(value string) (*SecureValue, error)

Creates a secure value, returning an error if memory locking fails.

Parameters:

  • value - string value to protect

Returns:

  • *SecureValue - secure value object
  • error - memory locking error (strict mode only)
go
env.SetMemoryLockEnabled(true)
env.SetMemoryLockStrict(true)

secret, err := env.NewSecureValueStrict("my-secret")
if err != nil {
    // Memory locking failed
    log.Printf("Warning: %v", err)
}
if secret != nil {
    defer secret.Release()
}

GetSecure (Loader Method)

go
func (l *Loader) GetSecure(key string) *SecureValue

Gets a secure value from the loader.

Parameters:

  • key - key name

Returns:

  • *SecureValue - defensive copy of the secure value; caller is responsible for releasing; returns nil if the key doesn't exist or loader is closed
go
secret := loader.GetSecure("API_KEY")
if secret != nil {
    defer secret.Release()
    // Use secret
}

TIP

GetSecure returns a copy of the original value, independent from the parent Loader. The caller is responsible for calling Release() or Close().


Methods

String

go
func (sv *SecureValue) String() string

Returns a masked representation, safe for logging and formatting. Implements the fmt.Stringer interface, preventing accidental key leakage through fmt.Printf, log.Println, or error wrapping.

Returns:

  • string - masked representation (e.g., [SECURE:32 bytes]); returns [NIL] when nil
go
secret := env.GetSecure("PASSWORD")
if secret != nil {
    log.Printf("Password: %s", secret)  // Safe, outputs masked representation
    // Equivalent to log.Printf("Password: %s", secret.Masked())
}

WARNING

String() returns a masked representation, not the plaintext value. To get the plaintext value, use Reveal().


Reveal

go
func (sv *SecureValue) Reveal() string

Returns the plaintext value. The caller is responsible for handling the returned string safely — avoid logging, serializing, or storing to persistent locations. Only use when the actual value is needed for cryptographic operations, API calls, or similar secure processing.

Returns:

  • string - plaintext value; returns empty string when closed or nil
go
secret := env.GetSecure("API_KEY")
if secret != nil {
    defer secret.Release()
    plaintext := secret.Reveal()  // Get plaintext value
    // Use plaintext for API calls and other secure operations
    _ = plaintext
}

DANGER

Reveal() returns a plaintext string. Go strings are immutable and cannot be manually zeroed. Use only when necessary, and avoid logging or storing the return value.


Bytes

go
func (sv *SecureValue) Bytes() []byte

Returns a byte slice copy of the value. The caller is responsible for zeroing it with ClearBytes.

Returns:

  • []byte - byte copy of the value; returns nil when closed
go
secret := env.GetSecure("API_KEY")
if secret != nil {
    data := secret.Bytes()
    defer env.ClearBytes(data)  // Zero after use
    // Use data
}

Length

go
func (sv *SecureValue) Length() int

Returns the length of the value without exposing content.

Returns:

  • int - value length; returns 0 when closed
go
secret := env.GetSecure("API_KEY")
if secret != nil {
    fmt.Printf("API Key length: %d\n", secret.Length())
}

Masked

go
func (sv *SecureValue) Masked() string

Returns a masked value for log output.

Returns:

  • string - masked representation

Output formats:

  • Closed: [CLOSED]
  • Empty value: [SECURE:0 bytes]
  • Normal: [SECURE:N bytes] or [SECURE:N bytes locked] or [SECURE:N bytes lock-failed] or [SECURE:N bytes unlocked]
go
secret := env.GetSecure("API_KEY")
if secret != nil {
    log.Printf("API Key: %s", secret.Masked())
    // Output: API Key: [SECURE:32 bytes]
    // Note: Only when memory locking is enabled (SetMemoryLockEnabled(true)) and locking succeeds,
    // the mask appends the " locked" suffix (also " lock-failed" / " unlocked")
}

MarshalJSON

go
func (sv *SecureValue) MarshalJSON() ([]byte, error)

Implements the json.Marshaler interface. Returns a masked representation, preventing accidental key leakage through reflection-based serializers like json.Marshal. The plaintext never appears in JSON output.

Returns:

  • []byte - JSON-safe masked string (e.g., "[SECURE:32 bytes]"); returns "null" when nil
  • error - always returns nil
go
type Response struct {
    APIKey *env.SecureValue `json:"api_key"`
}

resp := Response{APIKey: env.NewSecureValue("sk-1234567890")}
data, _ := json.Marshal(resp)
// {"api_key":"[SECURE:16 bytes]"}
// Plaintext does not appear in output

TIP

MarshalJSON ensures that even when SecureValue is embedded in a struct and JSON-serialized, the plaintext is not leaked. The output is consistent with String() / Masked().


MarshalText

go
func (sv *SecureValue) MarshalText() ([]byte, error)

Implements the encoding.TextMarshaler interface. Returns a masked representation consistent with String(), preventing accidental key leakage through text-based encoders like encoding/xml, text/template, structured logging, etc.

Returns:

  • []byte - masked string (e.g., "[SECURE:32 bytes]"); returns "[NIL]" when nil
  • error - always returns nil
go
type Config struct {
    Token *env.SecureValue `xml:"token"`
}

cfg := Config{Token: env.NewSecureValue("Bearer xyz")}
data, _ := xml.Marshal(cfg)
// <Config><token>[SECURE:10 bytes]</token></Config>

Close

go
func (sv *SecureValue) Close() error

Securely zeros memory and closes the object.

Returns:

  • error - always returns nil

Behavior:

  • Securely zeros internal data
  • Marks as closed
  • Does not return to the object pool
go
secret := env.GetSecure("TOKEN")
if secret != nil {
    defer secret.Close()
    // After Close, memory is zeroed
}

Release

go
func (sv *SecureValue) Release()

Zeros memory and returns to the object pool.

Behavior:

  • Securely zeros internal data
  • Clears GC finalizer
  • Returns to the object pool for reuse
go
secret := env.GetSecure("KEY")
if secret != nil {
    defer secret.Release()
    // After Release, memory is zeroed and the object is returned to the pool
}

TIP

  • Close() - zeros only, does not return to pool
  • Release() - zeros and returns to pool (recommended for high-frequency scenarios)

IsClosed

go
func (sv *SecureValue) IsClosed() bool

Checks whether the object has been closed.

Returns:

  • bool - whether closed
go
if secret.IsClosed() {
    // Object has been closed, cannot be used
}

IsMemoryLocked

go
func (sv *SecureValue) IsMemoryLocked() bool

Checks whether memory is locked (prevents swapping to disk).

Returns:

  • bool - whether locked
go
if secret.IsMemoryLocked() {
    fmt.Println("Memory is locked, protected from swapping")
}

MemoryLockError

go
func (sv *SecureValue) MemoryLockError() error

Returns the error from the memory locking attempt (if any).

Returns:

  • error - locking error; returns nil on success or if not attempted
go
if err := secret.MemoryLockError(); err != nil {
    log.Printf("Memory lock failed: %v", err)
}

Memory Lock Configuration

SetMemoryLockEnabled

go
func SetMemoryLockEnabled(enabled bool)

Globally enables/disables memory locking. Affects all newly created SecureValues.

Parameters:

  • enabled - whether to enable
go
package main

import "github.com/cybergodev/env"

func main() {
    // Enable at application startup
    env.SetMemoryLockEnabled(true)

    // All subsequent SecureValues will attempt to lock
}

IsMemoryLockEnabled

go
func IsMemoryLockEnabled() bool

Checks whether memory locking is enabled.

Returns:

  • bool - whether enabled
go
if env.IsMemoryLockEnabled() {
    // Memory locking is enabled
}

SetMemoryLockStrict

go
func SetMemoryLockStrict(strict bool)

Sets strict mode. When enabled, NewSecureValueStrict returns an error on locking failure.

Parameters:

  • strict - whether to enable strict mode
go
env.SetMemoryLockEnabled(true)
env.SetMemoryLockStrict(true)

secret, err := env.NewSecureValueStrict("sensitive-data")
if err != nil {
    // Locking failed
}

IsMemoryLockStrict

go
func IsMemoryLockStrict() bool

Checks whether strict mode is enabled.

Returns:

  • bool - whether enabled
go
strict := env.IsMemoryLockStrict()

IsMemoryLockSupported

go
func IsMemoryLockSupported() bool

Checks whether the current platform supports memory locking.

Returns:

  • bool - whether supported
PlatformSupported
Linux
macOS
Windows
FreeBSD
wasm

WARNING

Returning true only means the platform supports it, not that the process has sufficient permissions. Linux requires CAP_IPC_LOCK or root privileges.

go
if env.IsMemoryLockSupported() {
    env.SetMemoryLockEnabled(true)
}

Security Utility Functions

ClearBytes

go
func ClearBytes(b []byte)

Securely zeros a byte slice. Use to immediately zero sensitive data after use.

Parameters:

  • b - byte slice to zero
go
sensitive := []byte("secret-data")
// Use...
env.ClearBytes(sensitive)
// sensitive is now all zeros

IsSensitiveKey

go
func IsSensitiveKey(key string) bool

Checks whether a key name matches sensitive patterns.

Parameters:

  • key - key name

Returns:

  • bool - whether sensitive
go
if env.IsSensitiveKey("DB_PASSWORD") {
    // Sensitive key, handle securely
    secret := env.GetSecure("DB_PASSWORD")
    if secret != nil {
        defer secret.Release()
    }
}

Sensitive patterns: password, secret, token, key, api_key, credential, etc.


MaskValue

go
func MaskValue(key, value string) string

Returns a masked value based on the key's sensitivity.

Parameters:

  • key - key name
  • value - original value

Returns:

  • string - masked value
go
// Sensitive key - returns [MASKED:N chars] format
masked := env.MaskValue("API_KEY", "secret123")
// Returns: [MASKED:9 chars]

// Non-sensitive key - returns original value (truncated if over 20 chars)
masked := env.MaskValue("APP_NAME", "myapp")
// Returns: myapp

MaskKey

go
func MaskKey(key string) string

Masks a key name for logging.

Parameters:

  • key - key name

Returns:

  • string - masked key name
go
masked := env.MaskKey("DB_PASSWORD")
// Returns: DB***

SanitizeForLog

go
func SanitizeForLog(s string) string

Cleans sensitive key-value pair information from a string. Auto-detects and masks sensitive values in key=value format.

Parameters:

  • s - original string

Returns:

  • string - cleaned string
go
// Auto-mask sensitive key-value pairs
msg := "Connected with password=secret123 api_key=abc123"
clean := env.SanitizeForLog(msg)
// Returns: "Connected with password=[MASKED] api_key=[MASKED]"

MaskSensitiveInString

go
func MaskSensitiveInString(s string) string

Masks potential sensitive content in a string. Truncates strings longer than 50 characters.

Parameters:

  • s - original string

Returns:

  • string - masked string
go
// Long strings are truncated (keeps first 47 characters and appends "...")
long := "This is a very long string that exceeds 50 characters"
clean := env.MaskSensitiveInString(long)
// Returns: "This is a very long string that exceeds 50 char..."

TIP

Used for truncating long strings that may contain sensitive data. To auto-mask sensitive key-value pairs, use SanitizeForLog.


Complete Example

go
package main

import (
    "fmt"
    "log"

    "github.com/cybergodev/env"
)

func main() {
    // Check and enable memory locking
    if env.IsMemoryLockSupported() {
        env.SetMemoryLockEnabled(true)
        fmt.Println("Memory locking enabled")
    }

    // Load environment variables
    if err := env.Load(".env"); err != nil {
        log.Printf("Warning: %v", err)
    }

    // Securely get sensitive value
    apiKey := env.GetSecure("API_KEY")
    if apiKey == nil {
        log.Fatal("API_KEY not found")
    }
    defer apiKey.Release()

    // Safe usage
    fmt.Printf("API Key length: %d\n", apiKey.Length())
    fmt.Printf("API Key (masked): %s\n", apiKey.Masked())

    // Check memory lock status
    if apiKey.IsMemoryLocked() {
        fmt.Println("Memory is locked")
    }

    // Check locking error
    if err := apiKey.MemoryLockError(); err != nil {
        fmt.Printf("Memory lock warning: %v\n", err)
    }

    // Pass to other functions
    connectAPI(apiKey.Reveal())

    // Use security utility functions
    logMessage := "Processing with API_KEY=secret"
    safeMessage := env.SanitizeForLog(logMessage)
    fmt.Println(safeMessage)  // Processing with API_KEY=[MASKED]
}

func connectAPI(key string) {
    // Connect with key...
    fmt.Printf("Connecting with key of length %d\n", len(key))
}

Internal Implementation

Object Pool

SecureValue uses sync.Pool to reduce memory allocations:

go
var secureValuePool = sync.Pool{
    New: func() interface{} {
        return &SecureValue{}
    },
}

GC Finalizer

A GC finalizer is set at creation time, ensuring automatic zeroing during garbage collection:

go
runtime.SetFinalizer(sv, (*SecureValue).finalize)

Secure Zeroing

Uses unsafe.Pointer to prevent compiler optimization (must be called while holding the sv.mu lock):

go
func (sv *SecureValue) clearDataLocked() {
    if len(sv.data) == 0 {
        return
    }
    // Unlock memory (if locked)
    if sv.locked {
        internal.UnlockMemory(sv.data)
        sv.locked = false
    }
    dataPtr := unsafe.Pointer(&sv.data[0])
    for i := range sv.data {
        *(*byte)(unsafe.Pointer(uintptr(dataPtr) + uintptr(i))) = 0
    }
    runtime.KeepAlive(sv.data)
    sv.data = nil
    sv.lockErr = nil
}