diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..49c080c --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,31 @@ +name: Validate kit + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install Docker Sandboxes CLI + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release download \ + --repo docker/sbx-releases \ + --pattern DockerSandboxes-linux-amd64.tar.gz \ + --dir /tmp + tar -xzf /tmp/DockerSandboxes-linux-amd64.tar.gz -C /tmp + sudo env PREFIX="$HOME/.local/docker-sbx" /tmp/docker-sbx/install.sh + echo "$HOME/.local/docker-sbx/bin" >> "$GITHUB_PATH" + + - name: Validate kit + run: scripts/smoke.sh diff --git a/README.md b/README.md index 2ba8243..7648712 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,63 @@ -# Kernel Docker Sandbox Mixin +# Kernel kit for Docker Sandboxes -This repo contains a [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) (`sbx`) mixin kit for [Kernel](https://www.kernel.sh). It gives an agent sandbox Kernel tooling, Kernel skills for Claude Code, and proxy-managed Kernel API authentication without putting your real `KERNEL_API_KEY` inside the sandbox. +This mixin gives any [Docker Sandbox](https://docs.docker.com/ai/sandboxes/) agent access to [Kernel](https://www.kernel.sh/) cloud browsers. It installs the Kernel CLI, adds a quick-reference guide, permits the required network destinations, and keeps the Kernel API key outside the sandbox. -## Quickstart +Docker publishes the kit at [`docker.io/sbx/kernel-kit`](https://hub.docker.com/r/sbx/kernel-kit) from the [`docker/sbx-kits-contrib`](https://github.com/docker/sbx-kits-contrib/tree/main/kernel) repository. -### 1. Install Docker Sandboxes +## Use the published kit -Install and sign in to `sbx` using the [Docker Sandboxes getting started guide](https://docs.docker.com/ai/sandboxes/get-started/). +1. Install Docker Sandboxes and sign in by following Docker's [getting started guide](https://docs.docker.com/ai/sandboxes/get-started/). +2. Create a Kernel API key, then store it in Docker Sandboxes' host-side secret store: -### 2. Set Up Kernel + ```console + sbx secret set kernel + ``` -Create or use a Kernel account at [kernel.sh](https://www.kernel.sh), then create an API key. +3. Launch an agent with the kit: -Export the key in the host shell where you run `sbx`: + ```console + sbx run claude --kit docker.io/sbx/kernel-kit:latest + ``` -```bash -export KERNEL_API_KEY=... -``` - -The real key stays on the host. This kit configures the `sbx` proxy so Kernel API requests from inside the sandbox receive the right auth header. +On first use, `sbx` asks you to approve injecting the `kernel` credential into requests to `api.onkernel.com`. The sandbox receives only a `proxy-managed` sentinel; the host proxy replaces it with the real key when the request leaves the sandbox. -### 3. Set Up Claude +## Develop locally -The built-in Claude sandbox needs Anthropic credentials. Export your API key in the same host shell: +Validate and inspect the kit before creating a sandbox: -```bash -export ANTHROPIC_API_KEY=... +```console +sbx kit validate . +sbx kit inspect . ``` -### 4. Launch Claude With Kernel - -Start Claude with this mixin: +Run the non-destructive checks with: -```bash -sbx run --name kernel-demo --kit . claude -- "Using the Kernel CLI, create a browser and navigate to news.ycombinator.com. Tell me the top five articles." +```console +scripts/smoke.sh ``` -The agent should be able to call `kernel` and efficiently complete the task using the installed Kernel skills inside the sandbox without seeing the real `KERNEL_API_KEY`. - -## What The Mixin Does - -The mixin installs Kernel's CLI: +Run the full smoke test with a disposable Claude sandbox after storing the Kernel credential: -```yaml -commands: - install: - - command: "npm install -g @onkernel/cli" +```console +scripts/smoke.sh --create ``` -It also installs all agent skills from [`kernel/skills`](https://github.com/kernel/skills): +The full test verifies the CLI, bundled quick-reference guide, proxy-managed environment variable, and an authenticated Kernel API request. Set `KEEP_SANDBOX=1` to retain the sandbox for debugging. -```yaml -commands: - install: - - command: "DISABLE_TELEMETRY=1 npm_config_update_notifier=false npx -y skills add kernel/skills --skill '*' --agent claude-code --global --copy --yes && rm -rf \"$HOME/.agents/skills\" && mkdir -p \"$HOME/.agents\" && cp -a \"$HOME/.claude/skills\" \"$HOME/.agents/skills\"" - user: "1000" -``` - -Those flags make the `skills` install noninteractive: select all skills, target Claude Code, install globally into the sandbox agent user's home, copy files instead of symlinking, and accept prompts. After the CLI install, the command copies the resulting `~/.claude/skills` tree to `~/.agents/skills` so agents that read the generic skills location can use the same Kernel skills. - -It allows the package registry, GitHub, skills metadata, and Kernel API: - -```yaml -network: - allowedDomains: - - "registry.npmjs.org:443" - - "github.com:443" - - "api.github.com:443" - - "raw.githubusercontent.com:443" - - "release-assets.githubusercontent.com:443" - - "add-skill.vercel.sh:443" - - "skills.sh:443" - - "api.onkernel.com:443" -``` +## Publish an organization-owned copy -It maps `api.onkernel.com` to a host-side credential source named `kernel`: +Docker Hub publication uses an OCI artifact rather than a container image: -```yaml -credentials: - sources: - kernel: - env: - - KERNEL_API_KEY +```console +sbx login +sbx kit validate . +sbx kit push . docker.io/onkernel/kernel-kit:latest --sign ``` -The proxy injects the API key as an authorization header for Kernel API requests: +The Docker Verified Publisher badge is granted at the Docker Hub namespace level. Pushing a kit does not grant the badge; the `onkernel` namespace must complete Docker's [Verified Publisher application](https://hub.docker.com/publisher-program/apply) separately. -```yaml -network: - serviceDomains: - api.onkernel.com: kernel - serviceAuth: - kernel: - headerName: Authorization - valueFormat: "Bearer %s" -``` +## Kit contents +- `spec.yaml` — schema v2 mixin definition +- `files/home/.kernel/quickstart.md` — examples installed into the agent's home directory +- `scripts/smoke.sh` — local validation and optional end-to-end test diff --git a/files/home/.kernel/quickstart.md b/files/home/.kernel/quickstart.md new file mode 100644 index 0000000..e54e459 --- /dev/null +++ b/files/home/.kernel/quickstart.md @@ -0,0 +1,112 @@ +# Kernel Browser — Quick Reference + +`KERNEL_API_KEY` is proxy-managed: the sandbox holds a placeholder and the +proxy injects the real credential on outbound requests to `api.onkernel.com`. +The real key never enters the VM. + +--- + +## TypeScript / JavaScript + +Add the SDK as a project dependency: + + npm install @onkernel/sdk playwright-core + +Use `playwright-core` (not `playwright`) — it provides `connectOverCDP` +without downloading local Chromium binaries that you won't use. + + import Kernel from '@onkernel/sdk'; + import { chromium } from 'playwright-core'; + + const kernel = new Kernel(); + const session = await kernel.browsers.create({ headless: true }); + + const browser = await chromium.connectOverCDP(session.cdp_ws_url); + const page = browser.contexts()[0].pages()[0]; + + await page.goto('https://example.com'); + console.log(await page.title()); + + await browser.close(); + await kernel.browsers.deleteByID(session.session_id); + +--- + +## Python + +Add the SDK as a project dependency: + + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 pip install kernel playwright + +The env var prevents Playwright from downloading local Chromium binaries. + + import asyncio + from kernel import Kernel + from playwright.async_api import async_playwright + + kernel = Kernel() + session = kernel.browsers.create() + + async def run(): + async with async_playwright() as p: + browser = await p.chromium.connect_over_cdp(session.cdp_ws_url) + page = browser.contexts[0].pages[0] + await page.goto('https://example.com') + print(await page.title()) + await browser.close() + kernel.browsers.delete_by_id(session.session_id) + + asyncio.run(run()) + +--- + +## Stealth mode (residential proxy + CAPTCHA bypass) + + const session = await kernel.browsers.create({ stealth: true }); + +--- + +## GPU acceleration (vision-based / computer-use agents) + + const session = await kernel.browsers.create({ headless: false, gpu: true }); + +Requires Start-Up or Enterprise plan. + +--- + +## Session replays (record as MP4) + + const { replay_id: replayId } = await kernel.browsers.replays.start(session.session_id); + // ... do work ... + await kernel.browsers.replays.stop(replayId, { id_or_name: session.session_id }); + +Download recordings from the Kernel dashboard or via API. + +--- + +## Browser pools (pre-warmed for sub-30ms acquisition) + + const pool = await kernel.browserPools.create({ size: 5, headless: true }); + const session = await kernel.browserPools.acquire(pool.id, {}); + // ... use session ... + await kernel.browserPools.release(pool.id, { session_id: session.session_id }); + +--- + +## Browser profiles (persist login state across sessions) + + const profile = await kernel.profiles.create({ name: 'my-account' }); + const session = await kernel.browsers.create({ + profile: { name: 'my-account', save_changes: true }, + }); + // Log in once — subsequent sessions load the saved profile automatically. + +--- + +## CLI + + kernel browsers create --headless + kernel browsers create --stealth + kernel browsers list + kernel browsers delete + kernel auth # show active user + token expiry diff --git a/scripts/install-sbx-linux.sh b/scripts/install-sbx-linux.sh index a02c548..dfc454d 100755 --- a/scripts/install-sbx-linux.sh +++ b/scripts/install-sbx-linux.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -DEB_URL="${SBX_DEB_URL:-https://github.com/docker/sbx-releases/releases/latest/download/DockerSandboxes-linux-amd64-ubuntu2404.deb}" - if command -v sbx >/dev/null 2>&1; then echo "sbx is already installed: $(command -v sbx)" sbx version || true @@ -15,17 +13,33 @@ if [[ "$(uname -s)" != "Linux" ]]; then fi case "$(uname -m)" in - x86_64 | amd64) ;; + x86_64 | amd64) ARCH=amd64 ;; + aarch64 | arm64) ARCH=arm64 ;; + *) + echo "Unsupported architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + +if [[ -r /etc/os-release ]]; then + # shellcheck disable=SC1091 + source /etc/os-release +fi + +case "${ID:-}:${VERSION_ID:-}" in + ubuntu:24.04) UBUNTU_VERSION=2404 ;; + ubuntu:26.04) UBUNTU_VERSION=2604 ;; *) - echo "Docker Sandboxes currently publishes Linux amd64 packages for this path." >&2 + echo "This helper supports Ubuntu 24.04 and 26.04. Follow Docker's installation guide for this distribution." >&2 exit 1 ;; esac +DEB_URL="${SBX_DEB_URL:-https://github.com/docker/sbx-releases/releases/latest/download/DockerSandboxes-linux-${ARCH}-ubuntu${UBUNTU_VERSION}.deb}" tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' EXIT -echo "Adding Docker apt repository" +echo "Adding Docker's apt repository" curl -fsSL https://get.docker.com | sudo REPO_ONLY=1 sh echo "Installing docker-sbx from apt" @@ -36,8 +50,8 @@ if sudo apt-get install -y docker-sbx; then exit 0 fi -echo "docker-sbx is not available from this apt repository." -echo "Falling back to the Ubuntu 24.04 release .deb: $DEB_URL" +echo "docker-sbx is not available from the configured apt repository." +echo "Falling back to $DEB_URL" deb="$tmpdir/docker-sbx.deb" curl -fsSL "$DEB_URL" -o "$deb" diff --git a/scripts/smoke.sh b/scripts/smoke.sh index abe07a3..e894b2c 100755 --- a/scripts/smoke.sh +++ b/scripts/smoke.sh @@ -11,15 +11,14 @@ usage() { cat <<'EOF' Usage: scripts/smoke.sh [--create] [--sandbox NAME] -Validates the Kernel sbx mixin. By default, this checks local -prerequisites and validates the kit without creating a sandbox. +Validates the Kernel sbx mixin. By default, this checks the local kit without +creating a sandbox. Options: --create Create a fresh Claude sandbox with this kit, then run checks. --sandbox NAME Run checks against an existing sandbox. Environment: - KERNEL_API_KEY Host-side Kernel API key read by the sbx proxy. KEEP_SANDBOX Set to 1 to keep a sandbox created by --create. KIT_DIR Kit directory to validate. Defaults to the repo root. WORKSPACE_DIR Workspace mounted into a created sandbox. Defaults to KIT_DIR. @@ -59,24 +58,30 @@ require_cmd() { fi } -require_cmd sbx -require_cmd docker - -if [[ ! -e /dev/kvm ]]; then - echo "Missing /dev/kvm. Docker Sandboxes require KVM on Linux." >&2 - exit 1 -fi +cleanup() { + if [[ "$CREATED_SANDBOX" -eq 1 && "${KEEP_SANDBOX:-0}" != "1" ]]; then + echo "Removing smoke sandbox $SANDBOX_NAME" + sbx rm --force "$SANDBOX_NAME" >/dev/null + fi +} +trap cleanup EXIT -if [[ -z "${KERNEL_API_KEY:-}" ]]; then - echo "KERNEL_API_KEY must be set on the host for the sbx proxy credential source." >&2 - exit 1 -fi +require_cmd sbx echo "Validating kit at $KIT_DIR" sbx kit validate "$KIT_DIR" sbx kit inspect "$KIT_DIR" if [[ "$CREATE" -eq 1 ]]; then + if [[ ! -e /dev/kvm ]]; then + echo "Missing /dev/kvm. Docker Sandboxes require KVM on Linux." >&2 + exit 1 + fi + if ! sbx secret ls | awk '$2 == "service" && $3 == "kernel" { found = 1 } END { exit !found }'; then + echo "Store a Kernel API key with 'sbx secret set kernel' before running the full smoke test." >&2 + exit 1 + fi + if [[ -z "$SANDBOX_NAME" ]]; then SANDBOX_NAME="kernel-smoke-$(date +%Y%m%d%H%M%S)" fi @@ -91,11 +96,8 @@ if [[ -z "$SANDBOX_NAME" ]]; then Kit validation passed. -To run the full sandbox smoke test: - KERNEL_API_KEY=... scripts/smoke.sh --create - -Or validate manually: - KERNEL_API_KEY=... sbx run --name kernel-demo --kit "$KIT_DIR" claude +After storing a Kernel API key with 'sbx secret set kernel', run: + scripts/smoke.sh --create EOF exit 0 fi @@ -103,16 +105,14 @@ fi echo "Checking Kernel tooling inside sandbox $SANDBOX_NAME" sbx exec "$SANDBOX_NAME" sh -lc 'command -v kernel && kernel --version' -echo "Checking Kernel skills inside sandbox $SANDBOX_NAME" -sbx exec "$SANDBOX_NAME" sh -lc 'test -d "$HOME/.claude/skills/kernel-cli" && test -d "$HOME/.agents/skills/kernel-cli"' +echo "Checking the bundled quick-reference guide" +sbx exec "$SANDBOX_NAME" test -f /home/agent/.kernel/quickstart.md -echo "Checking Kernel API access through the proxy" -sbx exec "$SANDBOX_NAME" sh -lc 'kernel browsers list >/dev/null' +echo "Checking the proxy-managed credential sentinel" +sbx exec "$SANDBOX_NAME" sh -lc 'test "$KERNEL_API_KEY" = proxy-managed' -echo "Recent Kernel policy matches, if present:" -sbx policy log | grep -E 'api\.onkernel\.com|kernel' || true +echo "Checking Kernel API access through the proxy" +sbx exec "$SANDBOX_NAME" kernel browsers list >/dev/null -if [[ "$CREATED_SANDBOX" -eq 1 && "${KEEP_SANDBOX:-0}" != "1" ]]; then - echo "Removing smoke sandbox $SANDBOX_NAME" - sbx rm --force "$SANDBOX_NAME" -fi +echo "Recent Kernel network policy matches:" +sbx policy log "$SANDBOX_NAME" --type network --limit 20 | grep -E 'api\.onkernel\.com|kernel' || true diff --git a/spec.yaml b/spec.yaml index 1606e32..7aba20c 100644 --- a/spec.yaml +++ b/spec.yaml @@ -1,40 +1,47 @@ -schemaVersion: "1" +schemaVersion: "2" kind: mixin name: kernel -displayName: Kernel -description: Add Kernel tooling and proxy-managed Kernel API auth to Docker sandboxes +displayName: Kernel Browser +description: Cloud-hosted Chromium for AI agents with stealth, managed auth, and live session replay +sourceURL: https://github.com/kernel/docker-sbx-kit +licenses: + - Apache-2.0 -network: - allowedDomains: - - "registry.npmjs.org:443" - - "github.com:443" - - "api.github.com:443" - - "raw.githubusercontent.com:443" - - "release-assets.githubusercontent.com:443" - - "add-skill.vercel.sh:443" - - "skills.sh:443" - - "api.onkernel.com:443" - serviceDomains: - api.onkernel.com: kernel - serviceAuth: - kernel: - headerName: Authorization - valueFormat: "Bearer %s" +permissions: + network: + allow: + # Kernel REST API and regional CDP WebSocket proxy hosts. + - '*.onkernel.com' + # Package registries used when agents add a Kernel SDK. + - registry.npmjs.org + - '*.npmjs.org' + - pypi.org + - files.pythonhosted.org + # The Kernel CLI postinstall downloads its binary from GitHub Releases. + - github.com + - objects.githubusercontent.com + - release-assets.githubusercontent.com credentials: - sources: - kernel: - env: - - KERNEL_API_KEY + - service: kernel + description: Kernel API key + required: true + apiKey: + name: KERNEL_API_KEY + proxyManaged: true + inject: + - domain: api.onkernel.com + header: Authorization + format: Bearer %s -environment: - proxyManaged: - - KERNEL_API_KEY +agentInstructions: + content: | + The Kernel CLI is installed for cloud browser automation. Before using it, + read /home/agent/.kernel/quickstart.md for current CLI and SDK examples. + Delete browser sessions when work is complete. -commands: +setup: install: - - command: "npm install -g @onkernel/cli" - description: Install Kernel tooling - - command: "DISABLE_TELEMETRY=1 npm_config_update_notifier=false npx -y skills add kernel/skills --skill '*' --agent claude-code --global --copy --yes && rm -rf \"$HOME/.agents/skills\" && mkdir -p \"$HOME/.agents\" && cp -a \"$HOME/.claude/skills\" \"$HOME/.agents/skills\"" - user: "1000" - description: Install Kernel skills for Claude Code + - command: npm install -g @onkernel/cli + user: "0" + description: Install the Kernel CLI