Release Notes - v0.6.0¶
Release Date: 2026-10-25
Overview¶
OmniVault v0.6.0 is a security release. Secrets no longer print their value when formatted or logged, and the file provider keeps secret paths inside its directory and refuses secret files that other users can read. It also adds a SQL table-backed provider for applications that need a self-contained store before moving to an external vault.
This release contains breaking changes. Most importantly, Secret.String is removed: code that reads a secret's value must call Secret.Reveal instead. See Upgrading.
Highlights¶
- Secrets print as
[REDACTED]throughfmtandlog/slog Secret.Stringremoved;Secret.Revealreturns the value- File provider refuses paths that leave its directory and secret files readable by group or others
- New
sql://provider storing encrypted secrets in an application-owned SQL table
Security Fixes¶
Secrets redacted when formatted or logged¶
Secret.String returned the plaintext value, so passing a secret to fmt, wrapping it into an error with %v, or logging it with log/slog wrote the value into logs. Formatting a Secret value (not a pointer) also printed its Value and Fields.
GoString, Format and LogValue now produce [REDACTED] (the constant vault.Redacted) for both Secret and *Secret, for every fmt verb (%v, %+v, %#v, %s, %q, %x, ...) and for log/slog. String is removed rather than redacted, so code that called it to read the value fails to compile instead of silently using the redaction marker as the secret. JSON encoding is unchanged, since providers use it to store and transfer secrets.
File provider: paths stay inside the directory¶
The file provider joined the secret path onto its directory without checking the result, so a path such as ../other/secret read, wrote or deleted files outside the directory, and a symbolic link inside the directory could point anywhere.
Secret paths must now be local: non-empty, not absolute, and with no .. segments that leave the directory. Other paths fail with vault.ErrInvalidPath (also omnivault.ErrInvalidPath). Files are opened through os.Root, which also refuses symbolic links that resolve outside the directory.
File provider: secret files must be private¶
The file provider wrote secret files with a private mode but read any file regardless of its mode, so a secret left group- or world-readable was accepted silently.
Get now checks the mode of the opened file and returns an error matching file.ErrInsecurePermissions when group or others can access it (any bit in 0077). The new Config.AllowInsecurePermissions restores the previous behaviour where the mode cannot be changed. Platforms that do not enforce file modes, such as Windows, are unaffected.
What's New¶
Added¶
Secret.Reveal: Returns the primary value:ValueBytesas a string if set, otherwiseValuevault.Redacted: The marker a secret prints asfile.Config.AllowInsecurePermissionsandfile.ErrInsecurePermissions- SQL Store Provider (
providers/sqlstore): AES-GCM encrypted secrets in an application-owned table, for MySQL-compatible databases (including Dolt), PostgreSQL and SQLite, with optional auto-migration and a read-only mode ProviderSQLand theSQLConfigalias, sosql://URIs resolve through the same config-based dispatch as the other built-in providers
Changed¶
- The module now requires Go 1.26
- Bump
github.com/grokify/oscompatfrom 0.3.0 to 0.9.0 - Bump
golang.org/x/cryptofrom 0.52.0 to 0.57.0 - Bump
golang.org/x/termfrom 0.43.0 to 0.46.0
Usage¶
Reading a Secret¶
secret, err := client.Get(ctx, "database/password")
if err != nil {
return err
}
dsn := buildDSN(secret.Reveal()) // the value
raw := secret.Bytes() // the value as bytes
log.Printf("loaded %v", secret) // loaded [REDACTED]
slog.Info("loaded", "secret", secret) // secret=[REDACTED]
SQL Store Provider¶
import "github.com/plexusone/omnivault/providers/sqlstore"
provider, err := sqlstore.New(sqlstore.Config{
DB: db, // an open *sql.DB; the caller owns it
Table: "app_vault_secrets",
Dialect: sqlstore.DialectPostgres,
EncryptionKey: key, // 16, 24 or 32 bytes, kept outside this database
AutoMigrate: true,
})
if err != nil {
return err
}
resolver.Register("sql", provider)
dsn, err := resolver.Resolve(ctx, "sql://reporting/warehouse-dsn")
The table stores encrypted payloads only. Keep the encryption key outside the same database, for example in env://, the OS keyring or a cloud secret manager.
Installation¶
go get github.com/plexusone/[email protected]
Or install the CLI:
go install github.com/plexusone/omnivault/cmd/[email protected]
Upgrading¶
Many downstream modules use OmniVault. Work through every step below in each module that depends on it, including provider modules that construct or read vault.Secret values.
1. Replace Secret.String() with Secret.Reveal()¶
String no longer exists, so every call is a compile error that points at the line to change:
Reveal returns exactly what String used to return. Bytes() is unchanged if you need the value as bytes.
2. Find values read by formatting a secret¶
Code that read the value by formatting the secret still compiles, but now receives [REDACTED] instead of the value. Search for secrets passed to fmt functions and replace them with Reveal:
// Before: these used to yield the value and now yield "[REDACTED]"
header := fmt.Sprintf("Bearer %s", secret)
value := fmt.Sprint(secret)
// After
header := "Bearer " + secret.Reveal()
value := secret.Reveal()
A search such as the following lists likely places; check each hit where the formatted argument is a vault.Secret or *vault.Secret:
Tests that compared a formatted secret with its value need the same change.
3. File provider: make secret files private¶
Get refuses a secret file that group or others can access. Restrict each file:
Where the mode is outside your control, for example a Kubernetes secret volume mounted with the default mode 0644, set defaultMode: 0600 on the volume. If access is restricted some other way, opt out explicitly:
4. File provider: use paths inside the directory¶
Secret paths are relative to Directory. Paths that are empty, absolute or contain .. segments leaving the directory now fail with vault.ErrInvalidPath, and a symbolic link that resolves outside the directory is refused. Move such secrets into the directory, or point a separate file provider at their location.
5. Update the module¶
Build with Go 1.26 or later, then:
go get github.com/plexusone/[email protected]
go mod tidy