Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ac51640
feat(host-service): extract WorkspaceRuntime abstraction for PTY laun…
Aug 13, 2026
aae042e
feat(host-service): Docker sandbox runtime — container lifecycle, iso…
Aug 13, 2026
c156570
test(host-service): docker-gated sandbox integration test
Aug 13, 2026
a2f4bee
feat(host-service): per-workspace agent-hook token for sandboxed PTYs
Aug 13, 2026
cc2f79c
feat(cli,host-service): sandbox CLI bridge via SUPERSET_HOST_ENDPOINT…
Aug 13, 2026
2e62f9a
feat(host-service): sandbox image package, declared-port publishing, …
Aug 13, 2026
25965ef
feat(host-service): sandbox git sync — export-on-destroy and workspac…
Aug 13, 2026
98076eb
test(host-service): sandbox end-to-end test against the real app
Aug 13, 2026
beca354
feat(desktop): sandboxed-workspace badge in the dashboard sidebar
Aug 13, 2026
8494042
Merge branch 'main' of https://github.com/superset-sh/superset into f…
Aug 14, 2026
5d7f244
chore: register @superset/sandbox-image workspace in bun.lock
mikigraf Aug 27, 2026
1ae4839
Merge branch 'main' of https://github.com/superset-sh/superset into f…
mikigraf Aug 27, 2026
11039ea
test: adapt sandbox tests to upstream merge
mikigraf Aug 27, 2026
7f9a62a
fix(db): accept db.localhost for the local neon proxy
mikigraf Aug 27, 2026
d39f31a
feat(settings): host-wide sandbox default for new workspaces
mikigraf Aug 27, 2026
67823f9
feat(sandbox): eager container bootstrap at workspace create
mikigraf Aug 27, 2026
f353789
fix(sandbox): allow claude --dangerously-skip-permissions in containers
mikigraf Aug 27, 2026
9dbfb77
feat(sandbox): readable container names + per-instance ownership
mikigraf Aug 27, 2026
940c6f0
feat(sandbox): support all npm-distributed agent CLIs
mikigraf Aug 27, 2026
cbf5820
fix(sandbox): address code-review findings
mikigraf Aug 27, 2026
8157a31
fix(sandbox): address second code-review batch
mikigraf Aug 27, 2026
171b7c1
fix(sandbox): scope CLI tokens + remaining correctness (CWE-862 et al.)
mikigraf Aug 27, 2026
93e75ac
Merge branch 'main' into feat/docker-sandbox-workspaces
mikigraf Aug 27, 2026
a52eb37
fix(sandbox): close WS ACL bypass + document image publishing
mikigraf Aug 27, 2026
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
59 changes: 59 additions & 0 deletions .github/workflows/publish-sandbox-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Publish sandbox image

# Builds and publishes the default Superset workspace sandbox image to GHCR.
# host-service defaults non-development workspaces to
# ghcr.io/superset-sh/sandbox:latest, so this must run before that default is
# reachable in a release. Multi-arch (amd64 + arm64) so both host CPUs work.

on:
push:
branches: [main]
paths:
- "packages/sandbox-image/**"
- ".github/workflows/publish-sandbox-image.yml"
workflow_dispatch:
inputs:
tag:
description: "Image tag to publish (default: latest)"
required: false
type: string
default: latest

concurrency:
group: publish-sandbox-image
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
IMAGE_TAG: ${{ inputs.tag || 'latest' }}
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: packages/sandbox-image
file: packages/sandbox-image/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/superset-sh/sandbox:${{ env.IMAGE_TAG }}
Comment thread
mikigraf marked this conversation as resolved.
Resolved
cache-from: type=gha
cache-to: type=gha,mode=max
# NOTE: the GHCR package must be made PUBLIC once (Packages → sandbox →
# Package settings → Change visibility) so hosts can `docker pull` the default
# ghcr.io/superset-sh/sandbox:latest without a login. Until then, non-dev
# provisioning fails with "denied"; dev hosts use the local superset-sandbox:dev.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export type HostShapedWorkspace = Omit<
export interface HostWorkspaceRow extends HostShapedWorkspace {
worktreePath: string;
worktreeExists: boolean;
/** True when the workspace's terminals run inside a sandbox container. */
sandboxed?: boolean;
sandboxStatus?: "provisioning" | "ready" | "error";
/** Non-null = archived tombstone (only served on `includeArchived`). */
archivedAt?: number | null;
archiveReason?: "merged" | "deleted" | null;
Expand All @@ -36,6 +39,9 @@ export interface HostWorkspaceRow extends HostShapedWorkspace {
export interface HostWorkspaceItem extends HostShapedWorkspace {
worktreePath?: string;
worktreeExists?: boolean;
/** True when the workspace's terminals run inside a sandbox container. */
sandboxed?: boolean;
sandboxStatus?: "provisioning" | "ready" | "error";
/** False when the host didn't answer. */
hostReachable: boolean;
/** Non-null = archived tombstone (only present on `includeArchived`). */
Expand Down Expand Up @@ -232,6 +238,8 @@ export function applyWorkspaceChangedEvent(
createdAt: new Date(snapshot.createdAt),
updatedAt: new Date(snapshot.updatedAt),
worktreePath: snapshot.worktreePath,
sandboxed: snapshot.sandboxed,
sandboxStatus: snapshot.sandboxStatus,
// A host broadcasting created/updated just acted on the worktree;
// keep a known value over assuming.
worktreeExists: existing?.worktreeExists ?? true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useRef,
} from "react";
import { HiCheck, HiMiniMinus, HiMiniXMark } from "react-icons/hi2";
import { LuContainer } from "react-icons/lu";
import { WorkspaceNameMarquee } from "renderer/components/WorkspaceNameMarquee";
import type { DiffStats } from "renderer/hooks/host-service/useDiffStats";
import { useFocusVisible } from "renderer/hooks/useFocusVisible";
Expand Down Expand Up @@ -121,7 +122,14 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef<
}
}, [isActive]);

const creationStatusText = isPending ? "Creating…" : null;
// Sandbox provisioning outlives the create mutation (container ensure is
// fire-and-forget), so it shows both during and after the pending state.
const creationStatusText =
workspace.sandboxStatus === "provisioning"
? "Initializing sandbox…"
: isPending
? "Creating…"
: null;
const isMainWorkspace = workspace.type === "main";
// No hover action button on the local main workspace: a stray click on the
// minus would remove the project's anchor row. Removal stays available via
Expand Down Expand Up @@ -297,19 +305,34 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef<
)}
/>
) : (
<>
<span className="flex min-w-0 items-center gap-1">
<WorkspaceNameMarquee
name={name || branch}
forceActive={isFocused}
className={cn(
"text-[13px] leading-tight transition-colors",
"min-w-0 flex-1 text-[13px] leading-tight transition-colors",
isActive || isSelected
? "text-foreground"
: "text-foreground/80",
)}
/>
{isSelected && <span className="sr-only">, selected</span>}
</>
{workspace.sandboxed && (
<Tooltip delayDuration={300}>
<TooltipTrigger asChild>
<span className="shrink-0 text-muted-foreground">
<LuContainer
className="size-3"
aria-label="Sandboxed workspace"
/>
</span>
</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
Sandboxed — terminals run in a Docker container
</TooltipContent>
</Tooltip>
)}
</span>
)}

<div className="col-start-2 row-start-1 grid h-5 shrink-0 items-center justify-items-end [&>*]:col-start-1 [&>*]:row-start-1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export interface SidebarWorkspaceInput {
tabOrder: number;
sectionId: string | null;
pinnedAt: number | null;
/** True when the workspace's terminals run inside a sandbox container. */
sandboxed?: boolean;
sandboxStatus?: "provisioning" | "ready" | "error";
pendingTransaction: WorkspaceTransactionSnapshot | null;
}

Expand Down Expand Up @@ -104,6 +107,8 @@ function decorateSidebarWorkspace(
updatedAt: workspace.updatedAt,
taskId: workspace.taskId,
isPinned: workspace.pinnedAt != null,
sandboxed: workspace.sandboxed,
sandboxStatus: workspace.sandboxStatus,
pendingTransaction: workspace.pendingTransaction,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ export function useDashboardSidebarData() {
sectionId: localState.sectionId,
isHidden: localState.isHidden,
pinnedAt: localState.pinnedAt,
sandboxed: workspace.sandboxed,
sandboxStatus: workspace.sandboxStatus,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
},
];
}),
Expand Down Expand Up @@ -346,6 +348,8 @@ export function useDashboardSidebarData() {
taskId: workspace.taskId,
createdAt: workspace.createdAt,
updatedAt: workspace.updatedAt,
sandboxed: workspace.sandboxed,
sandboxStatus: workspace.sandboxStatus,
tabOrder: MAIN_WORKSPACE_TAB_ORDER,
sectionId: null as string | null,
// Auto-included mains have no local-state row; pinning one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export interface DashboardSidebarWorkspace {
updatedAt: Date;
taskId: string | null;
isPinned: boolean;
/** True when the workspace's terminals run inside a sandbox container. */
sandboxed?: boolean;
sandboxStatus?: "provisioning" | "ready" | "error";
pendingTransaction: WorkspaceTransactionSnapshot | null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
HiOutlineUser,
HiOutlineUserGroup,
} from "react-icons/hi2";
import { LuBrain, LuGitBranch, LuKeyboard } from "react-icons/lu";
import { LuBrain, LuContainer, LuGitBranch, LuKeyboard } from "react-icons/lu";
import { useIsV2CloudEnabled } from "renderer/hooks/useIsV2CloudEnabled";
import { electronTrpc } from "renderer/lib/electron-trpc";
import type { SettingsSection } from "renderer/stores/settings-state";
Expand All @@ -44,6 +44,7 @@ type SettingsRoute =
| "/settings/behavior"
| "/settings/browser"
| "/settings/git"
| "/settings/sandboxes"
| "/settings/agents"
| "/settings/terminal"
| "/settings/links"
Expand Down Expand Up @@ -124,6 +125,12 @@ const SECTION_GROUPS: SectionGroup[] = [
label: "Git & Worktrees",
icon: <LuGitBranch className="h-4 w-4" />,
},
{
id: "/settings/sandboxes",
section: "sandboxes",
label: "Sandboxes",
icon: <LuContainer className="h-4 w-4" />,
},
{
id: "/settings/agents",
section: "agents",
Expand Down
Loading