lockersm
v2.0.6
Published
Official Locker Secrets SDK for Node.js
Readme
Locker Secrets SDK for Node.js
The official Node.js SDK for Locker Passwords & Secrets Management. It uses
the versioned locker.sdk protocol exposed by the Locker CLI and supports both
ES modules and CommonJS.
Requirements
- Node.js 22.20+ or Node.js 24 LTS
- A Locker access key
- A compatible Locker CLI binary
Installing or importing the npm package never performs a network download.
An explicit absolute, regular, non-link CLI path through cliPath or
LOCKER_CLI_PATH always bypasses managed updates. Bare and relative values
are rejected instead of being searched through ambient PATH. Otherwise, the
first SDK client use checks the
signed release channel and periodically
checks for a newer compatible release. You can also force a check:
npx lockersm-installThe SDK verifies signed release metadata, the CLI artifact's SHA-256 hash and Ed25519 signature, and its declared OS and architecture. Rollback, same-version replacement, signature, provenance, and integrity failures are rejected. Resolution order is:
- Constructor
cliPath LOCKER_CLI_PATH- The latest fully verified managed release
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 skip managed-channel verification.
The legacy auto-downloaded locker_secret binary is never selected
automatically because it has no protocol-v1 provenance. During migration it
can only be selected explicitly; capability negotiation still rejects a
binary that does not implement the required protocol.
Installation
npm install lockersmMigrating from 1.x
Version 2 requires a supported Node.js LTS line (22.20+ or 24.x) and a Locker
CLI that exposes protocol v1.
Package installation no longer runs a postinstall downloader. Let the first
SDK use resolve the latest signed CLI, run npx lockersm-install, or set
cliPath / LOCKER_CLI_PATH to a trusted CLI. The legacy locker_secret
binary is not auto-selected.
get and getSync now return a default only for NOT_FOUND; other failures
raise typed errors. Use getRequired or getRequiredSync wherever a missing
secret must stop application startup.
Configuration
Use the canonical Locker environment variables:
| 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 |
Migration lookup uses this fixed precedence: LOCKER_ACCESS_KEY_ID then
ACCESS_KEY_ID, and LOCKER_SECRET_ACCESS_KEY then SECRET_ACCESS_KEY, then
LOCKER_ACCESS_KEY_SECRET, then ACCESS_KEY_SECRET.
The SDK trims outer credential whitespace, requires the access key ID to be an
exact UUIDv4, and requires a non-empty canonical standard-Base64 secret access
key. Invalid local credentials fail as a non-retryable
LockerAuthenticationError before the SDK resolves or launches a CLI binary.
import { Locker } from 'lockersm'
const locker = Locker.fromEnv()An explicit configuration is also supported:
const locker = new Locker({
accessKeyId: process.env.LOCKER_ACCESS_KEY_ID!,
secretAccessKey: process.env.LOCKER_SECRET_ACCESS_KEY!,
apiBase: 'https://api.locker.io/locker_secrets',
cliPath: process.env.LOCKER_CLI_PATH,
timeoutMs: 30_000,
})Credentials, headers and secret values are serialized into one JSON request
on the CLI's stdin. The child process receives only one argument: sdk.
Reading secrets
const value = await locker.get('DATABASE_PASSWORD', 'production')
const syncValue = locker.getSync('DATABASE_PASSWORD', 'production')get and getSync return undefined or the supplied default only when Locker
returns NOT_FOUND. Authentication, permission, network, storage and protocol
errors are never converted into a default.
For configuration that must exist, use the fail-closed accessors:
const value = await locker.getRequired('DATABASE_PASSWORD', 'production')
const syncValue = locker.getRequiredSync('DATABASE_PASSWORD', 'production')Retrieve the full typed resource or list resources:
const secret = await locker.retrieve('DATABASE_PASSWORD', 'production')
const secrets = await locker.list('production')
const environments = await locker.listEnvironments()Every read operation also has the existing synchronous variant.
Node.js inspection and Secret#toString() redact the value. Explicit
Secret#toJSON() and SDK export methods include plaintext by design, so their
results must be handled as sensitive data.
For large collections, use the bounded page APIs and continue until
nextCursor is null:
let cursor: string | undefined
do {
const page = await locker.listPage({ pageSize: 100, cursor }, 'production')
for (const secret of page.items) {
useSecret(secret)
}
cursor = page.nextCursor ?? undefined
} while (cursor !== undefined)listEnvironmentsPage, listPageSync, and
listEnvironmentsPageSync use the same opaque cursor contract.
Creating and updating
await locker.create({
key: 'DATABASE_PASSWORD',
value: 'secret value',
environmentName: 'production',
description: 'Primary database password',
})
await locker.modify('DATABASE_PASSWORD', 'production', {
newKey: 'PRIMARY_DATABASE_PASSWORD',
value: 'new value',
environmentName: null, // clear the environment association
description: '',
})
await locker.createEnvironment({
name: 'production',
externalUrl: 'https://example.com',
})
await locker.modifyEnvironment('production', {
newName: 'production-eu',
externalUrl: 'https://eu.example.com',
})Cache, timeout and cancellation
Cache settings can be configured globally or per call. Per-call values,
including false and 0, override the global defaults.
const controller = new AbortController()
const secrets = await locker.list(undefined, {
fetch: true,
restTime: 0,
timeoutMs: 5_000,
signal: controller.signal,
})
controller.abort()The SDK bounds request, stdout and stderr sizes and terminates the CLI process
tree when an asynchronous call times out or is cancelled. Synchronous calls
support a bounded timeout and detect an already-aborted signal. A call uses one
total timeout budget across capability negotiation and the vault operation;
each subprocess receives only the remaining time.
maxBufferBytes may lower the 20 MiB protocol response ceiling but cannot
raise it.
The SDK does not automatically retry a vault RPC. Create and update are sent
once because a lost response can leave the remote commit outcome unknown.
Applications may inspect error.retryable and add bounded retry only around
read-only operations.
restTime controls the CLI's encrypted, revision-aware vault cache; the SDK
does not retain plaintext secret values. restTime: 0 disables offline reuse,
and fetch: true requires a successful server refresh with no cache fallback.
A transient outage may reuse only a still-fresh cache last validated
successfully by the server. Authentication, authorization, TLS, integrity,
malformed-response, and local-storage failures fail closed.
Error handling
Protocol errors retain the JSON-RPC code, Locker kind, retryability and request ID:
import {
LockerAlreadyExistsError,
LockerAuthenticationError,
LockerConflictError,
LockerNotFoundError,
LockerRateLimitError,
LockerTransportError,
} from 'lockersm'
try {
await locker.create({
key: 'PAYMENT_API_KEY',
value: paymentApiKey,
})
} catch (error) {
if (error instanceof LockerAlreadyExistsError) {
// PAYMENT_API_KEY already exists.
// This is also a LockerConflictError.
} else if (error instanceof LockerNotFoundError) {
// Secret does not exist.
} else if (error instanceof LockerAuthenticationError) {
// Inspect error.kind without exposing either credential:
// missing_credentials, invalid_access_key_id,
// malformed_secret_access_key, invalid_secret_access_key, or unauthorized.
} else if (error instanceof LockerRateLimitError && error.retryable) {
// retryAfterSeconds is an optional validated 0..86400 hint.
} else if (error instanceof LockerTransportError) {
// The CLI process did not complete a valid protocol exchange.
}
}| Protocol code | JavaScript error | Canonical kind |
| ------------: | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| -32700 | LockerProtocolError | parse_error |
| -32600 | LockerProtocolError | invalid_request |
| -32601 | LockerProtocolError | method_not_found |
| -32602 | LockerProtocolError | invalid_params |
| -32603 | LockerProtocolError | internal_protocol_error |
| -32000 | LockerError and legacy subtypes | operation_error, request_rejected, response_too_large, cancelled |
| -32001 | LockerAuthenticationError | missing_credentials, invalid_access_key_id, malformed_secret_access_key, invalid_secret_access_key, unauthorized |
| -32003 | LockerPermissionError | forbidden; legacy permission_denied |
| -32004 | LockerNotFoundError | secret_not_found, environment_not_found; legacy not-found aliases |
| -32009 | LockerConflictError / LockerAlreadyExistsError | conflict, secret_already_exists, environment_already_exists |
| -32022 | LockerValidationError | validation_error |
| -32029 | LockerRateLimitError | rate_limited |
| -32050 | LockerNetworkError | network_error, network_timeout; legacy http_error |
| -32051 | LockerServerError | service_unavailable, internal_error; legacy server_error |
| -32060 | LockerStorageError | database_error, file_error, path_error |
| -32070 | LockerIntegrityError | integrity, transport-integrity, and data-integrity kinds |
Classification is numeric-first. Distinctive kinds from older CLI releases
(duplicate_hash, *_already_exists, conflict, validation_error, and
the integrity aliases) are also mapped when their legacy code is -32000.
request_rejected, response_too_large, and cancelled have explicit
subtypes but are never guessed to be conflicts. All -32000 errors and known
authentication, permission, not-found, conflict, validation, storage,
integrity, protocol, cancellation, and internal-server errors expose
retryable === false. Only rate-limit, network, service-unavailable, or an
unknown server-range code can preserve a true hint. The SDK never replays a
vault RPC automatically.
The SDK opts into typed errors only after system.capabilities advertises the
exact typed-v1 value in error_contracts; absent and unknown valid
contracts remain compatible and are not sent in operation context.
serverRequestId is a separately validated upstream correlation ID. It never
replaces the local JSON-RPC requestId and is not included in default error
text.
Error objects and logs never retain protocol bodies, raw CLI messages, or CLI output. Upgrade the CLI to receive the unambiguous numeric codes above.
Import and export
The compatibility import and export APIs remain available. File parsing and
formatting happen inside the SDK; every vault operation still uses protocol v1.
await locker.import('.env.production')
await locker.export({
outputFile: '.env.production',
format: 'env',
env: 'production',
})Import accepts dotenv assignments and INI-style environment sections. Export
supports txt, env and json.
Versioning and releases
The package follows Semantic Versioning. npm packages
use MAJOR.MINOR.PATCH versions and source releases use matching
vMAJOR.MINOR.PATCH tags.
Security
Report security issues through the Locker Bug Bounty program. Do not include access keys or plaintext secrets in issue trackers. Product help is available at support.locker.io.
Troubleshooting
missing_credentials: set both canonical credential environment variables.invalid_access_key_idormalformed_secret_access_key: correct the local UUIDv4/Base64 credential format.invalid_secret_access_keyorunauthorized: replace the credential pair or verify that it has not been revoked.- Permission errors: verify the credential's project/environment scope.
LockerTransportError: check the API base, CA/proxy, timeout, and absolute CLI path; protocol bodies and CLI stderr are intentionally unavailable.- Managed install failure: check system time, access to the
Locker Secrets download service, and
private ownership below
~/.locker/sdk-cli/nodejs. - Protocol failure: upgrade the SDK and CLI together or remove an incompatible
explicit
LOCKER_CLI_PATH. - Unexpected stale reads: use
fetch: true; do not loosen cache permissions.
License
Apache-2.0
