vaultkeeper 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +122 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -47
- package/dist/index.d.ts +108 -47
- package/dist/index.js +120 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -164,6 +164,29 @@ var IdentityMismatchError = class extends VaultError {
|
|
|
164
164
|
this.currentHash = currentHash;
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
|
+
var ExecError = class extends VaultError {
|
|
168
|
+
/**
|
|
169
|
+
* The command that failed to execute.
|
|
170
|
+
*/
|
|
171
|
+
command;
|
|
172
|
+
constructor(message, command) {
|
|
173
|
+
super(message);
|
|
174
|
+
this.name = "ExecError";
|
|
175
|
+
this.command = command;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var InvalidTokenError = class extends VaultError {
|
|
179
|
+
constructor(message) {
|
|
180
|
+
super(message);
|
|
181
|
+
this.name = "InvalidTokenError";
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var AccessorConsumedError = class extends VaultError {
|
|
185
|
+
constructor(message) {
|
|
186
|
+
super(message);
|
|
187
|
+
this.name = "AccessorConsumedError";
|
|
188
|
+
}
|
|
189
|
+
};
|
|
167
190
|
var InvalidAlgorithmError = class extends VaultError {
|
|
168
191
|
/**
|
|
169
192
|
* The algorithm that was requested.
|
|
@@ -495,7 +518,17 @@ function execCommandFull(command, args, options) {
|
|
|
495
518
|
resolve2({ stdout, stderr, exitCode: code ?? 1 });
|
|
496
519
|
});
|
|
497
520
|
proc.on("error", (error) => {
|
|
498
|
-
|
|
521
|
+
if ("code" in error && error.code === "ENOENT") {
|
|
522
|
+
reject(
|
|
523
|
+
new PluginNotFoundError(
|
|
524
|
+
`'${command}' is not installed or not found in PATH`,
|
|
525
|
+
command,
|
|
526
|
+
""
|
|
527
|
+
)
|
|
528
|
+
);
|
|
529
|
+
} else {
|
|
530
|
+
reject(error);
|
|
531
|
+
}
|
|
499
532
|
});
|
|
500
533
|
});
|
|
501
534
|
}
|
|
@@ -1835,40 +1868,40 @@ async function decryptToken(key, jwe) {
|
|
|
1835
1868
|
plaintext = result.plaintext;
|
|
1836
1869
|
} catch (err) {
|
|
1837
1870
|
const message = err instanceof Error ? err.message : String(err);
|
|
1838
|
-
throw new
|
|
1871
|
+
throw new InvalidTokenError(`JWE decryption failed: ${message}`);
|
|
1839
1872
|
}
|
|
1840
1873
|
let parsed;
|
|
1841
1874
|
try {
|
|
1842
1875
|
parsed = JSON.parse(new TextDecoder().decode(plaintext));
|
|
1843
1876
|
} catch {
|
|
1844
|
-
throw new
|
|
1877
|
+
throw new InvalidTokenError("JWE payload is not valid JSON");
|
|
1845
1878
|
}
|
|
1846
1879
|
const claims = parseVaultClaims(parsed);
|
|
1847
1880
|
if (claims === void 0) {
|
|
1848
|
-
throw new
|
|
1881
|
+
throw new InvalidTokenError("JWE payload does not match VaultClaims schema");
|
|
1849
1882
|
}
|
|
1850
1883
|
return claims;
|
|
1851
1884
|
}
|
|
1852
1885
|
function extractKid(jwe) {
|
|
1853
1886
|
const parts = jwe.split(".");
|
|
1854
1887
|
if (parts.length !== 5) {
|
|
1855
|
-
throw new
|
|
1888
|
+
throw new InvalidTokenError("Invalid JWE compact serialization: expected 5 parts");
|
|
1856
1889
|
}
|
|
1857
1890
|
const headerSegment = parts[0];
|
|
1858
1891
|
if (headerSegment === void 0 || headerSegment === "") {
|
|
1859
|
-
throw new
|
|
1892
|
+
throw new InvalidTokenError("Invalid JWE compact serialization: missing header segment");
|
|
1860
1893
|
}
|
|
1861
1894
|
let headerJson;
|
|
1862
1895
|
try {
|
|
1863
1896
|
headerJson = Buffer.from(headerSegment, "base64url").toString("utf-8");
|
|
1864
1897
|
} catch {
|
|
1865
|
-
throw new
|
|
1898
|
+
throw new InvalidTokenError("Invalid JWE compact serialization: header is not valid Base64URL");
|
|
1866
1899
|
}
|
|
1867
1900
|
let header;
|
|
1868
1901
|
try {
|
|
1869
1902
|
header = JSON.parse(headerJson);
|
|
1870
1903
|
} catch {
|
|
1871
|
-
throw new
|
|
1904
|
+
throw new InvalidTokenError("Invalid JWE compact serialization: header is not valid JSON");
|
|
1872
1905
|
}
|
|
1873
1906
|
if (!isObject2(header)) {
|
|
1874
1907
|
return void 0;
|
|
@@ -1983,6 +2016,12 @@ function replaceInRecord2(record, secret) {
|
|
|
1983
2016
|
return result;
|
|
1984
2017
|
}
|
|
1985
2018
|
function delegatedExec(secret, request) {
|
|
2019
|
+
if (request.command.includes(PLACEHOLDER2)) {
|
|
2020
|
+
throw new ExecError(
|
|
2021
|
+
`The {{secret}} placeholder is not supported in the command field. Use args or env instead.`,
|
|
2022
|
+
request.command
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
1986
2025
|
const args = (request.args ?? []).map((arg) => replacePlaceholder2(arg, secret));
|
|
1987
2026
|
const env = request.env !== void 0 ? replaceInRecord2(request.env, secret) : void 0;
|
|
1988
2027
|
return new Promise((resolve2, reject) => {
|
|
@@ -2008,7 +2047,22 @@ function delegatedExec(secret, request) {
|
|
|
2008
2047
|
resolve2({ stdout, stderr, exitCode: code ?? 1 });
|
|
2009
2048
|
});
|
|
2010
2049
|
proc.on("error", (error) => {
|
|
2011
|
-
|
|
2050
|
+
const isEnoent = error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
2051
|
+
if (isEnoent) {
|
|
2052
|
+
reject(
|
|
2053
|
+
new ExecError(
|
|
2054
|
+
`Command not found: ${request.command}. Verify the command exists and is in PATH.`,
|
|
2055
|
+
request.command
|
|
2056
|
+
)
|
|
2057
|
+
);
|
|
2058
|
+
} else {
|
|
2059
|
+
reject(
|
|
2060
|
+
new ExecError(
|
|
2061
|
+
`Failed to execute command: ${request.command}. ${error instanceof Error ? error.message : String(error)}`,
|
|
2062
|
+
request.command
|
|
2063
|
+
)
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2012
2066
|
});
|
|
2013
2067
|
});
|
|
2014
2068
|
}
|
|
@@ -2027,7 +2081,7 @@ function createSecretAccessor(secretValue) {
|
|
|
2027
2081
|
let consumed = false;
|
|
2028
2082
|
function readImpl(callback) {
|
|
2029
2083
|
if (consumed) {
|
|
2030
|
-
throw new
|
|
2084
|
+
throw new AccessorConsumedError("SecretAccessor has already been consumed \u2014 call getSecret() again to obtain a new accessor");
|
|
2031
2085
|
}
|
|
2032
2086
|
consumed = true;
|
|
2033
2087
|
const buf = Buffer.from(secretValue, "utf8");
|
|
@@ -2301,16 +2355,15 @@ async function runDoctor(options) {
|
|
|
2301
2355
|
const warnings = [];
|
|
2302
2356
|
const nextSteps = [];
|
|
2303
2357
|
for (const { required, result } of resolved) {
|
|
2358
|
+
const reasonSuffix = result.reason !== void 0 ? ` \u2014 ${result.reason}` : "";
|
|
2304
2359
|
if (result.status === "missing") {
|
|
2305
2360
|
if (required) {
|
|
2306
|
-
nextSteps.push(`Install missing required dependency: ${result.name}`);
|
|
2361
|
+
nextSteps.push(`Install missing required dependency: ${result.name}${reasonSuffix}`);
|
|
2307
2362
|
} else {
|
|
2308
|
-
warnings.push(
|
|
2309
|
-
`Optional dependency not found: ${result.name}${result.reason !== void 0 ? ` \u2014 ${result.reason}` : ""}`
|
|
2310
|
-
);
|
|
2363
|
+
warnings.push(`Optional dependency not found: ${result.name}${reasonSuffix}`);
|
|
2311
2364
|
}
|
|
2312
2365
|
} else if (result.status === "version-unsupported") {
|
|
2313
|
-
const msg = `${result.name} version is unsupported${
|
|
2366
|
+
const msg = `${result.name} version is unsupported${reasonSuffix}`;
|
|
2314
2367
|
if (required) {
|
|
2315
2368
|
nextSteps.push(`Upgrade required dependency: ${msg}`);
|
|
2316
2369
|
} else {
|
|
@@ -2397,21 +2450,54 @@ var VaultKeeper = class _VaultKeeper {
|
|
|
2397
2450
|
/**
|
|
2398
2451
|
* Run doctor checks without full initialization.
|
|
2399
2452
|
*
|
|
2400
|
-
*
|
|
2401
|
-
* checks are treated as required
|
|
2402
|
-
*
|
|
2453
|
+
* When called without arguments, uses conservative platform defaults —
|
|
2454
|
+
* all platform-native dependency checks are treated as required. Pass
|
|
2455
|
+
* `{ backends }` to scope checks to only the backends you plan to use.
|
|
2456
|
+
*
|
|
2457
|
+
* @param options - Optional doctor options (e.g. `{ backends }` to scope checks).
|
|
2458
|
+
*/
|
|
2459
|
+
static async doctor(options) {
|
|
2460
|
+
return runDoctor(options);
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Store a secret in the configured backend.
|
|
2464
|
+
*
|
|
2465
|
+
* This is a convenience method that delegates to the active backend's
|
|
2466
|
+
* `store()` method. If a secret with the same name already exists, it is
|
|
2467
|
+
* overwritten.
|
|
2468
|
+
*
|
|
2469
|
+
* @param name - Identifier for the secret.
|
|
2470
|
+
* @param value - The secret value to store.
|
|
2471
|
+
* @public
|
|
2472
|
+
*/
|
|
2473
|
+
async store(name, value) {
|
|
2474
|
+
_VaultKeeper.#validateSecretName(name);
|
|
2475
|
+
const backend = this.#requireBackend();
|
|
2476
|
+
await backend.store(name, value);
|
|
2477
|
+
}
|
|
2478
|
+
/**
|
|
2479
|
+
* Delete a secret from the configured backend.
|
|
2480
|
+
*
|
|
2481
|
+
* This is a convenience method that delegates to the active backend's
|
|
2482
|
+
* `delete()` method.
|
|
2483
|
+
*
|
|
2484
|
+
* @param name - Identifier for the secret to delete.
|
|
2485
|
+
* @public
|
|
2403
2486
|
*/
|
|
2404
|
-
|
|
2405
|
-
|
|
2487
|
+
async delete(name) {
|
|
2488
|
+
_VaultKeeper.#validateSecretName(name);
|
|
2489
|
+
const backend = this.#requireBackend();
|
|
2490
|
+
await backend.delete(name);
|
|
2406
2491
|
}
|
|
2407
2492
|
/**
|
|
2408
|
-
*
|
|
2493
|
+
* Read a stored secret from the backend and mint a JWE token that encapsulates it.
|
|
2409
2494
|
*
|
|
2410
2495
|
* @param secretName - Identifier for the secret
|
|
2411
2496
|
* @param options - Setup options
|
|
2412
2497
|
* @returns Compact JWE string
|
|
2413
2498
|
*/
|
|
2414
2499
|
async setup(secretName, options) {
|
|
2500
|
+
_VaultKeeper.#validateSecretName(secretName);
|
|
2415
2501
|
const backend = this.#requireBackend();
|
|
2416
2502
|
const backendType = options?.backendType ?? backend.type;
|
|
2417
2503
|
const ttlMinutes = options?.ttlMinutes ?? this.#config.defaults.ttlMinutes;
|
|
@@ -2456,7 +2542,10 @@ var VaultKeeper = class _VaultKeeper {
|
|
|
2456
2542
|
* an opaque CapabilityToken.
|
|
2457
2543
|
*
|
|
2458
2544
|
* @param jwe - Compact JWE string from setup()
|
|
2459
|
-
* @returns
|
|
2545
|
+
* @returns Object containing an opaque {@link CapabilityToken} for use with
|
|
2546
|
+
* fetch/exec/getSecret, and a {@link VaultResponse} describing key status.
|
|
2547
|
+
* When the JWE was decrypted with a non-current key,
|
|
2548
|
+
* `vaultResponse.rotatedJwt` contains a re-encrypted JWE for the current key.
|
|
2460
2549
|
*/
|
|
2461
2550
|
async authorize(jwe) {
|
|
2462
2551
|
const kid = extractKid(jwe);
|
|
@@ -2476,13 +2565,13 @@ var VaultKeeper = class _VaultKeeper {
|
|
|
2476
2565
|
}
|
|
2477
2566
|
}
|
|
2478
2567
|
const token = createCapabilityToken(claims);
|
|
2479
|
-
const
|
|
2568
|
+
const vaultResponse = { keyStatus };
|
|
2480
2569
|
if (keyStatus === "previous") {
|
|
2481
2570
|
const currentKey = this.#keyManager.getCurrentKey();
|
|
2482
2571
|
const rotatedJwt = await createToken(currentKey.key, claims, { kid: currentKey.id });
|
|
2483
|
-
|
|
2572
|
+
vaultResponse.rotatedJwt = rotatedJwt;
|
|
2484
2573
|
}
|
|
2485
|
-
return { token,
|
|
2574
|
+
return { token, vaultResponse };
|
|
2486
2575
|
}
|
|
2487
2576
|
/**
|
|
2488
2577
|
* Execute a delegated HTTP fetch, injecting the secret from the token.
|
|
@@ -2650,6 +2739,11 @@ var VaultKeeper = class _VaultKeeper {
|
|
|
2650
2739
|
// ---------------------------------------------------------------------------
|
|
2651
2740
|
// Private helpers
|
|
2652
2741
|
// ---------------------------------------------------------------------------
|
|
2742
|
+
static #validateSecretName(name) {
|
|
2743
|
+
if (name.trim() === "") {
|
|
2744
|
+
throw new VaultError("Secret name must not be empty");
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2653
2747
|
#resolveBackend() {
|
|
2654
2748
|
const enabledBackends = this.#config.backends.filter((b) => b.enabled);
|
|
2655
2749
|
if (enabledBackends.length === 0) {
|
|
@@ -2708,15 +2802,18 @@ var VaultKeeper = class _VaultKeeper {
|
|
|
2708
2802
|
}
|
|
2709
2803
|
};
|
|
2710
2804
|
|
|
2805
|
+
exports.AccessorConsumedError = AccessorConsumedError;
|
|
2711
2806
|
exports.AuthorizationDeniedError = AuthorizationDeniedError;
|
|
2712
2807
|
exports.BackendLockedError = BackendLockedError;
|
|
2713
2808
|
exports.BackendRegistry = BackendRegistry;
|
|
2714
2809
|
exports.BackendUnavailableError = BackendUnavailableError;
|
|
2715
2810
|
exports.CapabilityToken = CapabilityToken;
|
|
2716
2811
|
exports.DeviceNotPresentError = DeviceNotPresentError;
|
|
2812
|
+
exports.ExecError = ExecError;
|
|
2717
2813
|
exports.FilesystemError = FilesystemError;
|
|
2718
2814
|
exports.IdentityMismatchError = IdentityMismatchError;
|
|
2719
2815
|
exports.InvalidAlgorithmError = InvalidAlgorithmError;
|
|
2816
|
+
exports.InvalidTokenError = InvalidTokenError;
|
|
2720
2817
|
exports.KeyRevokedError = KeyRevokedError;
|
|
2721
2818
|
exports.KeyRotatedError = KeyRotatedError;
|
|
2722
2819
|
exports.PluginNotFoundError = PluginNotFoundError;
|