@hyperbrowser/sdk 1.4.1 → 1.5.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/README.md +88 -5
- package/dist/customer-key.d.ts +71 -0
- package/dist/customer-key.js +394 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -1
- package/dist/sandbox/process.d.ts +3 -3
- package/dist/sandbox/process.js +2 -2
- package/dist/sandbox/secret-references.d.ts +25 -3
- package/dist/sandbox/secret-references.js +86 -14
- package/dist/secrets-envelope.d.ts +2 -1
- package/dist/secrets-envelope.js +3 -1
- package/dist/services/credentials.d.ts +5 -1
- package/dist/services/credentials.js +12 -2
- package/dist/services/sandboxes.js +2 -2
- package/dist/services/secrets.d.ts +44 -3
- package/dist/services/secrets.js +164 -11
- package/dist/services/vaults.d.ts +25 -2
- package/dist/services/vaults.js +37 -1
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/credential.d.ts +14 -5
- package/dist/types/index.d.ts +6 -4
- package/dist/types/sandbox.d.ts +31 -15
- package/dist/types/secret.d.ts +80 -0
- package/dist/types/session.d.ts +8 -6
- package/dist/types/vault.d.ts +38 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -154,10 +154,11 @@ try {
|
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
- `credentials` takes up to 20 items: a secret's name (or `{ secret }`),
|
|
157
|
-
`{ vault }` for
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
`session.credentials` lists the attached
|
|
157
|
+
`{ vault }` for a stored vault, or `{ onePassword }` for an `op_token` secret
|
|
158
|
+
(see [Fill from 1Password](#fill-from-1password)); the API validates names
|
|
159
|
+
and limits. A vault is attached by reference, so each fill uses the secrets
|
|
160
|
+
its entries name at that moment. `session.credentials` lists the attached
|
|
161
|
+
secrets as `{ id, name }`, and `session.vaults` the vaults. Fill a
|
|
161
162
|
single secret with `{ credential: "github-password", fields: [{ selector }] }`;
|
|
162
163
|
with `vault`, each field names its entry in `field`.
|
|
163
164
|
- Each input reports an `outcome`: `filled`, `not_found` (no input matched),
|
|
@@ -339,6 +340,82 @@ console.log(result.fields);
|
|
|
339
340
|
- `CredentialFillTool` can't fill from 1Password; call
|
|
340
341
|
`sessions.credentials.fill` directly (see [Use with LLM SDKs](#use-with-llm-sdks)).
|
|
341
342
|
|
|
343
|
+
### Customer-protected secrets
|
|
344
|
+
|
|
345
|
+
A secret created with `protection: "customer"` is also wrapped with your own
|
|
346
|
+
key, so the platform stores nothing it can open alone. Each version is sealed
|
|
347
|
+
to the platform key as usual, then wrapped locally with your key. To use one,
|
|
348
|
+
unwrap it locally and pass the result. Only the platform can open the
|
|
349
|
+
unwrapped envelope, and it checks that the envelope belongs to the version
|
|
350
|
+
you pinned.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { createCustomerKey } from "@hyperbrowser/sdk";
|
|
354
|
+
|
|
355
|
+
// RSA (2048 bits or more), EC P-256/P-384/P-521, X25519 or X448: a KeyObject,
|
|
356
|
+
// PEM/DER or a JWK. A public key can wrap; unwrapping needs the private key.
|
|
357
|
+
const customerKey = createCustomerKey(process.env.HB_CUSTOMER_KEY_PEM!);
|
|
358
|
+
|
|
359
|
+
await client.secrets.create({ name: "db-pass", value: "hunter2", customerKey });
|
|
360
|
+
await client.secrets.addVersion("db-pass", "hunter3", { customerKey });
|
|
361
|
+
|
|
362
|
+
// A reference pinned to the version unwrapped: { name, version, unwrapped }.
|
|
363
|
+
const dbPass = await client.secrets.unwrap("db-pass", customerKey);
|
|
364
|
+
const sandbox = await client.sandboxes.create({
|
|
365
|
+
imageName: "my-image",
|
|
366
|
+
secrets: { DB_PASS: dbPass },
|
|
367
|
+
});
|
|
368
|
+
await sandbox.exec("node migrate.js", { secrets: { DB_PASS: dbPass } });
|
|
369
|
+
|
|
370
|
+
// A vault's customer-protected entries, unwrapped together. Its other entries
|
|
371
|
+
// are delivered as usual.
|
|
372
|
+
const appEnv = await client.vaults.unwrap("app-env", customerKey);
|
|
373
|
+
await sandbox.processes.start("node worker.js", { vault: appEnv });
|
|
374
|
+
|
|
375
|
+
// A customer-protected op_token, for sandboxes and 1Password fills.
|
|
376
|
+
const token = await client.secrets.unwrap("op-token", customerKey);
|
|
377
|
+
await sandbox.exec("node app.js", {
|
|
378
|
+
onePassword: { token, vault: "Engineering", fields: { DB_PASS: { item: "Postgres", field: "password" } } },
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- `protection` is `"platform"` (the default) or `"customer"`, and is fixed when
|
|
383
|
+
the secret is created. Passing `customerKey` to `create` implies
|
|
384
|
+
`"customer"`. Creating one without a value gives an empty secret; add its
|
|
385
|
+
first version with `addVersion(secret, value, { customerKey })`.
|
|
386
|
+
- The key's `kid` is its RFC 7638 thumbprint unless the JWK has a `kid` or you
|
|
387
|
+
pass `createCustomerKey(key, { kid })`. RSA keys wrap with `RSA-OAEP-256`.
|
|
388
|
+
EC and OKP keys default to `ECDH-ES+A256KW`; pass `{ algorithm: "ECDH-ES" }`
|
|
389
|
+
for direct key agreement. To keep the private key in a KMS or HSM, implement
|
|
390
|
+
`CustomerKey` yourself: `kid`, `wrap(innerEnvelope, encryptionContext)` and
|
|
391
|
+
`unwrap(wrappedEnvelope)` produce and open a compact JWE with protected
|
|
392
|
+
header `{ alg, enc: "A256GCM", kid, ctx }`. `ctx` must be the secret's
|
|
393
|
+
`encryptionContext`. Either method may return a promise.
|
|
394
|
+
- `unwrap(secret, keys, { version })` unwraps the latest version by default.
|
|
395
|
+
`vaults.unwrap(vault, keys)` unwraps the versions the vault's entries
|
|
396
|
+
resolve to now. Both take several keys, such as during a rotation, and use
|
|
397
|
+
the one whose `kid` wrapped each version (`customer_key_mismatch` if none
|
|
398
|
+
did). The unwrapped envelope is checked against the version's `innerDigest`
|
|
399
|
+
locally, and again by the API.
|
|
400
|
+
- An unwrapped reference must pin a number, never `"latest"`. A vault reference
|
|
401
|
+
is refused with `SECRET_VAULT_CHANGED` when an entry was repointed or moved
|
|
402
|
+
to a newer version since it was unwrapped; unwrap it again.
|
|
403
|
+
- Customer-protected secrets can't be typed into browser pages, including as
|
|
404
|
+
vault entries in a fill (`SECRET_REQUIRES_CUSTOMER_UNWRAP`). A
|
|
405
|
+
customer-protected `op_token` can still sign 1Password fills: pass the
|
|
406
|
+
unwrapped reference as `onePassword.token`.
|
|
407
|
+
- Rotate your key with `rewrap(secret, { from, to })`. It unwraps each version
|
|
408
|
+
still wrapped with another key and rewraps it with `to`, all locally; the
|
|
409
|
+
value and the version numbers don't change. It skips versions already
|
|
410
|
+
wrapped with `to`, so an interrupted rotation resumes where it stopped.
|
|
411
|
+
`getWrappedVersion` and `replaceVersionWrapping` are the single-version
|
|
412
|
+
steps, for wrapping done elsewhere.
|
|
413
|
+
- Version details include `outerKid`, the customer key that wrapped them, and
|
|
414
|
+
`retiresAt`. A version whose platform key is retired is refused with
|
|
415
|
+
`SECRET_KEY_RETIRED`: add a new version.
|
|
416
|
+
- Fetching wrapped versions needs `secrets.use`, and rewrapping needs
|
|
417
|
+
`secrets.manage`. Signed-in users must be team owners or admins.
|
|
418
|
+
|
|
342
419
|
### Errors
|
|
343
420
|
|
|
344
421
|
API errors are `HyperbrowserError`s with `statusCode`, `code` and `retryable`
|
|
@@ -347,7 +424,7 @@ own `details` holds the specifics (such as `error.details.details.origin`).
|
|
|
347
424
|
|
|
348
425
|
| Code | Meaning |
|
|
349
426
|
|---|---|
|
|
350
|
-
| `CREDENTIAL_NOT_ATTACHED` | The secret, vault or 1Password token wasn't attached to the session, or
|
|
427
|
+
| `CREDENTIAL_NOT_ATTACHED` | The secret, vault or 1Password token wasn't attached to the session, directly or as an entry of an attached vault. |
|
|
351
428
|
| `CREDENTIAL_ORIGIN_NOT_ALLOWED` | The page isn't on the secret's allowed origins (for 1Password, the request's); nothing was typed. |
|
|
352
429
|
| `CREDENTIAL_NOT_READY` | The secret has no allowed origins or no value. |
|
|
353
430
|
| `CREDENTIAL_PAGE_NOT_FOUND`, `CREDENTIAL_FRAME_NOT_FOUND` | No tab or iframe matched. |
|
|
@@ -367,6 +444,12 @@ own `details` holds the specifics (such as `error.details.details.origin`).
|
|
|
367
444
|
| `SECRET_ONEPASSWORD_AUTH_FAILED` | 1Password refused the token, or the token is malformed or signs in somewhere other than `1password.com`, `.ca` or `.eu` (or their subdomains). |
|
|
368
445
|
| `SECRET_ONEPASSWORD_RATE_LIMITED`, `SECRET_ONEPASSWORD_FAILED` | Retryable. |
|
|
369
446
|
| `SECRET_SOURCES_INVALID` | Bad `vault`/`onePassword`/`secrets`/`env` combination, such as a `onePassword` variable also set by the vault, more than 32 variables, or a vault with no entries. |
|
|
447
|
+
| `SECRET_REQUIRES_CUSTOMER_UNWRAP` | A customer-protected secret was used without its unwrapped envelope, unwrapped with `"latest"` instead of a pinned version, or used where it can't be (a fill). |
|
|
448
|
+
| `SECRET_PROTECTION_MISMATCH` | An unwrapped envelope for a secret that isn't customer-protected, or a version uploaded in the other protection's form. |
|
|
449
|
+
| `SECRET_DIGEST_MISMATCH` | The unwrapped envelope (or a rewrap's `innerDigest`) doesn't match the version. |
|
|
450
|
+
| `SECRET_VERSION_DUPLICATE` | Another version already holds this inner envelope; rewrap that version instead. |
|
|
451
|
+
| `SECRET_VAULT_CHANGED` | A vault entry resolves to another secret or version than the one unwrapped; unwrap the vault again. |
|
|
452
|
+
| `SECRET_KEY_RETIRED` | The version was sealed to a retired platform key; add a new version. |
|
|
370
453
|
| `VAULT_NOT_FOUND`, `VAULT_ENTRY_NOT_FOUND` | |
|
|
371
454
|
| `VAULT_NAME_TAKEN`, `VAULT_LIMIT_REACHED`, `VAULT_ENTRY_LIMIT_REACHED` | |
|
|
372
455
|
| `SECRETS_FEATURE_DISABLED` | The team doesn't have secrets enabled. |
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import type { WrappedSecretVersion } from "./types/secret";
|
|
3
|
+
/**
|
|
4
|
+
* How a customer key wraps an envelope (JWE key management). RSA keys use
|
|
5
|
+
* `RSA-OAEP-256`; EC and X25519/X448 keys use `ECDH-ES+A256KW` unless
|
|
6
|
+
* `ECDH-ES` is chosen. Content is always encrypted with A256GCM.
|
|
7
|
+
*/
|
|
8
|
+
export type CustomerKeyAlgorithm = "RSA-OAEP-256" | "ECDH-ES" | "ECDH-ES+A256KW";
|
|
9
|
+
/**
|
|
10
|
+
* The customer's own key for customer-protected secrets. Each version's
|
|
11
|
+
* envelope, already encrypted to the platform, is wrapped with this key
|
|
12
|
+
* before upload, so nothing the platform stores can be decrypted without it.
|
|
13
|
+
* Each use of the secret passes the envelope unwrapped with this key.
|
|
14
|
+
*
|
|
15
|
+
* `createCustomerKey` builds one from a local key. Implement this interface
|
|
16
|
+
* to keep the private key elsewhere, such as in a KMS or HSM.
|
|
17
|
+
*/
|
|
18
|
+
export interface CustomerKey {
|
|
19
|
+
/**
|
|
20
|
+
* The key's ID, written as the `kid` of every envelope it wraps: 1-256
|
|
21
|
+
* printable ASCII characters.
|
|
22
|
+
*/
|
|
23
|
+
readonly kid: string;
|
|
24
|
+
/**
|
|
25
|
+
* Wrap an inner envelope as a compact JWE (RSA-OAEP-256, ECDH-ES or
|
|
26
|
+
* ECDH-ES+A256KW with A256GCM) whose protected header holds exactly `alg`,
|
|
27
|
+
* `enc`, `kid` and `ctx` (plus `epk`, and optionally `apu` and `apv`, with
|
|
28
|
+
* ECDH-ES), with `ctx` set to `encryptionContext`.
|
|
29
|
+
*/
|
|
30
|
+
wrap(innerEnvelope: string, encryptionContext: string): string | Promise<string>;
|
|
31
|
+
/** Decrypt an envelope this key wrapped and return the inner envelope. */
|
|
32
|
+
unwrap(wrappedEnvelope: string): string | Promise<string>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A key as `createCustomerKey` takes it: a `KeyObject`, a PEM (or DER
|
|
36
|
+
* `Buffer`) private or public key, or a JWK. A public key can only wrap.
|
|
37
|
+
*/
|
|
38
|
+
export type CustomerKeyInput = crypto.KeyObject | string | Buffer | crypto.JsonWebKey;
|
|
39
|
+
export interface CustomerKeyOptions {
|
|
40
|
+
/** Defaults to the JWK's `kid`, or else the key's RFC 7638 thumbprint. */
|
|
41
|
+
kid?: string;
|
|
42
|
+
/** For EC and X25519/X448 keys; defaults to `ECDH-ES+A256KW`. */
|
|
43
|
+
algorithm?: CustomerKeyAlgorithm;
|
|
44
|
+
}
|
|
45
|
+
/** Throws unless `kid` is a key ID the API stores: 1-256 printable ASCII characters. */
|
|
46
|
+
export declare const assertCustomerKeyId: (kid: unknown) => string;
|
|
47
|
+
/**
|
|
48
|
+
* A customer key from a local key: RSA (at least 2048 bits), EC (P-256,
|
|
49
|
+
* P-384 or P-521), X25519 or X448. With only the public key it can wrap
|
|
50
|
+
* (upload versions) but not unwrap (use them).
|
|
51
|
+
*
|
|
52
|
+
* The key ID defaults to the JWK's `kid`, or else the RFC 7638 thumbprint of
|
|
53
|
+
* the public key, so the same key always gets the same ID.
|
|
54
|
+
*/
|
|
55
|
+
export declare const createCustomerKey: (key: CustomerKeyInput, options?: CustomerKeyOptions) => CustomerKey & {
|
|
56
|
+
readonly canUnwrap: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Identifies an inner envelope as the API does: the unpadded base64url
|
|
60
|
+
* SHA-256 of its compact form.
|
|
61
|
+
*/
|
|
62
|
+
export declare const secretEnvelopeDigest: (envelope: string) => string;
|
|
63
|
+
/** @internal Checks that `key` looks like a CustomerKey before anything is sent. */
|
|
64
|
+
export declare const assertCustomerKey: (key: unknown, what?: string) => CustomerKey;
|
|
65
|
+
/**
|
|
66
|
+
* @internal Unwrap a wrapped version with the key among `keys` that wrapped
|
|
67
|
+
* it, and check the result is the inner envelope the version holds.
|
|
68
|
+
*/
|
|
69
|
+
export declare const unwrapVersion: (wrapped: Pick<WrappedSecretVersion, "outerKid" | "innerDigest" | "envelope">, keys: CustomerKey | readonly CustomerKey[], label: string) => Promise<string>;
|
|
70
|
+
/** @internal The key among `keys` that wrapped an envelope with this `kid`. */
|
|
71
|
+
export declare const customerKeyFor: (keys: CustomerKey | readonly CustomerKey[], kid: string, label: string) => CustomerKey;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.customerKeyFor = exports.unwrapVersion = exports.assertCustomerKey = exports.secretEnvelopeDigest = exports.createCustomerKey = exports.assertCustomerKeyId = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const error_1 = require("./error");
|
|
9
|
+
const ENC = "A256GCM";
|
|
10
|
+
const IV_BYTES = 12;
|
|
11
|
+
const TAG_BYTES = 16;
|
|
12
|
+
const CEK_BYTES = 32;
|
|
13
|
+
/** The server's limits: an inner envelope of 64 KiB, an RSA encrypted key of 256 bytes. */
|
|
14
|
+
const MAX_INNER_ENVELOPE_LENGTH = 64 * 1024;
|
|
15
|
+
const MIN_RSA_BITS = 2048;
|
|
16
|
+
const MAX_KID_LENGTH = 256;
|
|
17
|
+
const KID_PATTERN = /^[\x20-\x7E]+$/;
|
|
18
|
+
const AES_KW_IV = Buffer.from("A6A6A6A6A6A6A6A6", "hex");
|
|
19
|
+
const EC_CURVES = {
|
|
20
|
+
prime256v1: "P-256",
|
|
21
|
+
secp384r1: "P-384",
|
|
22
|
+
secp521r1: "P-521",
|
|
23
|
+
};
|
|
24
|
+
const keyError = (message, code = "invalid_customer_key") => new error_1.HyperbrowserError(message, { code, retryable: false });
|
|
25
|
+
const unwrapFailed = (message) => keyError(message, "customer_key_unwrap_failed");
|
|
26
|
+
const base64Url = (data) => Buffer.from(data).toString("base64url");
|
|
27
|
+
const decodeSegment = (segment, what) => {
|
|
28
|
+
const decoded = /^[A-Za-z0-9_-]*$/.test(segment) ? Buffer.from(segment, "base64url") : null;
|
|
29
|
+
if (!decoded || decoded.toString("base64url") !== segment) {
|
|
30
|
+
throw unwrapFailed(`Wrapped envelope ${what} is not canonical base64url`);
|
|
31
|
+
}
|
|
32
|
+
return decoded;
|
|
33
|
+
};
|
|
34
|
+
const uint32 = (value) => {
|
|
35
|
+
const buffer = Buffer.alloc(4);
|
|
36
|
+
buffer.writeUInt32BE(value);
|
|
37
|
+
return buffer;
|
|
38
|
+
};
|
|
39
|
+
const lengthPrefixed = (data) => Buffer.concat([uint32(data.length), data]);
|
|
40
|
+
/** RFC 7518 §4.6.2 Concat KDF with SHA-256, for one 256-bit key. */
|
|
41
|
+
const concatKdf = (sharedSecret, algorithmId, apu, apv) => crypto_1.default
|
|
42
|
+
.createHash("sha256")
|
|
43
|
+
.update(Buffer.concat([
|
|
44
|
+
uint32(1),
|
|
45
|
+
sharedSecret,
|
|
46
|
+
lengthPrefixed(Buffer.from(algorithmId, "ascii")),
|
|
47
|
+
lengthPrefixed(apu),
|
|
48
|
+
lengthPrefixed(apv),
|
|
49
|
+
uint32(CEK_BYTES * 8),
|
|
50
|
+
]))
|
|
51
|
+
.digest();
|
|
52
|
+
/** The members of a public JWK that RFC 7638 thumbprints (and `epk` headers) hold. */
|
|
53
|
+
const publicMembers = (jwk) => {
|
|
54
|
+
switch (jwk.kty) {
|
|
55
|
+
case "RSA":
|
|
56
|
+
return { e: jwk.e, kty: "RSA", n: jwk.n };
|
|
57
|
+
case "EC":
|
|
58
|
+
return { crv: jwk.crv, kty: "EC", x: jwk.x, y: jwk.y };
|
|
59
|
+
default:
|
|
60
|
+
return { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/** The RFC 7638 SHA-256 thumbprint of a public key. */
|
|
64
|
+
const thumbprint = (publicKey) => base64Url(crypto_1.default
|
|
65
|
+
.createHash("sha256")
|
|
66
|
+
.update(JSON.stringify(publicMembers(publicKey.export({ format: "jwk" }))))
|
|
67
|
+
.digest());
|
|
68
|
+
const isJwk = (input) => !!input &&
|
|
69
|
+
typeof input === "object" &&
|
|
70
|
+
!Buffer.isBuffer(input) &&
|
|
71
|
+
!(input instanceof crypto_1.default.KeyObject) &&
|
|
72
|
+
typeof input.kty === "string";
|
|
73
|
+
const loadKeys = (input) => {
|
|
74
|
+
if (input instanceof crypto_1.default.KeyObject) {
|
|
75
|
+
if (input.type === "private") {
|
|
76
|
+
return { publicKey: crypto_1.default.createPublicKey(input), privateKey: input };
|
|
77
|
+
}
|
|
78
|
+
if (input.type === "public") {
|
|
79
|
+
return { publicKey: input };
|
|
80
|
+
}
|
|
81
|
+
throw keyError("A customer key must be an asymmetric key, not a secret key");
|
|
82
|
+
}
|
|
83
|
+
if (isJwk(input)) {
|
|
84
|
+
const jwkKid = typeof input.kid === "string" ? input.kid : undefined;
|
|
85
|
+
try {
|
|
86
|
+
if (typeof input.d === "string") {
|
|
87
|
+
const privateKey = crypto_1.default.createPrivateKey({ key: input, format: "jwk" });
|
|
88
|
+
return { publicKey: crypto_1.default.createPublicKey(privateKey), privateKey, jwkKid };
|
|
89
|
+
}
|
|
90
|
+
return { publicKey: crypto_1.default.createPublicKey({ key: input, format: "jwk" }), jwkKid };
|
|
91
|
+
}
|
|
92
|
+
catch (cause) {
|
|
93
|
+
throw new error_1.HyperbrowserError("The customer key JWK could not be read", {
|
|
94
|
+
code: "invalid_customer_key",
|
|
95
|
+
cause,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (typeof input === "string" || Buffer.isBuffer(input)) {
|
|
100
|
+
try {
|
|
101
|
+
const privateKey = crypto_1.default.createPrivateKey(input);
|
|
102
|
+
return { publicKey: crypto_1.default.createPublicKey(privateKey), privateKey };
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
try {
|
|
106
|
+
return { publicKey: crypto_1.default.createPublicKey(input) };
|
|
107
|
+
}
|
|
108
|
+
catch (cause) {
|
|
109
|
+
throw new error_1.HyperbrowserError("The customer key is not a PEM or DER key", {
|
|
110
|
+
code: "invalid_customer_key",
|
|
111
|
+
cause,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
throw keyError("A customer key must be a KeyObject, a PEM or DER key, or a JWK");
|
|
117
|
+
};
|
|
118
|
+
/** Throws unless `kid` is a key ID the API stores: 1-256 printable ASCII characters. */
|
|
119
|
+
const assertCustomerKeyId = (kid) => {
|
|
120
|
+
if (typeof kid !== "string" || kid.length > MAX_KID_LENGTH || !KID_PATTERN.test(kid)) {
|
|
121
|
+
throw keyError(`A customer key's kid must be 1-${MAX_KID_LENGTH} printable ASCII characters`);
|
|
122
|
+
}
|
|
123
|
+
return kid;
|
|
124
|
+
};
|
|
125
|
+
exports.assertCustomerKeyId = assertCustomerKeyId;
|
|
126
|
+
const keyKind = (publicKey) => {
|
|
127
|
+
const type = publicKey.asymmetricKeyType;
|
|
128
|
+
if (type === "rsa") {
|
|
129
|
+
const bits = publicKey.asymmetricKeyDetails?.modulusLength ?? 0;
|
|
130
|
+
if (bits < MIN_RSA_BITS) {
|
|
131
|
+
throw keyError(`RSA customer keys must be at least ${MIN_RSA_BITS} bits`);
|
|
132
|
+
}
|
|
133
|
+
return { type };
|
|
134
|
+
}
|
|
135
|
+
if (type === "ec") {
|
|
136
|
+
const curve = publicKey.asymmetricKeyDetails?.namedCurve ?? "";
|
|
137
|
+
if (!EC_CURVES[curve]) {
|
|
138
|
+
throw keyError("EC customer keys must be on P-256, P-384 or P-521");
|
|
139
|
+
}
|
|
140
|
+
return { type, curve };
|
|
141
|
+
}
|
|
142
|
+
if (type === "x25519" || type === "x448") {
|
|
143
|
+
return { type };
|
|
144
|
+
}
|
|
145
|
+
throw keyError("A customer key must be RSA, EC (P-256, P-384, P-521), X25519 or X448");
|
|
146
|
+
};
|
|
147
|
+
/** A customer key held in this process. */
|
|
148
|
+
class LocalCustomerKey {
|
|
149
|
+
constructor(publicKey, privateKey, options) {
|
|
150
|
+
this.publicKey = publicKey;
|
|
151
|
+
this.privateKey = privateKey;
|
|
152
|
+
this.kind = keyKind(publicKey);
|
|
153
|
+
const algorithm = options.algorithm ?? (this.kind.type === "rsa" ? "RSA-OAEP-256" : "ECDH-ES+A256KW");
|
|
154
|
+
if ((algorithm === "RSA-OAEP-256") !== (this.kind.type === "rsa")) {
|
|
155
|
+
throw keyError(this.kind.type === "rsa"
|
|
156
|
+
? "RSA customer keys wrap with RSA-OAEP-256"
|
|
157
|
+
: "EC and X25519/X448 customer keys wrap with ECDH-ES or ECDH-ES+A256KW");
|
|
158
|
+
}
|
|
159
|
+
if (!["RSA-OAEP-256", "ECDH-ES", "ECDH-ES+A256KW"].includes(algorithm)) {
|
|
160
|
+
throw keyError(`Unsupported customer key algorithm ${String(algorithm)}`);
|
|
161
|
+
}
|
|
162
|
+
this.algorithm = algorithm;
|
|
163
|
+
this.kid = (0, exports.assertCustomerKeyId)(options.kid ?? options.jwkKid ?? thumbprint(publicKey));
|
|
164
|
+
}
|
|
165
|
+
/** Whether this key can unwrap, that is, holds its private key. */
|
|
166
|
+
get canUnwrap() {
|
|
167
|
+
return this.privateKey !== undefined;
|
|
168
|
+
}
|
|
169
|
+
wrap(innerEnvelope, encryptionContext) {
|
|
170
|
+
if (typeof innerEnvelope !== "string" || innerEnvelope.length === 0) {
|
|
171
|
+
throw keyError("The envelope to wrap must be a non-empty string", "invalid_request");
|
|
172
|
+
}
|
|
173
|
+
if (innerEnvelope.length > MAX_INNER_ENVELOPE_LENGTH) {
|
|
174
|
+
throw keyError("The envelope to wrap is larger than 64 KiB", "invalid_request");
|
|
175
|
+
}
|
|
176
|
+
if (typeof encryptionContext !== "string" || encryptionContext.length === 0) {
|
|
177
|
+
throw keyError("An encryption context is required to wrap an envelope", "invalid_request");
|
|
178
|
+
}
|
|
179
|
+
const header = {
|
|
180
|
+
alg: this.algorithm,
|
|
181
|
+
enc: ENC,
|
|
182
|
+
kid: this.kid,
|
|
183
|
+
ctx: encryptionContext,
|
|
184
|
+
};
|
|
185
|
+
let cek;
|
|
186
|
+
let encryptedKey = Buffer.alloc(0);
|
|
187
|
+
if (this.kind.type === "rsa") {
|
|
188
|
+
cek = crypto_1.default.randomBytes(CEK_BYTES);
|
|
189
|
+
encryptedKey = crypto_1.default.publicEncrypt({
|
|
190
|
+
key: this.publicKey,
|
|
191
|
+
padding: crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING,
|
|
192
|
+
oaepHash: "sha256",
|
|
193
|
+
}, cek);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const ephemeral = this.kind.type === "ec"
|
|
197
|
+
? crypto_1.default.generateKeyPairSync("ec", { namedCurve: this.kind.curve })
|
|
198
|
+
: crypto_1.default.generateKeyPairSync(this.kind.type);
|
|
199
|
+
header.epk = publicMembers(ephemeral.publicKey.export({ format: "jwk" }));
|
|
200
|
+
const shared = crypto_1.default.diffieHellman({
|
|
201
|
+
privateKey: ephemeral.privateKey,
|
|
202
|
+
publicKey: this.publicKey,
|
|
203
|
+
});
|
|
204
|
+
const derived = concatKdf(shared, this.algorithm === "ECDH-ES" ? ENC : this.algorithm, Buffer.alloc(0), Buffer.alloc(0));
|
|
205
|
+
shared.fill(0);
|
|
206
|
+
if (this.algorithm === "ECDH-ES") {
|
|
207
|
+
cek = derived;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
cek = crypto_1.default.randomBytes(CEK_BYTES);
|
|
211
|
+
const wrapper = crypto_1.default.createCipheriv("id-aes256-wrap", derived, AES_KW_IV);
|
|
212
|
+
encryptedKey = Buffer.concat([wrapper.update(cek), wrapper.final()]);
|
|
213
|
+
derived.fill(0);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const protectedHeader = base64Url(Buffer.from(JSON.stringify(header), "utf8"));
|
|
218
|
+
const iv = crypto_1.default.randomBytes(IV_BYTES);
|
|
219
|
+
const cipher = crypto_1.default.createCipheriv("aes-256-gcm", cek, iv);
|
|
220
|
+
cipher.setAAD(Buffer.from(protectedHeader, "ascii"));
|
|
221
|
+
const ciphertext = Buffer.concat([cipher.update(innerEnvelope, "utf8"), cipher.final()]);
|
|
222
|
+
return [
|
|
223
|
+
protectedHeader,
|
|
224
|
+
base64Url(encryptedKey),
|
|
225
|
+
base64Url(iv),
|
|
226
|
+
base64Url(ciphertext),
|
|
227
|
+
base64Url(cipher.getAuthTag()),
|
|
228
|
+
].join(".");
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
cek.fill(0);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
unwrap(wrappedEnvelope) {
|
|
235
|
+
if (!this.privateKey) {
|
|
236
|
+
throw keyError(`Customer key ${this.kid} has no private key, so it can wrap but not unwrap`, "customer_key_private_key_required");
|
|
237
|
+
}
|
|
238
|
+
if (typeof wrappedEnvelope !== "string") {
|
|
239
|
+
throw unwrapFailed("The wrapped envelope must be a string");
|
|
240
|
+
}
|
|
241
|
+
const segments = wrappedEnvelope.split(".");
|
|
242
|
+
if (segments.length !== 5) {
|
|
243
|
+
throw unwrapFailed("The wrapped envelope must have five segments");
|
|
244
|
+
}
|
|
245
|
+
const [headerSegment, keySegment, ivSegment, ciphertextSegment, tagSegment] = segments;
|
|
246
|
+
let header;
|
|
247
|
+
try {
|
|
248
|
+
header = JSON.parse(decodeSegment(headerSegment, "header").toString("utf8"));
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error instanceof error_1.HyperbrowserError)
|
|
252
|
+
throw error;
|
|
253
|
+
throw unwrapFailed("The wrapped envelope header is not JSON");
|
|
254
|
+
}
|
|
255
|
+
if (!header || typeof header !== "object" || Array.isArray(header)) {
|
|
256
|
+
throw unwrapFailed("The wrapped envelope header is not a JSON object");
|
|
257
|
+
}
|
|
258
|
+
if (header.kid !== this.kid) {
|
|
259
|
+
throw keyError(`The envelope was wrapped with key ${JSON.stringify(header.kid)}, not ${JSON.stringify(this.kid)}`, "customer_key_mismatch");
|
|
260
|
+
}
|
|
261
|
+
if (header.enc !== ENC) {
|
|
262
|
+
throw unwrapFailed(`Unsupported wrapped envelope encryption ${String(header.enc)}`);
|
|
263
|
+
}
|
|
264
|
+
const alg = header.alg;
|
|
265
|
+
const encryptedKey = decodeSegment(keySegment, "encrypted key");
|
|
266
|
+
const iv = decodeSegment(ivSegment, "IV");
|
|
267
|
+
const ciphertext = decodeSegment(ciphertextSegment, "ciphertext");
|
|
268
|
+
const tag = decodeSegment(tagSegment, "tag");
|
|
269
|
+
if (iv.length !== IV_BYTES || tag.length !== TAG_BYTES) {
|
|
270
|
+
throw unwrapFailed("The wrapped envelope IV or tag has the wrong length");
|
|
271
|
+
}
|
|
272
|
+
let cek;
|
|
273
|
+
try {
|
|
274
|
+
if (alg === "RSA-OAEP-256" && this.kind.type === "rsa") {
|
|
275
|
+
cek = crypto_1.default.privateDecrypt({
|
|
276
|
+
key: this.privateKey,
|
|
277
|
+
padding: crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING,
|
|
278
|
+
oaepHash: "sha256",
|
|
279
|
+
}, encryptedKey);
|
|
280
|
+
}
|
|
281
|
+
else if ((alg === "ECDH-ES" || alg === "ECDH-ES+A256KW") && this.kind.type !== "rsa") {
|
|
282
|
+
const optional = (name) => header[name] === undefined ? Buffer.alloc(0) : decodeSegment(String(header[name]), name);
|
|
283
|
+
const epk = crypto_1.default.createPublicKey({
|
|
284
|
+
key: header.epk,
|
|
285
|
+
format: "jwk",
|
|
286
|
+
});
|
|
287
|
+
const shared = crypto_1.default.diffieHellman({ privateKey: this.privateKey, publicKey: epk });
|
|
288
|
+
const derived = concatKdf(shared, alg === "ECDH-ES" ? ENC : alg, optional("apu"), optional("apv"));
|
|
289
|
+
shared.fill(0);
|
|
290
|
+
if (alg === "ECDH-ES") {
|
|
291
|
+
cek = derived;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const unwrapper = crypto_1.default.createDecipheriv("id-aes256-wrap", derived, AES_KW_IV);
|
|
295
|
+
cek = Buffer.concat([unwrapper.update(encryptedKey), unwrapper.final()]);
|
|
296
|
+
derived.fill(0);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
throw unwrapFailed(`Customer key ${this.kid} can't unwrap an envelope wrapped with ${String(alg)}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (error instanceof error_1.HyperbrowserError)
|
|
305
|
+
throw error;
|
|
306
|
+
throw new error_1.HyperbrowserError("The wrapped envelope's key could not be decrypted", {
|
|
307
|
+
code: "customer_key_unwrap_failed",
|
|
308
|
+
cause: error,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
if (cek.length !== CEK_BYTES) {
|
|
313
|
+
throw unwrapFailed("The wrapped envelope's content key has the wrong length");
|
|
314
|
+
}
|
|
315
|
+
const decipher = crypto_1.default.createDecipheriv("aes-256-gcm", cek, iv);
|
|
316
|
+
decipher.setAAD(Buffer.from(headerSegment, "ascii"));
|
|
317
|
+
decipher.setAuthTag(tag);
|
|
318
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
if (error instanceof error_1.HyperbrowserError)
|
|
322
|
+
throw error;
|
|
323
|
+
throw new error_1.HyperbrowserError("The wrapped envelope could not be decrypted", {
|
|
324
|
+
code: "customer_key_unwrap_failed",
|
|
325
|
+
cause: error,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
cek.fill(0);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* A customer key from a local key: RSA (at least 2048 bits), EC (P-256,
|
|
335
|
+
* P-384 or P-521), X25519 or X448. With only the public key it can wrap
|
|
336
|
+
* (upload versions) but not unwrap (use them).
|
|
337
|
+
*
|
|
338
|
+
* The key ID defaults to the JWK's `kid`, or else the RFC 7638 thumbprint of
|
|
339
|
+
* the public key, so the same key always gets the same ID.
|
|
340
|
+
*/
|
|
341
|
+
const createCustomerKey = (key, options = {}) => {
|
|
342
|
+
const { publicKey, privateKey, jwkKid } = loadKeys(key);
|
|
343
|
+
return new LocalCustomerKey(publicKey, privateKey, { ...options, jwkKid });
|
|
344
|
+
};
|
|
345
|
+
exports.createCustomerKey = createCustomerKey;
|
|
346
|
+
/**
|
|
347
|
+
* Identifies an inner envelope as the API does: the unpadded base64url
|
|
348
|
+
* SHA-256 of its compact form.
|
|
349
|
+
*/
|
|
350
|
+
const secretEnvelopeDigest = (envelope) => crypto_1.default.createHash("sha256").update(envelope, "utf8").digest("base64url");
|
|
351
|
+
exports.secretEnvelopeDigest = secretEnvelopeDigest;
|
|
352
|
+
/** @internal Checks that `key` looks like a CustomerKey before anything is sent. */
|
|
353
|
+
const assertCustomerKey = (key, what = "customerKey") => {
|
|
354
|
+
const candidate = key;
|
|
355
|
+
if (!candidate ||
|
|
356
|
+
typeof candidate !== "object" ||
|
|
357
|
+
typeof candidate.wrap !== "function" ||
|
|
358
|
+
typeof candidate.unwrap !== "function") {
|
|
359
|
+
throw new error_1.HyperbrowserError(`${what} must be a customer key, such as one from createCustomerKey()`, { code: "invalid_request", retryable: false });
|
|
360
|
+
}
|
|
361
|
+
(0, exports.assertCustomerKeyId)(candidate.kid);
|
|
362
|
+
return candidate;
|
|
363
|
+
};
|
|
364
|
+
exports.assertCustomerKey = assertCustomerKey;
|
|
365
|
+
/**
|
|
366
|
+
* @internal Unwrap a wrapped version with the key among `keys` that wrapped
|
|
367
|
+
* it, and check the result is the inner envelope the version holds.
|
|
368
|
+
*/
|
|
369
|
+
const unwrapVersion = async (wrapped, keys, label) => {
|
|
370
|
+
const key = (0, exports.customerKeyFor)(keys, wrapped.outerKid, label);
|
|
371
|
+
const inner = await key.unwrap(wrapped.envelope);
|
|
372
|
+
if (typeof inner !== "string" || (0, exports.secretEnvelopeDigest)(inner) !== wrapped.innerDigest) {
|
|
373
|
+
throw new error_1.HyperbrowserError(`${label} unwrapped to an envelope that doesn't match its innerDigest; it may have been uploaded with the wrong digest`, { code: "customer_key_unwrap_failed", retryable: false });
|
|
374
|
+
}
|
|
375
|
+
return inner;
|
|
376
|
+
};
|
|
377
|
+
exports.unwrapVersion = unwrapVersion;
|
|
378
|
+
/** @internal The key among `keys` that wrapped an envelope with this `kid`. */
|
|
379
|
+
const customerKeyFor = (keys, kid, label) => {
|
|
380
|
+
const list = (Array.isArray(keys) ? keys : [keys]);
|
|
381
|
+
if (list.length === 0) {
|
|
382
|
+
throw new error_1.HyperbrowserError("Pass at least one customer key", {
|
|
383
|
+
code: "invalid_request",
|
|
384
|
+
retryable: false,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
list.forEach((key, index) => (0, exports.assertCustomerKey)(key, `customerKey[${index}]`));
|
|
388
|
+
const key = list.find((candidate) => candidate.kid === kid);
|
|
389
|
+
if (!key) {
|
|
390
|
+
throw new error_1.HyperbrowserError(`${label} was wrapped with customer key ${JSON.stringify(kid)}, which none of the keys given has`, { code: "customer_key_mismatch", retryable: false });
|
|
391
|
+
}
|
|
392
|
+
return key;
|
|
393
|
+
};
|
|
394
|
+
exports.customerKeyFor = customerKeyFor;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ import { dockerBuildContextFingerprint, DockerBuildContextChangedError, imageBui
|
|
|
2
2
|
export { dockerBuildContextFingerprint, DockerBuildContextChangedError, imageBuildName, dockerImageDigest, DockerCommandError, };
|
|
3
3
|
export type { DockerBuildContextOptions, ImageBuildNameOptions, ImageBuildSource, } from "./image-builds";
|
|
4
4
|
import { HyperbrowserClient, HyperbrowserError } from "./client";
|
|
5
|
+
import { createCustomerKey, secretEnvelopeDigest } from "./customer-key";
|
|
6
|
+
export { createCustomerKey, secretEnvelopeDigest };
|
|
7
|
+
export type { CustomerKey, CustomerKeyAlgorithm, CustomerKeyInput, CustomerKeyOptions, } from "./customer-key";
|
|
5
8
|
export declare const Hyperbrowser: typeof HyperbrowserClient;
|
|
6
9
|
export type Hyperbrowser = HyperbrowserClient;
|
|
7
10
|
export { HyperbrowserClient, HyperbrowserError };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HyperbrowserError = exports.HyperbrowserClient = exports.Hyperbrowser = exports.DockerCommandError = exports.dockerImageDigest = exports.imageBuildName = exports.DockerBuildContextChangedError = exports.dockerBuildContextFingerprint = void 0;
|
|
3
|
+
exports.HyperbrowserError = exports.HyperbrowserClient = exports.Hyperbrowser = exports.secretEnvelopeDigest = exports.createCustomerKey = exports.DockerCommandError = exports.dockerImageDigest = exports.imageBuildName = exports.DockerBuildContextChangedError = exports.dockerBuildContextFingerprint = void 0;
|
|
4
4
|
const image_builds_1 = require("./image-builds");
|
|
5
5
|
Object.defineProperty(exports, "dockerBuildContextFingerprint", { enumerable: true, get: function () { return image_builds_1.dockerBuildContextFingerprint; } });
|
|
6
6
|
Object.defineProperty(exports, "DockerBuildContextChangedError", { enumerable: true, get: function () { return image_builds_1.DockerBuildContextChangedError; } });
|
|
@@ -10,6 +10,9 @@ Object.defineProperty(exports, "DockerCommandError", { enumerable: true, get: fu
|
|
|
10
10
|
const client_1 = require("./client");
|
|
11
11
|
Object.defineProperty(exports, "HyperbrowserClient", { enumerable: true, get: function () { return client_1.HyperbrowserClient; } });
|
|
12
12
|
Object.defineProperty(exports, "HyperbrowserError", { enumerable: true, get: function () { return client_1.HyperbrowserError; } });
|
|
13
|
+
const customer_key_1 = require("./customer-key");
|
|
14
|
+
Object.defineProperty(exports, "createCustomerKey", { enumerable: true, get: function () { return customer_key_1.createCustomerKey; } });
|
|
15
|
+
Object.defineProperty(exports, "secretEnvelopeDigest", { enumerable: true, get: function () { return customer_key_1.secretEnvelopeDigest; } });
|
|
13
16
|
// Export HyperbrowserClient as Hyperbrowser for named imports
|
|
14
17
|
exports.Hyperbrowser = client_1.HyperbrowserClient;
|
|
15
18
|
exports.default = client_1.HyperbrowserClient;
|
|
@@ -24,5 +27,7 @@ if (typeof module !== "undefined" && module.exports) {
|
|
|
24
27
|
module.exports.imageBuildName = image_builds_1.imageBuildName;
|
|
25
28
|
module.exports.dockerImageDigest = image_builds_1.dockerImageDigest;
|
|
26
29
|
module.exports.DockerCommandError = image_builds_1.DockerCommandError;
|
|
30
|
+
module.exports.createCustomerKey = customer_key_1.createCustomerKey;
|
|
31
|
+
module.exports.secretEnvelopeDigest = customer_key_1.secretEnvelopeDigest;
|
|
27
32
|
module.exports.default = client_1.HyperbrowserClient;
|
|
28
33
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RuntimeSSEStream, RuntimeTransport } from "./base";
|
|
2
|
-
import { WireOnePassword, WireSecretReference } from "./secret-references";
|
|
2
|
+
import { WireOnePassword, WireSecretReference, WireVaultSource } from "./secret-references";
|
|
3
3
|
import { SandboxExecParams, SandboxExecOptions, SandboxResolvedSecret, SandboxProcessListParams, SandboxProcessListResponse, SandboxProcessResult, SandboxProcessSignal, SandboxProcessStdinParams, SandboxProcessStreamEvent, SandboxProcessSummary, SandboxProcessWaitParams } from "../types/sandbox";
|
|
4
4
|
export interface RawProcessSummary extends Partial<SandboxProcessSummary> {
|
|
5
5
|
id: string;
|
|
@@ -20,8 +20,8 @@ export interface SecretProcessStartPayload {
|
|
|
20
20
|
timeoutMs?: number;
|
|
21
21
|
runAs?: string;
|
|
22
22
|
secrets?: Record<string, WireSecretReference>;
|
|
23
|
-
/** A vault name; `secrets` is merged over its entries. */
|
|
24
|
-
vault?:
|
|
23
|
+
/** A vault, by name or with unwrapped entries; `secrets` is merged over its entries. */
|
|
24
|
+
vault?: WireVaultSource;
|
|
25
25
|
/** 1Password fields; their variables can't come from any other source. */
|
|
26
26
|
onePassword?: WireOnePassword;
|
|
27
27
|
}
|
package/dist/sandbox/process.js
CHANGED
|
@@ -557,8 +557,8 @@ class SandboxProcessesApi {
|
|
|
557
557
|
throw new error_1.HyperbrowserError("Starting a process with secrets requires a sandbox handle");
|
|
558
558
|
}
|
|
559
559
|
const secrets = (0, secret_references_1.serializeSecretReferences)(params.secrets);
|
|
560
|
-
const vault = params.vault;
|
|
561
|
-
const onePassword = params.onePassword;
|
|
560
|
+
const vault = (0, secret_references_1.serializeVaultSource)(params.vault);
|
|
561
|
+
const onePassword = (0, secret_references_1.serializeOnePassword)(params.onePassword);
|
|
562
562
|
// A clash between env and the vault's variables is reported by the API.
|
|
563
563
|
if (secrets !== undefined)
|
|
564
564
|
(0, secret_references_1.assertNoEnvSecretOverlap)(params.env, secrets);
|