The Locker Go SDK reads and manages Locker Passwords & Secrets through the
stable locker.sdk protocol exposed by the Locker CLI.
The SDK intentionally does not duplicate Locker's HTTP client, cryptography,
or local database. It starts locker sdk, sends one JSON-RPC 2.0 request over
stdin, reads one response from stdout, and terminates the process.
- Go 1.25.12, Go 1.26.5, or a later fully patched supported Go release
- A Locker CLI release that supports SDK protocol v1
LOCKER_ACCESS_KEY_IDLOCKER_SECRET_ACCESS_KEY
Install the library:
go get github.com/lockerpm/secrets-sdk-go/v2/lockerBy default the SDK installs and verifies the current Locker CLI from Locker's
signed release channel. Set
Config.CLIPath or LOCKER_CLI_PATH only when you intentionally supply a
caller-owned binary. Package import never performs I/O; managed resolution
begins when a client is constructed. Use locker.NewWithContext when startup
must be cooperatively cancelled while waiting for signed resolution, network
I/O, or the update lock.
package main
import (
"context"
"log"
locker "github.com/lockerpm/secrets-sdk-go/v2/locker"
)
func main() {
client, err := locker.FromEnv()
if err != nil {
log.Fatal(err)
}
databasePassword, err := client.GetRequired(
context.Background(),
"DATABASE_PASSWORD",
locker.InEnvironment("production"),
)
if err != nil {
log.Fatal(err)
}
// Pass databasePassword directly to the database driver. Never log it.
_ = databasePassword
}ACCESS_KEY_ID, SECRET_ACCESS_KEY, LOCKER_ACCESS_KEY_SECRET, and
ACCESS_KEY_SECRET remain temporary migration aliases. Canonical variables
always win.
FromEnv also reads optional LOCKER_API_BASE; LOCKER_CLI_PATH participates
in binary resolution. Explicit options take precedence.
| Environment variable | Purpose |
|---|---|
LOCKER_ACCESS_KEY_ID |
Project access key ID |
LOCKER_SECRET_ACCESS_KEY |
Project secret access key |
LOCKER_API_BASE |
Cloud or self-hosted API base URL |
LOCKER_CLI_PATH |
Absolute caller-owned CLI path |
For explicit configuration:
client, err := locker.New(locker.Config{
Credentials: locker.Credentials{
AccessKeyID: accessKeyID,
SecretAccessKey: secretAccessKey,
},
CLIPath: "/opt/locker/bin/locker",
Timeout: 20 * time.Second,
Transport: locker.TransportConfig{
APIBase: "https://api.locker.io/locker_secrets",
Headers: map[string]string{
"CF-Access-Client-Id": cfAccessClientID,
},
},
})Configuration maps are copied by New, and a Client is safe for concurrent
use.
Every method accepts a context. The earlier of its deadline/cancellation or
Config.Timeout stops CLI resolution, capability negotiation, and the
operation, then terminates the CLI process tree.
The SDK never automatically retries a vault RPC or API failure. In particular,
create and update are issued once because a lost response can leave the remote
commit outcome unknown. Applications may use RPCError.Retryable for a
bounded retry policy on read-only operations only.
The SDK does not cache plaintext secrets. CacheConfig delegates encrypted,
revision-aware caching to the CLI:
maxAge := 30
client, err := locker.FromEnv(
locker.WithCache(locker.CacheConfig{MaxAgeSeconds: &maxAge}),
)MaxAgeSeconds: &0 disables offline reuse. ForceRefresh: true requires a
successful server refresh and never falls back. A transient outage may use
only a still-fresh cache last validated successfully by the server;
authentication, authorization, TLS, integrity, malformed-response, and local
storage failures fail closed.
All operations accept context.Context, negotiate protocol v1 before the
first vault operation, and return typed stable models.
secret, err := client.GetSecret(ctx, "DATABASE_PASSWORD")
secrets, err := client.ListSecrets(ctx, locker.InEnvironment("production"))
secretPage, err := client.ListSecretsPage(
ctx,
locker.PageRequest{PageSize: 100},
locker.InEnvironment("production"),
)
createdSecret, err := client.CreateSecret(ctx, locker.SecretCreateInput{
Key: "DATABASE_PASSWORD",
Value: value, // Empty values are valid.
Environment: locker.String("production"),
Description: locker.String("Primary database password"),
})
updatedSecret, err := client.UpdateSecret(ctx, locker.SecretUpdateInput{
Key: "DATABASE_PASSWORD",
Environment: locker.String("production"),
Changes: locker.SecretChanges{
Key: locker.String("PRIMARY_DATABASE_PASSWORD"),
Value: locker.String(newValue),
Environment: locker.NullString(), // Clear the environment association.
Description: locker.String(""),
},
})
environment, err := client.GetEnvironment(ctx, "production")
environments, err := client.ListEnvironments(ctx)
environmentPage, err := client.ListEnvironmentsPage(
ctx,
locker.PageRequest{PageSize: 100},
)
createdEnvironment, err := client.CreateEnvironment(
ctx,
locker.EnvironmentCreateInput{
Name: "production",
ExternalURL: locker.String("https://example.com"),
Description: locker.String(""),
},
)
updatedEnvironment, err := client.UpdateEnvironment(
ctx,
locker.EnvironmentUpdateInput{
Name: "production",
Changes: locker.EnvironmentChanges{
Name: locker.String("production-eu"),
ExternalURL: locker.String("https://eu.example.com"),
},
},
)GetOrDefault returns its default only when the CLI returns numeric error
-32004:
value, err := client.GetOrDefault(ctx, "OPTIONAL_KEY", "fallback")Authentication, permission, protocol, network, server, rate-limit, and storage failures always fail closed.
Locker application errors are *locker.RPCError and map by numeric JSON-RPC
code:
_, err := client.CreateSecret(ctx, locker.SecretCreateInput{
Key: "PAYMENT_API_KEY",
Value: paymentAPIKey,
})
if errors.Is(err, locker.ErrAlreadyExists) {
// PAYMENT_API_KEY already exists.
// ErrAlreadyExists is also an ErrConflict.
}
var rpcError *locker.RPCError
if errors.As(err, &rpcError) && rpcError.Retryable {
// Apply the application's bounded retry policy.
// For rate_limited, RetryAfterSeconds may provide a 0..86400 hint.
}The stable protocol taxonomy is:
| Code | Go classification | Canonical kind |
|---|---|---|
-32700 |
ErrProtocol |
parse_error |
-32600 |
ErrProtocol |
invalid_request |
-32601 |
ErrProtocol |
method_not_found |
-32602 |
ErrProtocol |
invalid_params |
-32603 |
ErrProtocol |
internal_protocol_error |
-32000 |
ErrOperation and legacy subtypes |
operation_error, request_rejected, response_too_large, cancelled |
-32001 |
ErrAuthentication |
missing_credentials, invalid_access_key_id, malformed_secret_access_key, invalid_secret_access_key, unauthorized |
-32003 |
ErrPermission |
forbidden; legacy permission_denied |
-32004 |
ErrNotFound |
secret_not_found, environment_not_found; legacy not_found_error |
-32009 |
ErrConflict / ErrAlreadyExists |
conflict, secret_already_exists, environment_already_exists |
-32022 |
ErrValidation |
validation_error |
-32029 |
ErrRateLimited |
rate_limited |
-32050 |
ErrNetwork |
network_error, network_timeout; legacy http_error |
-32051 |
ErrServer |
service_unavailable, internal_error; legacy server_error |
-32060 |
ErrStorage |
database_error, file_error, path_error |
-32070 |
ErrIntegrity |
integrity_error, transport_integrity_error, data_integrity_error; legacy data_error |
Classification is numeric-first. For compatibility with older CLI releases,
the distinctive legacy -32000 kinds duplicate_hash, *_already_exists,
conflict, validation_error, and the integrity aliases retain their typed
classification. request_rejected, response_too_large, and cancelled
remain explicit ErrOperation subtypes and are never guessed to be conflicts.
Known authentication, permission, not-found, conflict, validation, storage,
integrity, protocol, cancellation, and internal-server errors are always
non-retryable. Only rate-limit, network, service-unavailable, or an unknown
server-range code can preserve a true retry hint. The SDK never retries a
vault mutation automatically.
Credential syntax is validated before the CLI is resolved, downloaded, or
launched. Outer whitespace is removed from both values. The access key ID must
be a UUIDv4 and the secret access key must be non-empty canonical standard
Base64. Missing or malformed values classify through ErrAuthentication and
the more specific ErrMissingAccessKeyID, ErrMissingSecretKey,
ErrInvalidAccessKeyID, or ErrMalformedSecretAccessKey sentinel. A
well-formed pair rejected by Locker uses invalid_secret_access_key and the
safe message the secret access key does not match the access key ID; a
backend unauthorized response retains the generic authentication failed
message. All five authentication kinds are non-retryable.
Typed errors use additive capability negotiation. The SDK sends
context.error_contract = "typed-v1" only when system.capabilities
advertises that exact value in error_contracts. An absent list or an
unknown valid contract remains compatible and does not opt in.
When present, a strictly validated ServerRequestID is the upstream service
correlation ID; it is separate from the local JSON-RPC RequestID and is
never included in the default error string.
Transport failures are *locker.TransportError; malformed or incompatible CLI
responses are *locker.ProtocolViolationError. Error strings never include
the request, response, child stderr, credentials, headers, or secret values.
Secret.String and Secret.GoString always redact Value.
Resolution order is:
- absolute
Config.CLIPath - absolute
LOCKER_CLI_PATH - the verified generation selected by
~/.locker/sdk-cli/go/current.json
Legacy names such as locker_secret are never resolved automatically. A
caller may provide its absolute regular non-symlink path explicitly, but
protocol capability negotiation is still mandatory. Bare and relative values
are rejected instead of being searched through ambient PATH.
InstallCLI(ctx) installs from the
signed release channel. Managed
resolution checks immediately on first use and periodically afterward; an
explicit InstallCLI call forces a check. The SDK verifies signed release
metadata, the artifact's SHA-256 hash and Ed25519 signature, and the
executable's declared OS and architecture. Rollback, same-version replacement,
signature, provenance, and integrity failures are rejected.
Managed binaries are reverified before execution, stored in a private per-user cache, and activated atomically only after verification succeeds. A transient network failure may use the last fully verified cached release; verification failures always fail closed. Explicit caller-owned CLI paths remain the caller's trust responsibility and do not use managed-channel verification.
- The CLI receives exactly one non-sensitive argv value:
sdk. - Credentials, custom headers, and mutation values exist only in the JSON request on stdin.
- The child gets a strict allowlist of OS, home, locale, proxy, and certificate environment variables. Locker credentials and arbitrary application variables are stripped.
- stdout and stderr are separately bounded; stderr is never included in SDK errors.
- timeouts and cancellations terminate the CLI process tree.
Config.Timeoutis one end-to-end budget for CLI resolution, capability negotiation, and the vault operation.- malformed, oversized, or incorrectly correlated protocol responses are rejected.
This refactor is a breaking v2 boundary. See MIGRATION.md.
The SDK follows Semantic Versioning. The module major
version is part of the import path, and public releases use
vMAJOR.MINOR.PATCH tags. Breaking API changes require a new major module
path.
ErrAuthentication: verify that the access key ID is UUIDv4, the secret access key is canonical standard Base64, and both values belong to the same active Locker project.ErrMissingAccessKeyID,ErrMissingSecretKey,ErrInvalidAccessKeyID, andErrMalformedSecretAccessKeyidentify local syntax failures.*TransportError: inspect its safe kind, the API base, CA/proxy, and CLI path. Error text intentionally excludes CLI stderr and protocol bodies.- Managed install failure: check system time, access to the
Locker Secrets download service, and
private ownership below
~/.locker/sdk-cli/go. *ProtocolViolationError: upgrade the SDK and CLI together or remove an incompatible explicitLOCKER_CLI_PATH.- Unexpected stale reads: use
ForceRefresh; do not loosen cache permissions.
Product help is available at support.locker.io. Report vulnerabilities through the Locker Bug Bounty program; never attach access keys or plaintext secret values to an issue.
The Locker Go SDK is licensed under the Apache License 2.0.