diff --git a/.release-please-manifest.json b/.release-please-manifest.json index dc3c35f1..df1862d1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.121.0" + ".": "0.122.0" } diff --git a/.stats.yml b/.stats.yml index 8e11d075..caa7c062 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 174 +configured_endpoints: 175 diff --git a/CHANGELOG.md b/CHANGELOG.md index be169824..ec86e0d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.122.0](https://github.com/kernel/kernel-node-sdk/compare/v0.121.0...v0.122.0) (2026-10-09) + + +### Features + +* Accept client-encrypted credential values in vault items ([78ee783](https://github.com/kernel/kernel-node-sdk/commit/78ee783d057d99807cb2693a017d22714113b94e)) +* Add feature-gated Korean ISP proxies ([96d63e2](https://github.com/kernel/kernel-node-sdk/commit/96d63e2d31b3ee1b3d65d5056db49f58705f96ef)) +* Describe vault fill as safe to retry ([b96e6ca](https://github.com/kernel/kernel-node-sdk/commit/b96e6cadb4decdbb9f2618853b1f571aa5af7f2b)) +* Infer a challenge result for unobserved captcha providers in the relay ([84022be](https://github.com/kernel/kernel-node-sdk/commit/84022be96b336b7f43a8e89293b8c72985ddbbbe)) +* Support proxy routes in browser pools ([e5b2ffc](https://github.com/kernel/kernel-node-sdk/commit/e5b2ffc07188d09ca6b04c253b5daf394aac7fbd)) + ## [0.121.0](https://github.com/kernel/kernel-node-sdk/compare/v0.120.0...v0.121.0) (2026-10-07) diff --git a/api.md b/api.md index c1fe320d..8b6dbe29 100644 --- a/api.md +++ b/api.md @@ -492,12 +492,14 @@ Methods: Types: - Vault +- VaultEncryptionKey Methods: - client.vaults.retrieve(idOrName) -> Vault - client.vaults.list({ ...params }) -> VaultsOffsetPagination - client.vaults.delete(idOrName) -> void +- client.vaults.retrieveEncryptionKey(idOrName) -> VaultEncryptionKey - client.vaults.upsert({ ...params }) -> Vault ## Items diff --git a/package.json b/package.json index dbf839aa..39e5a879 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/sdk", - "version": "0.121.0", + "version": "0.122.0", "description": "The official TypeScript library for the Kernel API", "author": "Kernel <>", "types": "dist/index.d.ts", diff --git a/src/client.ts b/src/client.ts index 439d8124..b53bdaa3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -232,6 +232,7 @@ import { import { Telemetry } from './resources/telemetry/telemetry'; import { Vault, + VaultEncryptionKey, VaultListParams, VaultUpsertParams, Vaults, @@ -1313,6 +1314,7 @@ export declare namespace Kernel { export { Vaults as Vaults, type Vault as Vault, + type VaultEncryptionKey as VaultEncryptionKey, type VaultsOffsetPagination as VaultsOffsetPagination, type VaultListParams as VaultListParams, type VaultUpsertParams as VaultUpsertParams, diff --git a/src/resources/browser-pools.ts b/src/resources/browser-pools.ts index 41e39473..f3b507f0 100644 --- a/src/resources/browser-pools.ts +++ b/src/resources/browser-pools.ts @@ -585,7 +585,9 @@ export interface BrowserPoolCreateParams { name?: string; /** - * Network configuration applied to browsers in this pool. + * Network configuration applied to browsers in this pool. Proxy routes require + * proxy-v3; the pool will not provision browsers through Envoy if proxy-v3 is + * unavailable. */ network?: BrowsersAPI.BrowserNetworkConfig; @@ -857,9 +859,11 @@ export interface BrowserPoolUpdateParams { /** * If provided, replaces the pool's network configuration. Omit to leave the * existing configuration unchanged; an empty object ({}) removes it, while - * network: {private_hosts: []} sets an explicit empty list. Only applied to - * browsers created in the pool after the update; browsers already in the pool keep - * their configuration until discarded (see discard_all_idle). + * network: {private_hosts: []} or network: {proxy_routes: []} sets an explicit + * empty list. Proxy routes require proxy-v3; the pool will not provision browsers + * through Envoy if proxy-v3 is unavailable. Only applied to browsers created in + * the pool after the update; browsers already in the pool keep their configuration + * until discarded (see discard_all_idle). */ network?: BrowsersAPI.BrowserNetworkConfig; diff --git a/src/resources/browsers/browsers.ts b/src/resources/browsers/browsers.ts index 1182ddf4..7854d497 100644 --- a/src/resources/browsers/browsers.ts +++ b/src/resources/browsers/browsers.ts @@ -379,8 +379,9 @@ export interface BrowserNetworkConfig { * Enforced at Kernel's egress proxy only: destinations in private_hosts, and * processes in the browser VM that do not use the browser's proxy, are not * filtered, and Kernel's own control traffic is always allowed. Can be replaced or - * removed while the session runs with PATCH /browsers/{id_or_name}. Requires proxy - * v3. Not supported on browser pools. + * removed while the session runs with PATCH /browsers/{id_or_name}, but not added + * to a browser created without one. Requires proxy v3. Not supported on browser + * pools. */ allowed_hosts?: Array; @@ -411,20 +412,20 @@ export interface BrowserNetworkConfig { private_hosts?: Array; /** - * Per-destination proxy routes for a browser session. After setup, a destination - * hostname is matched against every route's hosts, regardless of port; route order - * does not matter. An exact hostname beats a wildcard, and a longer wildcard - * suffix beats a shorter one (for a.b.example.com: "a.b.example.com" > - * "_.b.example.com" > "_.example.com"). A host pattern may appear in only one - * route. "\*.example.com" matches subdomains only, not example.com. A matched - * request selects the route's proxy instead of the session's top-level proxy - * (including mode: direct); the route proxy's own bypass_hosts still apply. If the - * route proxy becomes unavailable, matched requests fail closed without falling - * back. Requests that match no route use the session's default egress from the - * top-level proxy field (or the browser default when proxy is omitted: stealth - * proxy or direct egress). Routes take effect once the session is created; - * start_url and other traffic during browser setup use the top-level proxy. - * Setting routes requires proxy v3. Not supported on browser pools. + * Per-destination proxy routes for a browser session. A destination hostname is + * matched against every route's hosts, regardless of port; route order does not + * matter. An exact hostname beats a wildcard, and a longer wildcard suffix beats a + * shorter one (for a.b.example.com: "a.b.example.com" > "_.b.example.com" > + * "_.example.com"). A host pattern may appear in only one route. "\*.example.com" + * matches subdomains only, not example.com. A matched request selects the route's + * proxy instead of the session's top-level proxy (including mode: direct); the + * route proxy's own bypass_hosts still apply. If the route proxy becomes + * unavailable, matched requests fail closed without falling back. Requests that + * match no route use the session's default egress from the top-level proxy field + * (or the browser default when proxy is omitted: stealth proxy or direct egress). + * Routes apply from the start of the session, including to start_url and other + * traffic during browser setup. Setting routes requires proxy v3. Browser pools + * also support these routes. */ proxy_routes?: Array; } @@ -464,16 +465,18 @@ export namespace BrowserNetworkConfig { export interface BrowserNetworkUpdate { /** * Replaces the session's egress allowlist, using the same entry rules as - * network.allowed_hosts on create. Omit to leave the allowlist unchanged, or set - * to null to remove it and return to unfiltered egress; an empty list is invalid. - * The new list applies without restarting the browser: new requests to - * destinations it no longer allows are refused within a few seconds, and open - * connections to them are closed within about 30 seconds, or up to 10 minutes - * during a Kernel deploy. Connections to destinations it still allows, such as - * WebSockets, stay open. A start_url in the same request must be allowed by the - * updated list, and is loaded only after the list takes effect. Requires a browser - * created with proxy v3, and not supported on pooled browsers. If the request - * fails, retry it: the new list may already apply to some requests. + * network.allowed_hosts on create. Only an allowlist the browser was created with + * can be changed: a browser created without one can't be given one, and an + * allowlist removed with null can't be added back. Omit to leave the allowlist + * unchanged, or set to null to remove it and return to unfiltered egress; an empty + * list is invalid. The new list applies without restarting the browser: new + * requests to destinations it no longer allows are refused within a few seconds, + * and open connections to them are closed within about 30 seconds, or up to 10 + * minutes during a Kernel deploy. Connections to destinations it still allows, + * such as WebSockets, stay open. A start_url in the same request must be allowed + * by the updated list, and is loaded only after the list takes effect. Requires a + * browser created with proxy v3, and not supported on pooled browsers. If the + * request fails, retry it: the new list may already apply to some requests. */ allowed_hosts?: Array | null; } diff --git a/src/resources/browsers/telemetry.ts b/src/resources/browsers/telemetry.ts index 7b746490..46b50f27 100644 --- a/src/resources/browsers/telemetry.ts +++ b/src/resources/browsers/telemetry.ts @@ -194,15 +194,19 @@ export namespace BrowserCallStack { } /** - * A visible captcha challenge reached a terminal outcome. + * A captcha challenge reached an observed or inferred terminal outcome. */ export interface BrowserCaptchaChallengeResultEvent { category: 'captcha'; /** - * Per-challenge payload. This event is emitted once per challenge and determines - * its overall outcome; captcha_solve_started and captcha_solve_result describe - * individual tasks and may occur multiple times within the challenge. + * An observed challenge emits one outcome across any number of solver tasks. For + * eligible providers without a widget observer, each successful token task emits + * an inferred solved result instead; multiple such results may belong to one + * challenge. Failed tasks and image_challenge rounds produce no inferred result. + * Exactly one of challenge_id or task_id is present: challenge_id on an observed + * result, task_id on an inferred one. A challenge whose tasks all fail produces + * task events only, so consumers fall back to captcha_solve_result for it. */ data: BrowserCaptchaChallengeResultEvent.Data; @@ -226,15 +230,17 @@ export interface BrowserCaptchaChallengeResultEvent { export namespace BrowserCaptchaChallengeResultEvent { /** - * Per-challenge payload. This event is emitted once per challenge and determines - * its overall outcome; captcha_solve_started and captcha_solve_result describe - * individual tasks and may occur multiple times within the challenge. + * An observed challenge emits one outcome across any number of solver tasks. For + * eligible providers without a widget observer, each successful token task emits + * an inferred solved result instead; multiple such results may belong to one + * challenge. Failed tasks and image_challenge rounds produce no inferred result. + * Exactly one of challenge_id or task_id is present: challenge_id on an observed + * result, task_id on an inferred one. A challenge whose tasks all fail produces + * task events only, so consumers fall back to captcha_solve_result for it. */ export interface Data { /** - * Captcha kind. Enterprise reCAPTCHA variants are grouped into their version - * bucket (recaptcha_v2 or recaptcha_v3), press-and-hold challenges use - * press_and_hold, and unlisted kinds use other. + * @deprecated Deprecated: use captcha_provider. */ captcha_type: | 'hcaptcha' @@ -245,34 +251,67 @@ export namespace BrowserCaptchaChallengeResultEvent { | 'press_and_hold' | 'other'; + /** + * Wall-clock duration from the challenge appearing to its terminal outcome, + * covering every solver attempt in between. For an inferred result, the duration + * of its solver task. + */ + duration_ms: number; + + /** + * Terminal outcome of a challenge. solved: the page observed the challenge clear + * after a solver attempt, or an inferred result reports a token for the whole + * widget without page observation. failure: a terminal solver failure occurred, or + * all attempts ended while the challenge remained. timeout: the challenge-level + * wait budget expired while the challenge remained. abandoned: observation ended + * without an attributable terminal challenge outcome. This includes a dismissed + * widget or page unload without a solved signal or terminal solver outcome, and a + * token appearing while multiple same-provider challenges are open, because the + * producer cannot attribute that token to this visible challenge. A + * captcha_solve_result with the same challenge_id may therefore report success + * while the challenge result reports abandoned. A solved challenge does not prove + * the site accepted the token or that the guarded action succeeded. + */ + status: 'solved' | 'failure' | 'timeout' | 'abandoned'; + + /** + * Captcha product the challenge belongs to, not the service that solved it. + * Enterprise reCAPTCHA variants are grouped into their version bucket + * (recaptcha_v2 or recaptcha_v3), FunCaptcha uses arkose, press-and-hold + * challenges served by HUMAN (formerly PerimeterX) use human, and unlisted + * products use other. + */ + captcha_provider?: + | 'hcaptcha' + | 'recaptcha_v2' + | 'recaptcha_v3' + | 'turnstile' + | 'geetest' + | 'arkose' + | 'human' + | 'other'; + /** * Opaque identifier shared by events for one visible challenge. An image-grid * captcha may create multiple task_id values for one challenge_id. The same value * may continue across a page reload when the challenge episode continues. It does * not indicate task ordering or challenge completion. */ - challenge_id: string; + challenge_id?: string; /** - * Wall-clock duration from the challenge appearing to its terminal outcome, - * covering every solver attempt in between. + * True when the relay derived this result from a successful token task without + * observing the page. An inferred result has task_id instead of challenge_id. + * Absent on page-observed results. */ - duration_ms: number; + inferred?: boolean; /** - * Terminal outcome of the visible challenge. solved: the page observed the - * challenge clear after a solver attempt. failure: a terminal solver failure - * occurred, or all attempts ended while the challenge remained. timeout: the - * challenge-level wait budget expired while the challenge remained. abandoned: - * observation ended without an attributable terminal challenge outcome. This - * includes a dismissed widget or page unload without a solved signal or terminal - * solver outcome, and a token appearing while multiple same-provider challenges - * are open, because the producer cannot attribute that token to this visible - * challenge. A captcha_solve_result with the same challenge_id may therefore - * report success while the challenge result reports abandoned. A solved challenge - * does not prove the site accepted the token or that the guarded action succeeded. + * The task_id of the solver task an inferred result was derived from. Join on it + * to pair the result with that task's captcha_solve_started and + * captcha_solve_result. Present only when inferred is true. */ - status: 'solved' | 'failure' | 'timeout' | 'abandoned'; + task_id?: string; /** * Host of the page where the challenge appeared. @@ -315,9 +354,7 @@ export interface BrowserCaptchaSolveResultEvent { export namespace BrowserCaptchaSolveResultEvent { export interface Data { /** - * Captcha kind. Enterprise reCAPTCHA variants are grouped into their version - * bucket (recaptcha_v2 or recaptcha_v3), press-and-hold challenges use - * press_and_hold, and unlisted kinds use other. + * @deprecated Deprecated: use captcha_provider and task_kind. */ captcha_type: | 'hcaptcha' @@ -343,6 +380,23 @@ export namespace BrowserCaptchaSolveResultEvent { */ status: 'success' | 'failure' | 'timeout' | 'abandoned'; + /** + * Captcha product the challenge belongs to, not the service that solved it. + * Enterprise reCAPTCHA variants are grouped into their version bucket + * (recaptcha_v2 or recaptcha_v3), FunCaptcha uses arkose, press-and-hold + * challenges served by HUMAN (formerly PerimeterX) use human, and unlisted + * products use other. + */ + captcha_provider?: + | 'hcaptcha' + | 'recaptcha_v2' + | 'recaptcha_v3' + | 'turnstile' + | 'geetest' + | 'arkose' + | 'human' + | 'other'; + /** * Opaque identifier shared by events for one visible challenge. An image-grid * captcha may create multiple task_id values for one challenge_id. The same value @@ -362,6 +416,11 @@ export namespace BrowserCaptchaSolveResultEvent { */ task_id?: string; + /** + * What the solver task produces. Absent when the producer cannot tell. + */ + task_kind?: 'token' | 'image_challenge' | 'press_and_hold'; + /** * Host of the page where the captcha was solved. */ @@ -415,9 +474,7 @@ export namespace BrowserCaptchaSolveStartedEvent { */ export interface Data { /** - * Captcha kind. Enterprise reCAPTCHA variants are grouped into their version - * bucket (recaptcha_v2 or recaptcha_v3), press-and-hold challenges use - * press_and_hold, and unlisted kinds use other. + * @deprecated Deprecated: use captcha_provider and task_kind. */ captcha_type: | 'hcaptcha' @@ -428,6 +485,23 @@ export namespace BrowserCaptchaSolveStartedEvent { | 'press_and_hold' | 'other'; + /** + * Captcha product the challenge belongs to, not the service that solved it. + * Enterprise reCAPTCHA variants are grouped into their version bucket + * (recaptcha_v2 or recaptcha_v3), FunCaptcha uses arkose, press-and-hold + * challenges served by HUMAN (formerly PerimeterX) use human, and unlisted + * products use other. + */ + captcha_provider?: + | 'hcaptcha' + | 'recaptcha_v2' + | 'recaptcha_v3' + | 'turnstile' + | 'geetest' + | 'arkose' + | 'human' + | 'other'; + /** * Opaque identifier shared by events for one visible challenge. An image-grid * captcha may create multiple task_id values for one challenge_id. The same value @@ -441,6 +515,11 @@ export namespace BrowserCaptchaSolveStartedEvent { */ task_id?: string; + /** + * What the solver task produces. Absent when the producer cannot tell. + */ + task_kind?: 'token' | 'image_challenge' | 'press_and_hold'; + /** * Host of the page where the captcha is being solved. May be empty for solver * tasks that carry no page URL. diff --git a/src/resources/config-registry/config-registry.ts b/src/resources/config-registry/config-registry.ts index a8042bf1..d98336d6 100644 --- a/src/resources/config-registry/config-registry.ts +++ b/src/resources/config-registry/config-registry.ts @@ -336,8 +336,8 @@ export namespace Proxy { */ export interface IspProxyConfig { /** - * ISO 3166 country code. Supported countries are US, GB, FR, DE, and SG. Defaults - * to US if not provided. + * ISO 3166 country code. Supported countries are US, GB, FR, DE, SG, and KR. + * Defaults to US if not provided. */ country?: string; } diff --git a/src/resources/index.ts b/src/resources/index.ts index e71326aa..3255e808 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -210,6 +210,7 @@ export { export { Vaults, type Vault, + type VaultEncryptionKey, type VaultListParams, type VaultUpsertParams, type VaultsOffsetPagination, diff --git a/src/resources/proxies.ts b/src/resources/proxies.ts index a2a6ab49..1f00130d 100644 --- a/src/resources/proxies.ts +++ b/src/resources/proxies.ts @@ -166,8 +166,8 @@ export namespace ProxyCreateResponse { */ export interface IspProxyConfig { /** - * ISO 3166 country code. Supported countries are US, GB, FR, DE, and SG. Defaults - * to US if not provided. + * ISO 3166 country code. Supported countries are US, GB, FR, DE, SG, and KR. + * Defaults to US if not provided. */ country?: string; } @@ -315,8 +315,8 @@ export namespace ProxyRetrieveResponse { */ export interface IspProxyConfig { /** - * ISO 3166 country code. Supported countries are US, GB, FR, DE, and SG. Defaults - * to US if not provided. + * ISO 3166 country code. Supported countries are US, GB, FR, DE, SG, and KR. + * Defaults to US if not provided. */ country?: string; } @@ -464,8 +464,8 @@ export namespace ProxyUpdateResponse { */ export interface IspProxyConfig { /** - * ISO 3166 country code. Supported countries are US, GB, FR, DE, and SG. Defaults - * to US if not provided. + * ISO 3166 country code. Supported countries are US, GB, FR, DE, SG, and KR. + * Defaults to US if not provided. */ country?: string; } @@ -613,8 +613,8 @@ export namespace ProxyListResponse { */ export interface IspProxyConfig { /** - * ISO 3166 country code. Supported countries are US, GB, FR, DE, and SG. Defaults - * to US if not provided. + * ISO 3166 country code. Supported countries are US, GB, FR, DE, SG, and KR. + * Defaults to US if not provided. */ country?: string; } @@ -762,8 +762,8 @@ export namespace ProxyCheckResponse { */ export interface IspProxyConfig { /** - * ISO 3166 country code. Supported countries are US, GB, FR, DE, and SG. Defaults - * to US if not provided. + * ISO 3166 country code. Supported countries are US, GB, FR, DE, SG, and KR. + * Defaults to US if not provided. */ country?: string; } @@ -891,8 +891,8 @@ export namespace ProxyCreateParams { */ export interface IspProxyConfig { /** - * ISO 3166 country code. Supported countries are US, GB, FR, DE, and SG. Defaults - * to US if not provided. + * ISO 3166 country code. Supported countries are US, GB, FR, DE, SG, and KR. + * Defaults to US if not provided. */ country?: string; } diff --git a/src/resources/vaults/index.ts b/src/resources/vaults/index.ts index 555097e4..8c888192 100644 --- a/src/resources/vaults/index.ts +++ b/src/resources/vaults/index.ts @@ -75,6 +75,7 @@ export { export { Vaults, type Vault, + type VaultEncryptionKey, type VaultListParams, type VaultUpsertParams, type VaultsOffsetPagination, diff --git a/src/resources/vaults/items.ts b/src/resources/vaults/items.ts index 38d863cc..dc1cee73 100644 --- a/src/resources/vaults/items.ts +++ b/src/resources/vaults/items.ts @@ -146,8 +146,8 @@ export class Items extends APIResource { * return 400 (invalid request or targets), 403 (access or destination denied), 404 * (resource not found), or 409 (item or browser not ready). Once writing starts, * known partial failures and indeterminate field outcomes return 200 with status - * `failed` or `unknown`, not an automatic-retry signal. A transport error may - * leave the outcome unknown; do not automatically retry. + * `failed` or `unknown`. Fill never submits the page, so it is safe to retry after + * a failure, an `unknown` outcome, or a transport error. * * @example * ```ts @@ -769,6 +769,22 @@ export interface CredentialVaultFieldInput { */ type: CredentialVaultFieldType; + /** + * Alternative to value for custom credential collection web apps. Use this only if + * you run your own credential collection web app and want values encrypted in the + * browser, sent to your backend still encrypted, and forwarded to Kernel's API + * still encrypted. In every other case, including server-side code that already + * holds the plaintext, use value. The field's value encrypted client-side as a + * compact JWE with alg ECDH-ES and enc A256GCM to the key from GET + * /vaults/{id_or_name}/encryption_key, with that key's kid in the protected + * header. Compression is not supported. The decrypted value follows the same rules + * as value, including that an empty string clears the field on update. A kid that + * is not this vault's key returns 400 encryption_key_mismatch; fetch the key again + * and re-encrypt. Other malformed or undecryptable values return 400 + * invalid_request. The whole request body is limited to 128 KiB. + */ + encrypted_value?: string; + /** * Optional human-readable display label. It is returned as non-secret metadata and * never affects value keys, updates, or browser fills. Use single-line, trimmed @@ -791,7 +807,9 @@ export interface CredentialVaultFieldInput { /** * Optional initial value satisfying the declared type, at most 16 KiB in UTF-8 * bytes. Omit to leave unset; null and empty strings are rejected on creation. - * Sensitive values are encrypted and never copied into the returned spec. + * Sensitive values are encrypted and never copied into the returned spec. Use this + * unless your own credential collection web app encrypts values in the browser; + * mutually exclusive with encrypted_value. */ value?: string; } @@ -819,7 +837,28 @@ export interface CredentialVaultFieldState { */ export type CredentialVaultFieldType = 'text' | 'email' | 'password' | 'totp'; +/** + * Set exactly one of value or encrypted_value. Use value unless the update comes + * from your own credential collection web app that encrypts values in the browser; + * see encrypted_value. + */ export interface CredentialVaultFieldUpdate { + /** + * Alternative to value for custom credential collection web apps. Use this only if + * you run your own credential collection web app and want values encrypted in the + * browser, sent to your backend still encrypted, and forwarded to Kernel's API + * still encrypted. In every other case, including server-side code that already + * holds the plaintext, use value. The field's value encrypted client-side as a + * compact JWE with alg ECDH-ES and enc A256GCM to the key from GET + * /vaults/{id_or_name}/encryption_key, with that key's kid in the protected + * header. Compression is not supported. The decrypted value follows the same rules + * as value, including that an empty string clears the field on update. A kid that + * is not this vault's key returns 400 encryption_key_mismatch; fetch the key again + * and re-encrypt. Other malformed or undecryptable values return 400 + * invalid_request. The whole request body is limited to 128 KiB. + */ + encrypted_value?: string; + /** * Replacement value (at most 16 KiB in UTF-8 bytes), or null or an empty string to * immediately clear the stored value. Clearing a required form-supported field @@ -828,7 +867,7 @@ export interface CredentialVaultFieldUpdate { * seed, never a current code. Clearing a required totp field returns 400 because * it cannot be collected in a form. */ - value: string | null; + value?: string | null; } export interface CredentialVaultItem { @@ -1067,8 +1106,10 @@ export interface CredentialVaultItemUpdateRequest { * atomic: previously filled fields are not rolled back. Never submit the form or * click buttons, though input/change events may trigger site behavior. Link and * Kernel cards use fill for browser checkout and do not expose aliases or support - * egress substitution. Do not automatically retry a failed or indeterminate - * operation. + * egress substitution. Fill does not consume the item, so a failed or + * indeterminate fill is safe to retry; a retry rewrites the same fields. A retry + * right after an indeterminate fill may wait up to 15 seconds for the earlier + * attempt's browser lock to expire. * * Secret values are never returned or included in operation logs, traces, audit * events, or error details. This does not prevent an agent with unrestricted diff --git a/src/resources/vaults/vaults.ts b/src/resources/vaults/vaults.ts index 7b35d8ba..befee853 100644 --- a/src/resources/vaults/vaults.ts +++ b/src/resources/vaults/vaults.ts @@ -132,6 +132,26 @@ export class Vaults extends APIResource { }); } + /** + * Returns the public key that custom credential collection web apps use to encrypt + * values in the browser. Use this only if you run your own credential collection + * web app and want values encrypted in the browser, sent to your backend still + * encrypted, and forwarded to Kernel's API still encrypted. In every other case, + * including server-side code that already holds the plaintext, use value. The page + * encrypts each value to this key, your backend forwards the ciphertext unchanged + * as encrypted_value when creating or updating a credential item in this vault, + * and Kernel decrypts it. + * + * @example + * ```ts + * const vaultEncryptionKey = + * await client.vaults.retrieveEncryptionKey('id_or_name'); + * ``` + */ + retrieveEncryptionKey(idOrName: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/vaults/${idOrName}/encryption_key`, options); + } + /** * Free organizations can store up to 3 non-deleted vaults across all projects. * Paid plans and active trials have no vault cap. Retrieving an existing vault by @@ -164,6 +184,58 @@ export interface Vault { updated_at: string; } +/** + * Public key for encrypted_value on credential fields. Use this only if you run + * your own credential collection web app and want values encrypted in the browser, + * sent to your backend still encrypted, and forwarded to Kernel's API still + * encrypted. In every other case, including server-side code that already holds + * the plaintext, use value. Each vault has its own key; a value encrypted for one + * vault is rejected by every other vault. The key is created on first request and + * stays the same for the vault's lifetime, so it may be cached. + */ +export interface VaultEncryptionKey { + /** + * JWE key management algorithm. + */ + alg: 'ECDH-ES'; + + /** + * JWE content encryption algorithm. + */ + enc: 'A256GCM'; + + /** + * P-256 public key in JWK form. + */ + jwk: VaultEncryptionKey.Jwk; + + /** + * Key ID. Set it as the kid protected header of every encrypted_value. + */ + kid: string; +} + +export namespace VaultEncryptionKey { + /** + * P-256 public key in JWK form. + */ + export interface Jwk { + crv: 'P-256'; + + kty: 'EC'; + + /** + * Base64url-encoded x coordinate. + */ + x: string; + + /** + * Base64url-encoded y coordinate. + */ + y: string; + } +} + export interface VaultListParams extends OffsetPaginationParams { /** * Case-insensitive substring match against vault name. IDs match by exact value. @@ -183,6 +255,7 @@ Vaults.Items = Items; export declare namespace Vaults { export { type Vault as Vault, + type VaultEncryptionKey as VaultEncryptionKey, type VaultsOffsetPagination as VaultsOffsetPagination, type VaultListParams as VaultListParams, type VaultUpsertParams as VaultUpsertParams, diff --git a/src/version.ts b/src/version.ts index c985f5a7..3bf6938c 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.121.0'; // x-release-please-version +export const VERSION = '0.122.0'; // x-release-please-version diff --git a/tests/api-resources/vaults/vaults.test.ts b/tests/api-resources/vaults/vaults.test.ts index 7e94d822..860dcc79 100644 --- a/tests/api-resources/vaults/vaults.test.ts +++ b/tests/api-resources/vaults/vaults.test.ts @@ -59,6 +59,18 @@ describe('resource vaults', () => { expect(dataAndResponse.response).toBe(rawResponse); }); + // Mock server tests are disabled + test.skip('retrieveEncryptionKey', async () => { + const responsePromise = client.vaults.retrieveEncryptionKey('id_or_name'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + // Mock server tests are disabled test.skip('upsert: only required params', async () => { const responsePromise = client.vaults.upsert({ name: 'checkout' });