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
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
func NewSecureValue(value string) *SecureValueCreates 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)
secret := env.NewSecureValue("my-secret-password")
defer secret.Release() // or Close()NewSecureValueStrict
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 objecterror- memory locking error (strict mode only)
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)
func (l *Loader) GetSecure(key string) *SecureValueGets 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
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
func (sv *SecureValue) String() stringReturns 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
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
func (sv *SecureValue) Reveal() stringReturns 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
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
func (sv *SecureValue) Bytes() []byteReturns 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
secret := env.GetSecure("API_KEY")
if secret != nil {
data := secret.Bytes()
defer env.ClearBytes(data) // Zero after use
// Use data
}Length
func (sv *SecureValue) Length() intReturns the length of the value without exposing content.
Returns:
int- value length; returns 0 when closed
secret := env.GetSecure("API_KEY")
if secret != nil {
fmt.Printf("API Key length: %d\n", secret.Length())
}Masked
func (sv *SecureValue) Masked() stringReturns 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]
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
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 nilerror- always returns nil
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 outputTIP
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
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 nilerror- always returns nil
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
func (sv *SecureValue) Close() errorSecurely 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
secret := env.GetSecure("TOKEN")
if secret != nil {
defer secret.Close()
// After Close, memory is zeroed
}Release
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
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 poolRelease()- zeros and returns to pool (recommended for high-frequency scenarios)
IsClosed
func (sv *SecureValue) IsClosed() boolChecks whether the object has been closed.
Returns:
bool- whether closed
if secret.IsClosed() {
// Object has been closed, cannot be used
}IsMemoryLocked
func (sv *SecureValue) IsMemoryLocked() boolChecks whether memory is locked (prevents swapping to disk).
Returns:
bool- whether locked
if secret.IsMemoryLocked() {
fmt.Println("Memory is locked, protected from swapping")
}MemoryLockError
func (sv *SecureValue) MemoryLockError() errorReturns the error from the memory locking attempt (if any).
Returns:
error- locking error; returns nil on success or if not attempted
if err := secret.MemoryLockError(); err != nil {
log.Printf("Memory lock failed: %v", err)
}Memory Lock Configuration
SetMemoryLockEnabled
func SetMemoryLockEnabled(enabled bool)Globally enables/disables memory locking. Affects all newly created SecureValues.
Parameters:
enabled- whether to enable
package main
import "github.com/cybergodev/env"
func main() {
// Enable at application startup
env.SetMemoryLockEnabled(true)
// All subsequent SecureValues will attempt to lock
}IsMemoryLockEnabled
func IsMemoryLockEnabled() boolChecks whether memory locking is enabled.
Returns:
bool- whether enabled
if env.IsMemoryLockEnabled() {
// Memory locking is enabled
}SetMemoryLockStrict
func SetMemoryLockStrict(strict bool)Sets strict mode. When enabled, NewSecureValueStrict returns an error on locking failure.
Parameters:
strict- whether to enable strict mode
env.SetMemoryLockEnabled(true)
env.SetMemoryLockStrict(true)
secret, err := env.NewSecureValueStrict("sensitive-data")
if err != nil {
// Locking failed
}IsMemoryLockStrict
func IsMemoryLockStrict() boolChecks whether strict mode is enabled.
Returns:
bool- whether enabled
strict := env.IsMemoryLockStrict()IsMemoryLockSupported
func IsMemoryLockSupported() boolChecks whether the current platform supports memory locking.
Returns:
bool- whether supported
| Platform | Supported |
|---|---|
| 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.
if env.IsMemoryLockSupported() {
env.SetMemoryLockEnabled(true)
}Security Utility Functions
ClearBytes
func ClearBytes(b []byte)Securely zeros a byte slice. Use to immediately zero sensitive data after use.
Parameters:
b- byte slice to zero
sensitive := []byte("secret-data")
// Use...
env.ClearBytes(sensitive)
// sensitive is now all zerosIsSensitiveKey
func IsSensitiveKey(key string) boolChecks whether a key name matches sensitive patterns.
Parameters:
key- key name
Returns:
bool- whether sensitive
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
func MaskValue(key, value string) stringReturns a masked value based on the key's sensitivity.
Parameters:
key- key namevalue- original value
Returns:
string- masked value
// 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: myappMaskKey
func MaskKey(key string) stringMasks a key name for logging.
Parameters:
key- key name
Returns:
string- masked key name
masked := env.MaskKey("DB_PASSWORD")
// Returns: DB***SanitizeForLog
func SanitizeForLog(s string) stringCleans 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
// 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
func MaskSensitiveInString(s string) stringMasks potential sensitive content in a string. Truncates strings longer than 50 characters.
Parameters:
s- original string
Returns:
string- masked string
// 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
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:
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:
runtime.SetFinalizer(sv, (*SecureValue).finalize)Secure Zeroing
Uses unsafe.Pointer to prevent compiler optimization (must be called while holding the sv.mu lock):
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
}Related Documentation
- Constants & Errors - Forbidden keys, sensitive key patterns, error types
- Security Overview - Security architecture and core features
- Production Checklist - Pre-launch security checks
- Loader API - GetSecure method