From f7caf3e0631ccce66694709001a9639bc3d1c144 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:43:19 +0000 Subject: [PATCH 1/3] feat: Allow auto-starting replays during browser creation and pool acquire Stainless-Generated-From: ac0f236613cf91b306bf2e1b88d17f621cc30785 --- src/resources/browser-pools.ts | 22 +++++++++++++++++++ src/resources/browsers/browsers.ts | 5 +++++ tests/api-resources/browser-pools.test.ts | 1 + tests/api-resources/browsers/browsers.test.ts | 1 + 4 files changed, 29 insertions(+) diff --git a/src/resources/browser-pools.ts b/src/resources/browser-pools.ts index 97fec043..ad370211 100644 --- a/src/resources/browser-pools.ts +++ b/src/resources/browser-pools.ts @@ -296,6 +296,11 @@ export namespace BrowserPool { */ refresh_on_profile_update?: boolean; + /** + * Start replay recording automatically when a browser is acquired from this pool. + */ + start_replay?: boolean; + /** * Optional URL to navigate to when a new browser is warmed into the pool. * Best-effort: failures to navigate do not fail pool fill. Only applied to @@ -637,6 +642,11 @@ export interface BrowserPoolCreateParams { */ region?: 'us-east' | 'us-west' | 'eu-west' | 'ap-southeast'; + /** + * Start replay recording automatically when a browser is acquired from this pool. + */ + start_replay?: boolean; + /** * Optional URL to navigate to when a new browser is warmed into the pool. * Best-effort: failures to navigate do not fail pool fill. Only applied to @@ -931,6 +941,12 @@ export interface BrowserPoolUpdateParams { */ size?: number; + /** + * If provided, replaces whether replay recording starts automatically for future + * acquired browsers. + */ + start_replay?: boolean; + /** * If provided, replaces the URL to navigate to when a new browser is warmed into * the pool. Empty string clears the previously-set URL. Omit this field to leave @@ -1161,6 +1177,12 @@ export interface BrowserPoolAcquireParams { */ profile?: Shared.BrowserProfile; + /** + * Start replay recording immediately for the acquired browser. Omit to use the + * pool's configured default. + */ + start_replay?: boolean; + /** * Optional URL to navigate the acquired browser to. Overrides the pool's start_url * for this acquire only. Best-effort: failures to navigate do not fail the diff --git a/src/resources/browsers/browsers.ts b/src/resources/browsers/browsers.ts index 0469a262..fb4a1317 100644 --- a/src/resources/browsers/browsers.ts +++ b/src/resources/browsers/browsers.ts @@ -1779,6 +1779,11 @@ export interface BrowserCreateParams { */ region?: 'us-east' | 'us-west' | 'eu-west' | 'ap-southeast'; + /** + * Start replay recording as part of creating this browser session. + */ + start_replay?: boolean; + /** * Optional URL to open when the browser session is created. Navigation is * best-effort, so navigation failures do not prevent the session from being diff --git a/tests/api-resources/browser-pools.test.ts b/tests/api-resources/browser-pools.test.ts index a13b78db..dd1a78cc 100644 --- a/tests/api-resources/browser-pools.test.ts +++ b/tests/api-resources/browser-pools.test.ts @@ -45,6 +45,7 @@ describe('resource browserPools', () => { proxy_id: 'proxy_id', refresh_on_profile_update: true, region: 'us-east', + start_replay: true, start_url: 'https://example.com', stealth: true, telemetry: { diff --git a/tests/api-resources/browsers/browsers.test.ts b/tests/api-resources/browsers/browsers.test.ts index 63ada8e7..ab10b2e3 100644 --- a/tests/api-resources/browsers/browsers.test.ts +++ b/tests/api-resources/browsers/browsers.test.ts @@ -58,6 +58,7 @@ describe('resource browsers', () => { }, proxy_id: 'proxy_id', region: 'us-east', + start_replay: true, start_url: 'https://example.com', stealth: true, tags: { team: 'backend', env: 'staging' }, From 1b13f05e1296a06e0be0aa8cd3b79b34685a7b1b Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 10 Nov 2026 11:24:57 -0500 Subject: [PATCH 2/3] Use federated model credentials instead of static LLM API keys (#16) * Use federated model credentials for pi * Pin llm-credentials action to a commit --------- Co-authored-by: rgarcia <72655+rgarcia@users.noreply.github.com> --- .../tests/update-coverage-security.test.mjs | 27 ++++++++++++++----- .github/workflows/semgrep.yml | 2 ++ .github/workflows/update-docs.yml | 22 +++++++++++++-- .github/workflows/update-mcp-coverage.yml | 22 +++++++++++++-- .github/workflows/vuln-remediation.yml | 2 ++ 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/.github/tests/update-coverage-security.test.mjs b/.github/tests/update-coverage-security.test.mjs index c48d7079..ed951b96 100644 --- a/.github/tests/update-coverage-security.test.mjs +++ b/.github/tests/update-coverage-security.test.mjs @@ -23,6 +23,9 @@ const load = (file) => { return { text, jobText, jobs, steps: Object.values(jobs).flat(), step }; }; +const llmCredentials = + 'uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main\n'; + const updaters = [ { file: 'update-mcp-coverage.yml', @@ -78,7 +81,7 @@ for (const { file, agentStep, context, prompt } of updaters) { test(`${file}: actions are pinned and checkouts do not persist credentials`, () => { for (const entry of steps.filter((entry) => entry.includes('uses: '))) { - assert.match(entry, /uses: [\w/-]+@[a-f0-9]{40}(?:\s|$)/); + assert.match(entry, /uses: [\w./-]+@[a-f0-9]{40}(?:\s|$)/); if (entry.includes('uses: actions/checkout@')) { assert.match(entry, /persist-credentials: false/); assert.doesNotMatch(entry, /token:/); @@ -86,7 +89,7 @@ for (const { file, agentStep, context, prompt } of updaters) { } }); - test(`${file}: the job that runs Pi references only the model API keys`, () => { + test(`${file}: the job that runs Pi references only the context key`, () => { // A job's secrets stay in runner memory for the whole job, where an agent with // sudo can read them, so revoking a minted token before Pi starts is not enough. const agentJobs = Object.keys(jobs).filter((name) => @@ -95,11 +98,7 @@ for (const { file, agentStep, context, prompt } of updaters) { assert.deepEqual(agentJobs, ['generate']); // UPDATER_CONTEXT_KEY only decrypts the context this job is meant to read. const secrets = [...jobText.generate.matchAll(/\bsecrets\b(?:\.(\w+))?/g)].map((match) => match[1]); - assert.deepEqual([...new Set(secrets)].sort(), [ - 'ANTHROPIC_API_KEY', - 'OPENAI_API_KEY', - 'UPDATER_CONTEXT_KEY', - ]); + assert.deepEqual([...new Set(secrets)], ['UPDATER_CONTEXT_KEY']); const keyUses = jobs.generate.filter((entry) => entry.includes('secrets.UPDATER_CONTEXT_KEY')); assert.deepEqual(keyUses, [step('generate', 'Decrypt update context')]); assert.doesNotMatch( @@ -110,6 +109,20 @@ for (const { file, agentStep, context, prompt } of updaters) { assert.match(step('generate', agentStep), /pi --print --no-session --approve/); }); + test(`${file}: model credentials exist only while Pi runs`, () => { + const agent = jobs.generate.indexOf(step('generate', agentStep)); + const start = step('generate', 'Model credentials'); + const stop = step('generate', 'Stop model credentials'); + assert.equal(jobs.generate.indexOf(start), agent - 1); + assert.equal(jobs.generate.indexOf(stop), agent + 1); + assert.ok(start.includes(llmCredentials)); + assert.match(start, /model: \$\{\{ vars\.PI_PREFERRED_MODEL \}\}\n/); + assert.match(stop, /if: always\(\)\n/); + assert.ok(stop.includes(llmCredentials)); + assert.match(stop, /stop: true\n/); + assert.match(jobText.generate, /\n permissions:\n contents: read\n id-token: write\n/); + }); + test(`${file}: GitHub reads happen in an earlier job that hands over only data`, () => { const readToken = step('context', 'Generate API read token'); assert.match(readToken, /repositories: kernel\n/); diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 94717a2a..86b33907 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -7,6 +7,8 @@ on: permissions: contents: read pull-requests: write + # semgrep exchanges the job's OIDC token for short-lived model credentials. + id-token: write jobs: scan: diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml index 567b8054..4b795147 100644 --- a/.github/workflows/update-docs.yml +++ b/.github/workflows/update-docs.yml @@ -255,6 +255,9 @@ jobs: if: needs.context.outputs.update == 'true' runs-on: ubuntu-latest timeout-minutes: 55 + permissions: + contents: read + id-token: write outputs: changed: ${{ steps.snapshot.outputs.changed }} main_sha: ${{ needs.context.outputs.main_sha }} @@ -364,11 +367,20 @@ jobs: npm install -g --ignore-scripts @earendil-works/pi-coding-agent mint@4.2.930 pi --version + - name: Model credentials + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main + with: + model: ${{ vars.PI_PREFERRED_MODEL }} + anthropic-federation-rule-id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }} + anthropic-organization-id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }} + anthropic-service-account-id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }} + openai-identity-provider-id: ${{ vars.OPENAI_IDENTITY_PROVIDER_ID }} + openai-service-account-id: ${{ vars.OPENAI_SERVICE_ACCOUNT_ID }} + openai-audience: ${{ vars.OPENAI_WIF_AUDIENCE }} + - name: Update docs working-directory: docs env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PI_PREFERRED_MODEL: ${{ vars.PI_PREFERRED_MODEL }} PI_SKIP_VERSION_CHECK: '1' PI_TELEMETRY: '0' @@ -378,6 +390,12 @@ jobs: --model "$PI_PREFERRED_MODEL" --thinking max \ @../.github/prompts/update-docs.md + - name: Stop model credentials + if: always() + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main + with: + stop: true + - name: Snapshot publishable changes id: snapshot working-directory: docs diff --git a/.github/workflows/update-mcp-coverage.yml b/.github/workflows/update-mcp-coverage.yml index ce1404f2..c28dba9e 100644 --- a/.github/workflows/update-mcp-coverage.yml +++ b/.github/workflows/update-mcp-coverage.yml @@ -224,6 +224,9 @@ jobs: if: needs.context.outputs.update == 'true' runs-on: ubuntu-latest timeout-minutes: 55 + permissions: + contents: read + id-token: write outputs: changed: ${{ steps.snapshot.outputs.changed }} main_sha: ${{ needs.context.outputs.main_sha }} @@ -320,11 +323,20 @@ jobs: npm install -g --ignore-scripts @earendil-works/pi-coding-agent pi --version + - name: Model credentials + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main + with: + model: ${{ vars.PI_PREFERRED_MODEL }} + anthropic-federation-rule-id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }} + anthropic-organization-id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }} + anthropic-service-account-id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }} + openai-identity-provider-id: ${{ vars.OPENAI_IDENTITY_PROVIDER_ID }} + openai-service-account-id: ${{ vars.OPENAI_SERVICE_ACCOUNT_ID }} + openai-audience: ${{ vars.OPENAI_WIF_AUDIENCE }} + - name: Update SDK dependency and MCP coverage working-directory: mcp env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PI_PREFERRED_MODEL: ${{ vars.PI_PREFERRED_MODEL }} PI_SKIP_VERSION_CHECK: '1' PI_TELEMETRY: '0' @@ -335,6 +347,12 @@ jobs: --model "$PI_PREFERRED_MODEL" --thinking max \ @../.github/prompts/update-mcp-coverage.md + - name: Stop model credentials + if: always() + uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main + with: + stop: true + - name: Snapshot publishable changes id: snapshot working-directory: mcp diff --git a/.github/workflows/vuln-remediation.yml b/.github/workflows/vuln-remediation.yml index e7b1ea9b..8ead8df2 100644 --- a/.github/workflows/vuln-remediation.yml +++ b/.github/workflows/vuln-remediation.yml @@ -8,6 +8,8 @@ on: permissions: contents: write pull-requests: write + # vuln-remediation exchanges the job's OIDC token for short-lived model credentials. + id-token: write jobs: remediate: From 39ea00846be58bbbc37cf9775383dd26ead8107b Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:26:17 +0000 Subject: [PATCH 3/3] release: 0.146.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 80e5148f..2bf9d984 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.145.0" + ".": "0.146.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cdb5fb0..b8f82cc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.146.0](https://github.com/kernel/kernel-node-sdk/compare/v0.145.0...v0.146.0) (2026-11-10) + + +### Features + +* Allow auto-starting replays during browser creation and pool acquire ([f7caf3e](https://github.com/kernel/kernel-node-sdk/commit/f7caf3e0631ccce66694709001a9639bc3d1c144)) + ## [0.145.0](https://github.com/kernel/kernel-node-sdk/compare/v0.144.0...v0.145.0) (2026-11-10) diff --git a/package.json b/package.json index 833d03b4..26652fb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/sdk", - "version": "0.145.0", + "version": "0.146.0", "description": "The official TypeScript library for the Kernel API", "author": "Kernel <>", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index cce58f68..6953fb55 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.145.0'; // x-release-please-version +export const VERSION = '0.146.0'; // x-release-please-version