@hyperbrowser/sdk 1.2.2 → 1.4.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.
Files changed (43) hide show
  1. package/README.md +427 -3
  2. package/dist/client.d.ts +2 -0
  3. package/dist/client.js +2 -0
  4. package/dist/error.d.ts +2 -0
  5. package/dist/error.js +4 -1
  6. package/dist/sandbox/process.d.ts +7 -3
  7. package/dist/sandbox/process.js +15 -3
  8. package/dist/sandbox/secret-references.d.ts +4 -0
  9. package/dist/sandbox/secret-references.js +11 -2
  10. package/dist/secret-values.d.ts +14 -0
  11. package/dist/secret-values.js +153 -0
  12. package/dist/secrets-envelope.d.ts +5 -0
  13. package/dist/secrets-envelope.js +17 -7
  14. package/dist/services/credentials.d.ts +24 -0
  15. package/dist/services/credentials.js +48 -0
  16. package/dist/services/sandboxes.d.ts +3 -2
  17. package/dist/services/sandboxes.js +20 -10
  18. package/dist/services/secrets.d.ts +19 -4
  19. package/dist/services/secrets.js +53 -17
  20. package/dist/services/sessions.d.ts +2 -0
  21. package/dist/services/sessions.js +2 -0
  22. package/dist/services/vaults.d.ts +26 -0
  23. package/dist/services/vaults.js +80 -0
  24. package/dist/tools/anthropic.d.ts +1 -0
  25. package/dist/tools/anthropic.js +6 -1
  26. package/dist/tools/gemini.d.ts +56 -0
  27. package/dist/tools/gemini.js +40 -0
  28. package/dist/tools/index.d.ts +92 -0
  29. package/dist/tools/index.js +81 -1
  30. package/dist/tools/openai.d.ts +1 -0
  31. package/dist/tools/openai.js +10 -1
  32. package/dist/tools/schema.d.ts +49 -0
  33. package/dist/tools/schema.js +53 -4
  34. package/dist/types/constants.d.ts +26 -4
  35. package/dist/types/credential.d.ts +120 -0
  36. package/dist/types/credential.js +2 -0
  37. package/dist/types/index.d.ts +4 -2
  38. package/dist/types/sandbox.d.ts +89 -2
  39. package/dist/types/secret.d.ts +61 -3
  40. package/dist/types/session.d.ts +14 -0
  41. package/dist/types/vault.d.ts +55 -0
  42. package/dist/types/vault.js +2 -0
  43. package/package.json +1 -1
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secretPlaintext = exports.checkTotpSeed = void 0;
4
+ const secrets_envelope_1 = require("./secrets-envelope");
5
+ const TOTP_ALGORITHMS = new Set(["SHA1", "SHA256", "SHA512"]);
6
+ const MAX_TOTP_PERIOD_SECONDS = 300;
7
+ /**
8
+ * Unpadded base32 can't end with 1, 3 or 6 characters of a block, so such a
9
+ * seed was copied incompletely. vm-service refuses it too (older hosts
10
+ * dropped the extra characters and gave wrong codes).
11
+ */
12
+ const BASE32_TAIL_LENGTHS = new Set([0, 2, 4, 5, 7]);
13
+ const BYTE_ORDER_MARK = "\uFEFF";
14
+ const INVALID_TOTP = "A TOTP value must be a base32 seed or an otpauth://totp/ URI with a base32 secret, " +
15
+ "and optionally algorithm SHA1, SHA256 or SHA512, digits 6 or 8, and a period of 1 to 300 seconds";
16
+ // vm-service parses seeds with Go: these mirror strings.TrimSpace,
17
+ // url.Parse and url.ParseQuery, so a value is refused here whenever it would
18
+ // be refused there.
19
+ /** Go's unicode.IsSpace: ASCII spaces, U+0085, U+00A0 and the Unicode White_Space spaces. */
20
+ const GO_SPACE = "\t\n\v\f\r \u0085             

   ";
21
+ const trimGoSpace = (value) => {
22
+ let start = 0;
23
+ let end = value.length;
24
+ while (start < end && GO_SPACE.includes(value[start]))
25
+ start++;
26
+ while (end > start && GO_SPACE.includes(value[end - 1]))
27
+ end--;
28
+ return value.slice(start, end);
29
+ };
30
+ const isHex = (char) => char !== undefined && /^[0-9A-Fa-f]$/.test(char);
31
+ /** Every '%' starts a two-digit hex escape, as url.Parse requires outside the query. */
32
+ const hasValidEscapes = (text) => {
33
+ for (let i = 0; i < text.length; i++) {
34
+ if (text[i] === "%" && !(isHex(text[i + 1]) && isHex(text[i + 2])))
35
+ return false;
36
+ }
37
+ return true;
38
+ };
39
+ /** url.QueryUnescape; undefined for a bad escape. Bytes are read as Latin-1. */
40
+ const queryUnescape = (text) => {
41
+ if (!hasValidEscapes(text))
42
+ return undefined;
43
+ return text
44
+ .replace(/\+/g, " ")
45
+ .replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
46
+ };
47
+ /** url.Values.Get after url.ParseQuery: pairs with bad escapes or ';' are skipped. */
48
+ const queryGet = (query, key) => {
49
+ for (const pair of query.split("&")) {
50
+ if (!pair || pair.includes(";"))
51
+ continue;
52
+ const separator = pair.indexOf("=");
53
+ const name = queryUnescape(separator < 0 ? pair : pair.slice(0, separator));
54
+ const value = queryUnescape(separator < 0 ? "" : pair.slice(separator + 1));
55
+ if (name === key && value !== undefined)
56
+ return value;
57
+ }
58
+ return "";
59
+ };
60
+ /** Characters url.Parse accepts in the userinfo before '@'. */
61
+ const USERINFO_PATTERN = /^[A-Za-z0-9\-._:~!$&'()*+,;=%@]*$/;
62
+ /**
63
+ * The otpauth:// URI's query, after the checks url.Parse and vm-service make.
64
+ * `uri` starts with "otpauth://" in any case.
65
+ */
66
+ const otpauthQuery = (uri) => {
67
+ if ([...uri].some((char) => char.charCodeAt(0) < 0x20 || char.charCodeAt(0) === 0x7f)) {
68
+ throw (0, secrets_envelope_1.invalidSecretValue)("A TOTP otpauth:// URI must not contain control characters");
69
+ }
70
+ const hash = uri.indexOf("#");
71
+ const fragment = hash < 0 ? "" : uri.slice(hash + 1);
72
+ const beforeFragment = hash < 0 ? uri : uri.slice(0, hash);
73
+ const question = beforeFragment.indexOf("?");
74
+ const query = question < 0 ? "" : beforeFragment.slice(question + 1);
75
+ const rest = (question < 0 ? beforeFragment : beforeFragment.slice(0, question)).slice("otpauth://".length);
76
+ const slash = rest.indexOf("/");
77
+ const authority = slash < 0 ? rest : rest.slice(0, slash);
78
+ const path = slash < 0 ? "" : rest.slice(slash);
79
+ const at = authority.lastIndexOf("@");
80
+ const userinfo = at < 0 ? "" : authority.slice(0, at);
81
+ const host = (at < 0 ? authority : authority.slice(at + 1)).toLowerCase();
82
+ if (!USERINFO_PATTERN.test(userinfo) || ![userinfo, path, fragment].every(hasValidEscapes)) {
83
+ throw (0, secrets_envelope_1.invalidSecretValue)(INVALID_TOTP);
84
+ }
85
+ if (host === "hotp") {
86
+ throw (0, secrets_envelope_1.invalidSecretValue)("HOTP (otpauth://hotp/) is not supported; a TOTP value must be a base32 seed or an otpauth://totp/ URI");
87
+ }
88
+ if (host !== "totp") {
89
+ throw (0, secrets_envelope_1.invalidSecretValue)(INVALID_TOTP);
90
+ }
91
+ return query;
92
+ };
93
+ /**
94
+ * The bytes a base32 seed decodes to, read the way the platform reads it:
95
+ * spaces, '-', line breaks, lowercase and trailing '=' padding are accepted. Undefined
96
+ * when it isn't ASCII base32 or has an impossible length.
97
+ */
98
+ const base32SeedBytes = (seed) => {
99
+ // Upper-cased one character at a time, as Go does, so "ß" can't turn into
100
+ // "SS" while "ı" still reads as "I".
101
+ const upper = Array.from(seed.replace(/[ \r\n-]/g, ""), (char) => {
102
+ const mapped = char.toUpperCase();
103
+ return mapped.length === 1 ? mapped : char;
104
+ }).join("");
105
+ if (!/^[A-Z2-7]+=*$/.test(upper))
106
+ return undefined;
107
+ const normalized = upper.replace(/=+$/, "");
108
+ return BASE32_TAIL_LENGTHS.has(normalized.length % 8)
109
+ ? Math.floor((normalized.length * 5) / 8)
110
+ : undefined;
111
+ };
112
+ const isIntegerIn = (text, allowed) => /^[+-]?\d+$/.test(text) && allowed(Number(text));
113
+ /**
114
+ * Check a TOTP seed as the platform will read it at fill or process time; the
115
+ * API can't see values, so a bad seed would otherwise only fail then. Seeds
116
+ * under 10 bytes (16 base32 characters) are accepted, though they are
117
+ * unusually short.
118
+ */
119
+ const checkTotpSeed = (value) => {
120
+ let seed = trimGoSpace(value.startsWith(BYTE_ORDER_MARK) ? value.slice(1) : value);
121
+ if (seed.toLowerCase().startsWith("otpauth://")) {
122
+ const query = otpauthQuery(seed);
123
+ const algorithm = queryGet(query, "algorithm");
124
+ const digits = queryGet(query, "digits");
125
+ const period = queryGet(query, "period");
126
+ if ((algorithm && !TOTP_ALGORITHMS.has(algorithm.toUpperCase())) ||
127
+ (digits && !isIntegerIn(digits, (n) => n === 6 || n === 8)) ||
128
+ (period && !isIntegerIn(period, (n) => n >= 1 && n <= MAX_TOTP_PERIOD_SECONDS))) {
129
+ throw (0, secrets_envelope_1.invalidSecretValue)(INVALID_TOTP);
130
+ }
131
+ seed = queryGet(query, "secret");
132
+ }
133
+ else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(seed)) {
134
+ throw (0, secrets_envelope_1.invalidSecretValue)(INVALID_TOTP);
135
+ }
136
+ if (base32SeedBytes(seed) === undefined) {
137
+ throw (0, secrets_envelope_1.invalidSecretValue)(INVALID_TOTP);
138
+ }
139
+ };
140
+ exports.checkTotpSeed = checkTotpSeed;
141
+ /**
142
+ * The plaintext stored for a secret of `type`, after the platform's rules
143
+ * for every value (1 byte to 32 KiB of UTF-8, no NUL) and the type's own.
144
+ * Types this SDK doesn't know get the common rules only.
145
+ */
146
+ const secretPlaintext = (type, value) => {
147
+ const bytes = (0, secrets_envelope_1.secretValueBytes)(value);
148
+ if (type === "totp") {
149
+ (0, exports.checkTotpSeed)(bytes.toString("utf8"));
150
+ }
151
+ return bytes;
152
+ };
153
+ exports.secretPlaintext = secretPlaintext;
@@ -1,11 +1,16 @@
1
+ import { HyperbrowserError } from "./error";
1
2
  import { SecretsPublicKey, SecretValue } from "./types/secret";
2
3
  /** The largest secret value the platform accepts. */
3
4
  export declare const MAX_SECRET_VALUE_BYTES: number;
5
+ /** @internal A value the platform would refuse. Messages never include the value. */
6
+ export declare const invalidSecretValue: (message: string) => HyperbrowserError;
4
7
  /**
5
8
  * Apply the platform's value rules locally, so a bad value is reported before
6
9
  * anything is created rather than when a sandbox starts.
7
10
  */
8
11
  export declare const secretValueBytes: (value: SecretValue) => Buffer;
12
+ /** A plain object, as opposed to an array, bytes or a class instance. */
13
+ export declare const isPlainObject: (value: unknown) => value is Record<string, unknown>;
9
14
  /** Encrypts values to a verified secrets public key. */
10
15
  export declare class SecretsSealer {
11
16
  private readonly kid;
@@ -3,14 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SecretsSealer = exports.secretValueBytes = exports.MAX_SECRET_VALUE_BYTES = void 0;
6
+ exports.SecretsSealer = exports.isPlainObject = exports.secretValueBytes = exports.invalidSecretValue = exports.MAX_SECRET_VALUE_BYTES = void 0;
7
7
  const crypto_1 = __importDefault(require("crypto"));
8
8
  const error_1 = require("./error");
9
9
  /** The largest secret value the platform accepts. */
10
10
  exports.MAX_SECRET_VALUE_BYTES = 32 * 1024;
11
11
  const MIN_SECRETS_KEY_BITS = 3072;
12
12
  const SECRETS_KEY_EXPONENT = 65537;
13
- const invalid = (message) => new error_1.HyperbrowserError(message, { code: "invalid_secret_value", retryable: false });
13
+ /** @internal A value the platform would refuse. Messages never include the value. */
14
+ const invalidSecretValue = (message) => new error_1.HyperbrowserError(message, { code: "invalid_secret_value", retryable: false });
15
+ exports.invalidSecretValue = invalidSecretValue;
14
16
  const base64Url = (data) => Buffer.from(data).toString("base64url");
15
17
  /**
16
18
  * Apply the platform's value rules locally, so a bad value is reported before
@@ -18,27 +20,35 @@ const base64Url = (data) => Buffer.from(data).toString("base64url");
18
20
  */
19
21
  const secretValueBytes = (value) => {
20
22
  if (typeof value !== "string" && !(value instanceof Uint8Array)) {
21
- throw invalid("Secret value must be a string or bytes");
23
+ throw (0, exports.invalidSecretValue)("Secret value must be a string or bytes");
22
24
  }
23
25
  const bytes = Buffer.from(value);
24
26
  if (bytes.length === 0) {
25
- throw invalid("Secret value is empty");
27
+ throw (0, exports.invalidSecretValue)("Secret value is empty");
26
28
  }
27
29
  if (bytes.length > exports.MAX_SECRET_VALUE_BYTES) {
28
- throw invalid(`Secret value is more than ${exports.MAX_SECRET_VALUE_BYTES} bytes`);
30
+ throw (0, exports.invalidSecretValue)(`Secret value is more than ${exports.MAX_SECRET_VALUE_BYTES} bytes`);
29
31
  }
30
32
  if (bytes.includes(0)) {
31
- throw invalid("Secret value must not contain NUL bytes");
33
+ throw (0, exports.invalidSecretValue)("Secret value must not contain NUL bytes");
32
34
  }
33
35
  try {
34
36
  new TextDecoder("utf-8", { fatal: true }).decode(bytes);
35
37
  }
36
38
  catch {
37
- throw invalid("Secret value must be valid UTF-8");
39
+ throw (0, exports.invalidSecretValue)("Secret value must be valid UTF-8");
38
40
  }
39
41
  return bytes;
40
42
  };
41
43
  exports.secretValueBytes = secretValueBytes;
44
+ /** A plain object, as opposed to an array, bytes or a class instance. */
45
+ const isPlainObject = (value) => {
46
+ if (value === null || typeof value !== "object")
47
+ return false;
48
+ const prototype = Object.getPrototypeOf(value);
49
+ return prototype === Object.prototype || prototype === null;
50
+ };
51
+ exports.isPlainObject = isPlainObject;
42
52
  const minimalBytes = (value) => {
43
53
  let start = 0;
44
54
  while (start < value.length - 1 && value[start] === 0)
@@ -0,0 +1,24 @@
1
+ import { CredentialFillResult, FillCredentialParams } from "../types/credential";
2
+ import { BaseService } from "./base";
3
+ /** Fill secrets attached to a browser session into its pages. */
4
+ export declare class CredentialsService extends BaseService {
5
+ /**
6
+ * Type a secret, a vault's entries, or 1Password fields into inputs on one
7
+ * of the session's pages. A TOTP secret types its current code. Only types;
8
+ * never submits, so click the login button or press Enter afterwards.
9
+ * Values never leave the platform: the result lists an outcome per input.
10
+ * Safe to retry; it is not retried automatically.
11
+ *
12
+ * With `onePassword`, each field's `ref` names the 1Password field, the
13
+ * `op_token` secret must have been attached at session creation as
14
+ * `{ onePassword: name }`, and the request's `allowedOrigins` are the
15
+ * origins the values may be typed on. Each field's result carries the
16
+ * `reference` read, and there is no top-level `version`.
17
+ *
18
+ * API errors are `HyperbrowserError`s whose `details` is the error body; the
19
+ * server's own details are in `details.details`, such as `details.details.pages`
20
+ * for `CREDENTIAL_PAGE_AMBIGUOUS`, or `selectors` and `reasons` (by
21
+ * selector) for `SECRET_ONEPASSWORD_REFERENCE_FAILED`.
22
+ */
23
+ fill(sessionId: string, params: FillCredentialParams): Promise<CredentialFillResult>;
24
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CredentialsService = void 0;
4
+ const error_1 = require("../error");
5
+ const secrets_envelope_1 = require("../secrets-envelope");
6
+ const base_1 = require("./base");
7
+ /** The API gives up on a fill after about 18 seconds. */
8
+ const MIN_FILL_TIMEOUT_MS = 30000;
9
+ /** Fill secrets attached to a browser session into its pages. */
10
+ class CredentialsService extends base_1.BaseService {
11
+ /**
12
+ * Type a secret, a vault's entries, or 1Password fields into inputs on one
13
+ * of the session's pages. A TOTP secret types its current code. Only types;
14
+ * never submits, so click the login button or press Enter afterwards.
15
+ * Values never leave the platform: the result lists an outcome per input.
16
+ * Safe to retry; it is not retried automatically.
17
+ *
18
+ * With `onePassword`, each field's `ref` names the 1Password field, the
19
+ * `op_token` secret must have been attached at session creation as
20
+ * `{ onePassword: name }`, and the request's `allowedOrigins` are the
21
+ * origins the values may be typed on. Each field's result carries the
22
+ * `reference` read, and there is no top-level `version`.
23
+ *
24
+ * API errors are `HyperbrowserError`s whose `details` is the error body; the
25
+ * server's own details are in `details.details`, such as `details.details.pages`
26
+ * for `CREDENTIAL_PAGE_AMBIGUOUS`, or `selectors` and `reasons` (by
27
+ * selector) for `SECRET_ONEPASSWORD_REFERENCE_FAILED`.
28
+ */
29
+ async fill(sessionId, params) {
30
+ if (typeof sessionId !== "string" || !sessionId.trim()) {
31
+ throw (0, error_1.invalidRequest)("A session ID is required");
32
+ }
33
+ if (!(0, secrets_envelope_1.isPlainObject)(params)) {
34
+ throw (0, error_1.invalidRequest)("Fill parameters must be an object");
35
+ }
36
+ if ("frame" in params) {
37
+ throw (0, error_1.invalidRequest)("Use frameUrl to select an iframe");
38
+ }
39
+ // Preserve request members so the API can reject unknown or conflicting options.
40
+ const { frameUrl, ...body } = params;
41
+ return this.request(`/session/${encodeURIComponent(sessionId)}/credentials/fill`, {
42
+ method: "POST",
43
+ body: JSON.stringify({ ...body, frame: frameUrl == null ? undefined : { url: frameUrl } }),
44
+ timeout: Math.max(this.timeout, MIN_FILL_TIMEOUT_MS),
45
+ });
46
+ }
47
+ }
48
+ exports.CredentialsService = CredentialsService;
@@ -118,8 +118,9 @@ export declare class SandboxesService extends BaseService {
118
118
  updateNetwork(id: string, policy: SandboxNetworkPolicyPatch): Promise<SandboxNetworkUpdateResult>;
119
119
  unexpose(id: string, port: number): Promise<SandboxUnexposeResult>;
120
120
  /**
121
- * With secrets, a create whose result is unknown is never retried: the
122
- * sandbox may exist and be running its startup command with the secrets.
121
+ * With secrets, a vault or onePassword, a create whose result is unknown is never
122
+ * retried: the sandbox may exist and be running its startup command with
123
+ * the secrets.
123
124
  */
124
125
  private createDetail;
125
126
  }
@@ -71,9 +71,12 @@ const serializeCreateSandboxParams = (params) => {
71
71
  throw new error_1.HyperbrowserError("Unsupported sandbox runtimeClass");
72
72
  if (typeof params.imageName === "string") {
73
73
  const secrets = (0, secret_references_1.serializeSecretReferences)(params.secrets);
74
+ const vault = params.vault;
75
+ const onePassword = params.onePassword;
74
76
  // The startup command with secrets runs only in Firecracker sandboxes.
75
- if (secrets !== undefined && params.runtimeClass === "gvisor-cpu")
76
- throw new error_1.HyperbrowserError("secrets are not supported with runtimeClass gvisor-cpu");
77
+ if ((secrets !== undefined || vault !== undefined || onePassword !== undefined) &&
78
+ params.runtimeClass === "gvisor-cpu")
79
+ throw new error_1.HyperbrowserError("secrets, vault and onePassword are not supported with runtimeClass gvisor-cpu");
77
80
  return {
78
81
  runtimeClass: params.runtimeClass,
79
82
  imageName: params.imageName,
@@ -90,12 +93,16 @@ const serializeCreateSandboxParams = (params) => {
90
93
  allowOut: params.allowOut,
91
94
  denyOut: params.denyOut,
92
95
  secrets,
96
+ vault,
97
+ onePassword,
93
98
  };
94
99
  }
95
100
  const snapshotParams = params;
96
- if (snapshotParams.secrets !== undefined) {
101
+ if (snapshotParams.secrets !== undefined ||
102
+ snapshotParams.vault !== undefined ||
103
+ snapshotParams.onePassword !== undefined) {
97
104
  // Snapshot launches do not run a startup command.
98
- throw new error_1.HyperbrowserError("secrets are only supported for image launches");
105
+ throw new error_1.HyperbrowserError("secrets, vault and onePassword are only supported for image launches");
99
106
  }
100
107
  if (snapshotParams.cpu !== undefined ||
101
108
  snapshotParams.memoryMiB !== undefined ||
@@ -320,7 +327,9 @@ class SandboxHandle {
320
327
  return this.processes.get(processId);
321
328
  }
322
329
  hydrate(detail) {
323
- this.detail = detail;
330
+ // Only the create response reports what the startup command received.
331
+ const startupSecrets = detail.startupSecrets ?? this.detail?.startupSecrets;
332
+ this.detail = startupSecrets === undefined ? detail : { ...detail, startupSecrets };
324
333
  this.runtimeSession = SandboxHandle.toRuntimeSession(detail);
325
334
  }
326
335
  async resolveRuntimeConnection(forceRefresh = false) {
@@ -1120,24 +1129,25 @@ class SandboxesService extends base_2.BaseService {
1120
1129
  }
1121
1130
  }
1122
1131
  /**
1123
- * With secrets, a create whose result is unknown is never retried: the
1124
- * sandbox may exist and be running its startup command with the secrets.
1132
+ * With secrets, a vault or onePassword, a create whose result is unknown is never
1133
+ * retried: the sandbox may exist and be running its startup command with
1134
+ * the secrets.
1125
1135
  */
1126
1136
  async createDetail(params) {
1127
1137
  const body = serializeCreateSandboxParams(params);
1138
+ const withSecrets = body.secrets !== undefined || body.vault !== undefined || body.onePassword !== undefined;
1128
1139
  try {
1129
1140
  const detail = await this.request("/sandbox", {
1130
1141
  method: "POST",
1131
1142
  body: JSON.stringify(body),
1132
1143
  });
1133
- if (body.secrets !== undefined &&
1134
- (typeof detail?.id !== "string" || !detail.id.trim())) {
1144
+ if (withSecrets && (typeof detail?.id !== "string" || !detail.id.trim())) {
1135
1145
  throw new Error("The API accepted the create but returned no sandbox ID");
1136
1146
  }
1137
1147
  return normalizeSandboxDetail(detail);
1138
1148
  }
1139
1149
  catch (error) {
1140
- if (body.secrets !== undefined && (0, process_1.requestMayHaveArrived)(error)) {
1150
+ if (withSecrets && (0, process_1.requestMayHaveArrived)(error)) {
1141
1151
  throw new error_1.HyperbrowserError(`could not confirm the create result: ${error instanceof Error
1142
1152
  ? error.message.replace(/^\[Hyperbrowser\]: /, "")
1143
1153
  : String(error)}; a sandbox may have been created with the secrets, so list sandboxes before retrying`, {
@@ -14,15 +14,30 @@ export declare class SecretsService extends BaseService {
14
14
  list(params?: SecretListParams): Promise<SecretListResponse>;
15
15
  /**
16
16
  * Create a secret, and store its first version when `value` is given. The
17
- * value and the platform key are checked before the secret is created, so a
18
- * bad value never leaves an empty secret behind.
17
+ * value is checked against its type's rules, and the platform key is
18
+ * checked, before the secret is created, so a bad value never leaves an
19
+ * empty secret behind.
19
20
  */
20
21
  create(params: CreateSecretParams): Promise<Secret>;
21
22
  /** Metadata and versions; never the value. */
22
23
  get(secret: string): Promise<SecretDetail>;
24
+ /**
25
+ * Change a secret's description or metadata. Metadata members sent replace
26
+ * the stored ones, `null` removes a member, and members not sent are kept.
27
+ */
23
28
  update(secret: string, params: UpdateSecretParams): Promise<Secret>;
24
- /** Encrypt `value` locally and store it as the secret's next version. */
29
+ /**
30
+ * Encrypt `value` locally and store it as the secret's next version. The
31
+ * value is checked against the rules of the secret's type, such as a TOTP
32
+ * seed's format, before it is sealed.
33
+ */
25
34
  addVersion(secret: string, value: SecretValue): Promise<SecretVersion>;
35
+ /**
36
+ * Replace the origins a secret may be filled into in browser sessions, or
37
+ * clear them with `null` (it then can't be filled until origins are set
38
+ * again). Not for `op_token` secrets.
39
+ */
40
+ setAllowedOrigins(secret: string, allowedOrigins: string[] | null): Promise<Secret>;
26
41
  /**
27
42
  * Permanently delete a secret and its versions. Values already delivered to
28
43
  * sandboxes are not revoked.
@@ -30,7 +45,7 @@ export declare class SecretsService extends BaseService {
30
45
  delete(secret: string): Promise<SecretDeleteResult>;
31
46
  /** Delete one stored version. The latest version cannot be deleted. */
32
47
  deleteVersion(secret: string, version: number): Promise<SecretVersionDeleteResult>;
33
- private prepareSealer;
48
+ /** Sealed to the context the API returned for the secret; never one built here. */
34
49
  private uploadVersion;
35
50
  private resolve;
36
51
  private resolveDetail;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SecretsService = void 0;
4
4
  const error_1 = require("../error");
5
5
  const secret_references_1 = require("../sandbox/secret-references");
6
+ const secret_values_1 = require("../secret-values");
6
7
  const secrets_envelope_1 = require("../secrets-envelope");
7
8
  const base_1 = require("./base");
8
9
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -44,11 +45,15 @@ class SecretsService extends base_1.BaseService {
44
45
  (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT)) {
45
46
  throw new error_1.HyperbrowserError(`limit must be an integer from 1 to ${MAX_LIST_LIMIT}`);
46
47
  }
48
+ if (params.type !== undefined && (typeof params.type !== "string" || !params.type)) {
49
+ throw (0, error_1.invalidRequest)('type must be a secret type such as "secret" or "totp"');
50
+ }
47
51
  try {
48
52
  return await this.request("/secrets", undefined, {
49
53
  search: params.search,
50
54
  page: params.page,
51
55
  limit: params.limit,
56
+ type: params.type,
52
57
  });
53
58
  }
54
59
  catch (error) {
@@ -57,30 +62,36 @@ class SecretsService extends base_1.BaseService {
57
62
  }
58
63
  /**
59
64
  * Create a secret, and store its first version when `value` is given. The
60
- * value and the platform key are checked before the secret is created, so a
61
- * bad value never leaves an empty secret behind.
65
+ * value is checked against its type's rules, and the platform key is
66
+ * checked, before the secret is created, so a bad value never leaves an
67
+ * empty secret behind.
62
68
  */
63
69
  async create(params) {
64
- const { name, description, value } = params ?? {};
70
+ const { name, description, type, allowedOrigins, value } = params ?? {};
65
71
  if (typeof name !== "string" || !secret_references_1.SECRET_NAME_PATTERN.test(name)) {
66
72
  throw new error_1.HyperbrowserError("Secret name must be 1-128 letters, digits, '_' or '-', starting with a letter or digit");
67
73
  }
68
- const sealer = value === undefined ? undefined : await this.prepareSealer(value);
74
+ if (type !== undefined && (typeof type !== "string" || !type)) {
75
+ throw (0, error_1.invalidRequest)('type must be "secret", "totp" or "op_token"');
76
+ }
77
+ const plaintext = value === undefined ? undefined : (0, secret_values_1.secretPlaintext)(type ?? "secret", value);
78
+ const sealer = plaintext === undefined ? undefined : secrets_envelope_1.SecretsSealer.fromPublicKey(await this.getPublicKey());
79
+ const metadata = allowedOrigins === undefined ? undefined : { browser: { allowedOrigins } };
69
80
  let secret;
70
81
  try {
71
82
  secret = await this.request("/secrets", {
72
83
  method: "POST",
73
- body: JSON.stringify({ name, description }),
84
+ body: JSON.stringify({ name, description, type, metadata }),
74
85
  });
75
86
  }
76
87
  catch (error) {
77
88
  return rethrow(error, `Failed to create secret ${name}`);
78
89
  }
79
- if (sealer === undefined) {
90
+ if (sealer === undefined || plaintext === undefined) {
80
91
  return secret;
81
92
  }
82
93
  try {
83
- const version = await this.uploadVersion(secret, sealer, value);
94
+ const version = await this.uploadVersion(secret, sealer, plaintext);
84
95
  return { ...secret, latestVersion: version.version };
85
96
  }
86
97
  catch (error) {
@@ -104,28 +115,56 @@ class SecretsService extends base_1.BaseService {
104
115
  return rethrow(error, `Failed to get secret ${secret}`);
105
116
  }
106
117
  }
118
+ /**
119
+ * Change a secret's description or metadata. Metadata members sent replace
120
+ * the stored ones, `null` removes a member, and members not sent are kept.
121
+ */
107
122
  async update(secret, params) {
123
+ const { description, metadata } = params ?? {};
124
+ if (description === undefined && metadata === undefined) {
125
+ throw (0, error_1.invalidRequest)("Pass a description or metadata to update");
126
+ }
127
+ if (metadata !== undefined && !(0, secrets_envelope_1.isPlainObject)(metadata)) {
128
+ throw (0, error_1.invalidRequest)("Secret metadata must be an object");
129
+ }
108
130
  try {
109
131
  const { id } = await this.resolve(secret);
110
132
  return await this.request(secretPath(id), {
111
133
  method: "PATCH",
112
- body: JSON.stringify({ description: params.description }),
134
+ body: JSON.stringify({ description, metadata }),
113
135
  });
114
136
  }
115
137
  catch (error) {
116
138
  return rethrow(error, `Failed to update secret ${secret}`);
117
139
  }
118
140
  }
119
- /** Encrypt `value` locally and store it as the secret's next version. */
141
+ /**
142
+ * Encrypt `value` locally and store it as the secret's next version. The
143
+ * value is checked against the rules of the secret's type, such as a TOTP
144
+ * seed's format, before it is sealed.
145
+ */
120
146
  async addVersion(secret, value) {
121
- const sealer = await this.prepareSealer(value);
147
+ (0, secrets_envelope_1.secretValueBytes)(value);
148
+ const sealer = secrets_envelope_1.SecretsSealer.fromPublicKey(await this.getPublicKey());
122
149
  try {
123
- return await this.uploadVersion(await this.resolve(secret), sealer, value);
150
+ const resolved = await this.resolve(secret);
151
+ const plaintext = (0, secret_values_1.secretPlaintext)(resolved.type ?? "secret", value);
152
+ return await this.uploadVersion(resolved, sealer, plaintext);
124
153
  }
125
154
  catch (error) {
126
155
  return rethrow(error, `Failed to add a version to secret ${secret}`);
127
156
  }
128
157
  }
158
+ /**
159
+ * Replace the origins a secret may be filled into in browser sessions, or
160
+ * clear them with `null` (it then can't be filled until origins are set
161
+ * again). Not for `op_token` secrets.
162
+ */
163
+ async setAllowedOrigins(secret, allowedOrigins) {
164
+ return this.update(secret, {
165
+ metadata: { browser: allowedOrigins === null ? null : { allowedOrigins } },
166
+ });
167
+ }
129
168
  /**
130
169
  * Permanently delete a secret and its versions. Values already delivered to
131
170
  * sandboxes are not revoked.
@@ -154,14 +193,11 @@ class SecretsService extends base_1.BaseService {
154
193
  return rethrow(error, `Failed to delete version ${version} of secret ${secret}`);
155
194
  }
156
195
  }
157
- async prepareSealer(value) {
158
- (0, secrets_envelope_1.secretValueBytes)(value);
159
- return secrets_envelope_1.SecretsSealer.fromPublicKey(await this.getPublicKey());
160
- }
161
- uploadVersion(secret, sealer, value) {
196
+ /** Sealed to the context the API returned for the secret; never one built here. */
197
+ uploadVersion(secret, sealer, plaintext) {
162
198
  return this.request(`${secretPath(secret.id)}/versions`, {
163
199
  method: "POST",
164
- body: JSON.stringify({ envelope: sealer.seal(secret.encryptionContext, value) }),
200
+ body: JSON.stringify({ envelope: sealer.seal(secret.encryptionContext, plaintext) }),
165
201
  });
166
202
  }
167
203
  async resolve(secret) {
@@ -1,6 +1,7 @@
1
1
  import { BasicResponse, CaptchaEvaluationParams, CaptchaEvaluationResponse, CreateSessionParams, CreateSessionSnapshotResponse, GetActiveSessionsCountResponse, GetSessionDownloadsUrlResponse, GetSessionRecordingUrlResponse, GetSessionVideoRecordingUrlResponse, SessionDetail, SessionListParams, SessionListResponse, SessionRecording, UploadFileOptions, UploadFileResponse, SessionEventLogListParams, SessionEventLogListResponse, UpdateSessionProfileParams, UpdateSessionProxyParams, UpdateSessionScreenParams, UpdateSessionSolveCaptchasParams, UpdateSessionSolveCaptchasResponse, SessionGetParams } from "../types/session";
2
2
  import { BaseService } from "./base";
3
3
  import { WebMCPService } from "./webmcp";
4
+ import { CredentialsService } from "./credentials";
4
5
  /**
5
6
  * Service for managing session event logs
6
7
  */
@@ -15,6 +16,7 @@ declare class SessionEventLogsService extends BaseService {
15
16
  export declare class SessionsService extends BaseService {
16
17
  readonly eventLogs: SessionEventLogsService;
17
18
  readonly webmcp: WebMCPService;
19
+ readonly credentials: CredentialsService;
18
20
  constructor(apiKey: string, baseUrl: string, timeout: number);
19
21
  /**
20
22
  * Create a new browser session
@@ -32,6 +32,7 @@ const path = __importStar(require("path"));
32
32
  const form_data_1 = __importDefault(require("form-data"));
33
33
  const base_1 = require("./base");
34
34
  const webmcp_1 = require("./webmcp");
35
+ const credentials_1 = require("./credentials");
35
36
  const client_1 = require("../client");
36
37
  const CAPTCHA_EVALUATION_REQUEST_TIMEOUT_MS = 185000;
37
38
  /**
@@ -63,6 +64,7 @@ class SessionsService extends base_1.BaseService {
63
64
  super(apiKey, baseUrl, timeout);
64
65
  this.eventLogs = new SessionEventLogsService(apiKey, baseUrl, timeout);
65
66
  this.webmcp = new webmcp_1.WebMCPService(apiKey, baseUrl, timeout);
67
+ this.credentials = new credentials_1.CredentialsService(apiKey, baseUrl, timeout);
66
68
  }
67
69
  /**
68
70
  * Create a new browser session
@@ -0,0 +1,26 @@
1
+ import { CreateVaultParams, UpdateVaultParams, Vault, VaultDeleteResult, VaultListParams, VaultListResponse } from "../types/vault";
2
+ import { BaseService } from "./base";
3
+ /**
4
+ * Vaults name a set of environment variables whose values come from stored
5
+ * secrets. Pass one as `vault` to a sandbox process
6
+ * or sandbox create, or attach it to a browser session with
7
+ * `credentials: [{ vault }]` and fill its entries by name.
8
+ *
9
+ * Methods that take `vault` accept its ID or its exact name. A UUID-shaped
10
+ * argument is tried as an ID first and then as a name.
11
+ */
12
+ export declare class VaultsService extends BaseService {
13
+ list(params?: VaultListParams): Promise<VaultListResponse>;
14
+ get(vault: string): Promise<Vault>;
15
+ /** Create a vault. Entries name secrets, never values. */
16
+ create(params: CreateVaultParams): Promise<Vault>;
17
+ /**
18
+ * Change a vault's description or its entries:
19
+ * each entry given replaces that variable, `null` removes it, and entries
20
+ * not given are kept.
21
+ */
22
+ update(vault: string, params: UpdateVaultParams): Promise<Vault>;
23
+ /** Delete a vault. The secrets its entries name are kept. */
24
+ delete(vault: string): Promise<VaultDeleteResult>;
25
+ private resolve;
26
+ }