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' });