Make KERNEL_API_KEY optional for external credential injection (#687)

* Make KERNEL_API_KEY optional for external credential injection

When running inside environments with external credential injection
(e.g. Vercel Sandbox credentials brokering), the KERNEL_API_KEY env
var can be omitted. The network layer injects the Authorization header
on outbound requests to api.onkernel.com, so the API key never needs
to exist inside the sandbox.

If KERNEL_API_KEY is set, it's used as before. If not, requests are
sent without an Authorization header, allowing external injection.
Without either, the Kernel API returns 401.

Made-with: Cursor

* Make KERNEL_API_KEY optional in native Rust daemon too

Applies the same change to the native Rust connect_kernel() function
so both the Node.js and native code paths support external credential
injection.

Made-with: Cursor

* Address review feedback: fix type errors, cargo fmt, always send cleanup DELETE

- Fix kernelApiKey assignment: use ?? null for undefined -> null
- Fix closeKernelSession signature: accept string | undefined
- Always send DELETE on cleanup even without local API key (external
  injection covers it)
- Run cargo fmt on Rust code

Made-with: Cursor
This commit is contained in:
Mason Williams
2026-03-09 17:04:58 -05:00
committed by GitHub
parent 5bf9fedd58
commit d0651f14bc
2 changed files with 35 additions and 26 deletions
+6 -6
View File
@@ -185,8 +185,7 @@ async fn connect_browser_use() -> Result<(String, Option<ProviderSession>), Stri
}
async fn connect_kernel() -> Result<(String, Option<ProviderSession>), String> {
let api_key =
env::var("KERNEL_API_KEY").map_err(|_| "KERNEL_API_KEY environment variable is not set")?;
let api_key = env::var("KERNEL_API_KEY").ok();
let endpoint =
env::var("KERNEL_ENDPOINT").unwrap_or_else(|_| "https://api.onkernel.com".to_string());
@@ -218,10 +217,11 @@ async fn connect_kernel() -> Result<(String, Option<ProviderSession>), String> {
}
let client = reqwest::Client::new();
let response = client
.post(&url)
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
let mut request = client.post(&url).header("Content-Type", "application/json");
if let Some(ref key) = api_key {
request = request.header("Authorization", format!("Bearer {}", key));
}
let response = request
.json(&body)
.send()
.await
+29 -20
View File
@@ -891,12 +891,14 @@ export class BrowserManager {
/**
* Close a Kernel session via API
*/
private async closeKernelSession(sessionId: string, apiKey: string): Promise<void> {
private async closeKernelSession(sessionId: string, apiKey: string | undefined): Promise<void> {
const headers: Record<string, string> = {};
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
const response = await fetch(`https://api.onkernel.com/browsers/${sessionId}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
},
headers,
});
if (!response.ok) {
@@ -974,16 +976,19 @@ export class BrowserManager {
*/
private async findOrCreateKernelProfile(
profileName: string,
apiKey: string
apiKey: string | undefined
): Promise<{ name: string }> {
const headers: Record<string, string> = {};
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
// First, try to get the existing profile
const getResponse = await fetch(
`https://api.onkernel.com/profiles/${encodeURIComponent(profileName)}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
},
headers,
}
);
@@ -1001,7 +1006,7 @@ export class BrowserManager {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
...headers,
},
body: JSON.stringify({ name: profileName }),
});
@@ -1015,13 +1020,13 @@ export class BrowserManager {
/**
* Connect to Kernel remote browser via CDP.
* Requires KERNEL_API_KEY environment variable.
* Uses KERNEL_API_KEY environment variable for authentication when set.
* When running inside environments with external credential injection
* (e.g. Vercel Sandbox credentials brokering), the API key can be omitted
* and auth headers will be injected at the network layer.
*/
private async connectToKernel(): Promise<void> {
const kernelApiKey = process.env.KERNEL_API_KEY;
if (!kernelApiKey) {
throw new Error('KERNEL_API_KEY is required when using kernel as a provider');
}
// Find or create profile if KERNEL_PROFILE_NAME is set
const profileName = process.env.KERNEL_PROFILE_NAME;
@@ -1037,12 +1042,16 @@ export class BrowserManager {
};
}
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (kernelApiKey) {
headers['Authorization'] = `Bearer ${kernelApiKey}`;
}
const response = await fetch('https://api.onkernel.com/browsers', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${kernelApiKey}`,
},
headers,
body: JSON.stringify({
// Kernel browsers are headful by default with stealth mode available
// The user can configure these via environment variables if needed
@@ -1093,7 +1102,7 @@ export class BrowserManager {
}
this.kernelSessionId = session.session_id;
this.kernelApiKey = kernelApiKey;
this.kernelApiKey = kernelApiKey ?? null;
this.browser = browser;
context.setDefaultTimeout(getDefaultTimeout());
this.contexts.push(context);
@@ -2513,8 +2522,8 @@ export class BrowserManager {
}
);
this.browser = null;
} else if (this.kernelSessionId && this.kernelApiKey) {
await this.closeKernelSession(this.kernelSessionId, this.kernelApiKey).catch((error) => {
} else if (this.kernelSessionId) {
await this.closeKernelSession(this.kernelSessionId, this.kernelApiKey ?? undefined).catch((error) => {
console.error('Failed to close Kernel session:', error);
});
this.browser = null;