Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions .github/tests/update-coverage-security.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -78,15 +81,15 @@ 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:/);
}
}
});

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) =>
Expand All @@ -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(
Expand All @@ -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/);
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/update-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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'
Expand All @@ -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
Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/update-mcp-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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'
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/vuln-remediation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.145.0"
".": "0.146.0"
}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
22 changes: 22 additions & 0 deletions src/resources/browser-pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/resources/browsers/browsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '0.145.0'; // x-release-please-version
export const VERSION = '0.146.0'; // x-release-please-version
1 change: 1 addition & 0 deletions tests/api-resources/browser-pools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
1 change: 1 addition & 0 deletions tests/api-resources/browsers/browsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
Loading