dev-bubble 0.6.20__tar.gz → 0.7.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/.claude/CLAUDE.md +18 -11
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/CHANGELOG.md +44 -2
- {dev_bubble-0.6.20/dev_bubble.egg-info → dev_bubble-0.7.3}/PKG-INFO +67 -25
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/README.md +66 -24
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/SPEC.md +45 -22
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/__init__.py +1 -1
- dev_bubble-0.7.3/bubble/auth_proxy.py +1161 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/automation.py +3 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/claude.py +6 -4
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/cli.py +110 -31
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/clone.py +8 -9
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/cloud.py +2 -2
- dev_bubble-0.7.3/bubble/commands/completion.py +119 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/images.py +4 -3
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/list_cmd.py +89 -12
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/relay_cmd.py +2 -4
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/security_cmd.py +14 -8
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/settings.py +155 -65
- dev_bubble-0.7.3/bubble/commands/status_cmd.py +202 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/config.py +0 -11
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/container_helpers.py +14 -10
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/finalization.py +19 -11
- dev_bubble-0.7.3/bubble/github_token.py +610 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/hooks/lean.py +2 -4
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/image_management.py +14 -15
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/builder.py +23 -9
- dev_bubble-0.7.3/bubble/images/scripts/tools/gh.sh +34 -0
- dev_bubble-0.7.3/bubble/lean.py +7 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/lifecycle.py +31 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/native.py +11 -10
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/network.py +0 -81
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/notices.py +0 -5
- dev_bubble-0.7.3/bubble/output.py +18 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/provisioning.py +68 -15
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/relay.py +7 -2
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/runtime/colima.py +63 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/security.py +108 -63
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/setup.py +0 -21
- dev_bubble-0.7.3/bubble/spinner.py +46 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/target.py +36 -48
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/tools.py +7 -10
- {dev_bubble-0.6.20 → dev_bubble-0.7.3/dev_bubble.egg-info}/PKG-INFO +67 -25
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/dev_bubble.egg-info/SOURCES.txt +10 -0
- dev_bubble-0.7.3/tests/test_auth_proxy.py +1243 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_branch_no_target.py +11 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_build_lock.py +37 -8
- dev_bubble-0.7.3/tests/test_completion.py +122 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_config.py +127 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_customize.py +7 -7
- dev_bubble-0.7.3/tests/test_github_token.py +480 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_integration.py +4 -19
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_lifecycle.py +35 -0
- dev_bubble-0.7.3/tests/test_list_columns.py +83 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_mounts.py +151 -24
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_notices.py +0 -6
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_security.py +325 -115
- dev_bubble-0.7.3/tests/test_spinner.py +65 -0
- dev_bubble-0.7.3/tests/test_status.py +243 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_tools.py +8 -32
- dev_bubble-0.6.20/bubble/auth_proxy.py +0 -543
- dev_bubble-0.6.20/bubble/github_token.py +0 -318
- dev_bubble-0.6.20/tests/test_auth_proxy.py +0 -498
- dev_bubble-0.6.20/tests/test_github_token.py +0 -232
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/.github/workflows/ci.yml +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/.github/workflows/publish.yml +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/.gitignore +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/LICENSE +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/__main__.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/clean.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/cloud_types.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/__init__.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/cloud_cmd.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/doctor.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/infrastructure.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/lifecycle.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/commands/remote_cmd.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/data/skill.md +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/default_repos.json +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/git_store.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/hooks/__init__.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/hooks/python.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/__init__.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/base.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/cloud-init.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/lean-toolchain.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/lean.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/python.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/tools/claude.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/tools/codex.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/tools/elan.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/tools/emacs.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/tools/neovim.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/tools/pins.json +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/images/scripts/tools/vscode.sh +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/naming.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/remote.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/repo_registry.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/runtime/__init__.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/runtime/base.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/runtime/incus.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/skill.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/token_store.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/tunnel.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/bubble/vscode.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/config/com.bubble.git-update.plist +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/config/com.bubble.image-refresh.plist +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/config/com.bubble.relay-daemon.plist +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/conftest.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/dev_bubble.egg-info/dependency_links.txt +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/dev_bubble.egg-info/entry_points.txt +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/dev_bubble.egg-info/requires.txt +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/dev_bubble.egg-info/top_level.txt +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/pyproject.toml +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/setup.cfg +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/conftest.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_claude.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_claude_projects_symlink.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_cloud.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_editor.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_git_store.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_hooks.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_list_remote.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_multi_target.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_naming.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_native.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_network.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_relay.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_remote.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_repo_registry.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_skill.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_target.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_tunnel.py +0 -0
- {dev_bubble-0.6.20 → dev_bubble-0.7.3}/tests/test_vscode.py +0 -0
|
@@ -72,7 +72,7 @@ Images are defined in `builder.py`'s `IMAGES` dict with script and parent refere
|
|
|
72
72
|
Tools are installed in container images via the `[tools]` config section. Each tool has a self-contained install script in `bubble/images/scripts/tools/` and is registered in the `TOOLS` dict in `tools.py` with its script filename, host detection command, required network domains, and a priority for install ordering. Tools include:
|
|
73
73
|
|
|
74
74
|
- **Language tools** (priority 10): `elan` — auto-detected if elan is on the host
|
|
75
|
-
- **General tools** (priority 50): `claude`, `codex` — auto-detected via host commands
|
|
75
|
+
- **General tools** (priority 50): `claude`, `codex`, `gh` — auto-detected via host commands
|
|
76
76
|
- **Editors** (priority 90): `vscode`, `emacs`, `neovim` — driven by the `editor` config key
|
|
77
77
|
|
|
78
78
|
Config values are `"yes"`, `"no"`, or `"auto"` (default). Editor tools are special: the configured editor (default: vscode) is treated as `"yes"` unless explicitly `"no"` in `[tools]`. Tools are installed into the `base` image during `build_image("base")` in priority order (language tools before editors, so vscode can detect elan and install Lean extensions). When the resolved tool set changes (detected via a content-aware hash stored in `~/.bubble/tools-hash`), the `base` image is rebuilt synchronously, and stale derived images are purged.
|
|
@@ -114,21 +114,32 @@ Bubbles can run on a remote machine instead of locally. The `--ssh HOST` flag (o
|
|
|
114
114
|
|
|
115
115
|
**Priority chain for remote host resolution:** `--local` > `--ssh HOST` > `--cloud` > `[cloud] default` > `[remote] default_host`
|
|
116
116
|
|
|
117
|
-
**State:** `~/.bubble/cloud.json` tracks server ID, IP, SSH key ID. Token comes from `
|
|
117
|
+
**State:** `~/.bubble/cloud.json` tracks server ID, IP, SSH key ID. Token comes from `HCLOUD_TOKEN` env var (never stored).
|
|
118
118
|
|
|
119
119
|
### User Customization Script
|
|
120
120
|
Users can place a `customize.sh` script at `~/.bubble/customize.sh` to run custom setup in all container images. The script runs as root as the final step when building any image (base, lean, lean-v4.X.Y). This lets users add tools, dotfiles, shell config, etc. without forking image scripts. The script's content hash is tracked in `~/.bubble/customize-hash`; on `bubble open`, if the hash differs from the stored value, a background rebuild of the base image is triggered (same pattern as VS Code commit hash drift). Code is in `builder.py` (`customize_hash()`, `_run_customize_script()`).
|
|
121
121
|
|
|
122
122
|
### GitHub Auth Proxy
|
|
123
|
-
The auth proxy (`auth_proxy.py`) provides repo-scoped GitHub authentication without injecting the host's token into containers. It's an HTTP reverse proxy that runs on the host
|
|
123
|
+
The auth proxy (`auth_proxy.py`) provides repo-scoped GitHub authentication without injecting the host's token into containers. It's an HTTP reverse proxy that runs on the host with graduated access levels:
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
| Level | Description | Routes |
|
|
126
|
+
|-------|-------------|--------|
|
|
127
|
+
| 1 | Git only | `/git/{owner}/{repo}/...` (smart HTTP) |
|
|
128
|
+
| 2 | Git + REST read | + `GET /repos/{owner}/{repo}/...` |
|
|
129
|
+
| 3 | Git + gh read-only (default) | + `POST /graphql` (queries only, mutations blocked) |
|
|
130
|
+
| 4 | Git + gh read-write | + mutations + REST POST/PATCH/DELETE |
|
|
126
131
|
|
|
127
|
-
**
|
|
132
|
+
**Git flow:** Container git → `url.insteadOf` rewrites to `http://127.0.0.1:7654/git/...` → proxy validates `X-Bubble-Token` header → checks path matches allowed `owner/repo` → adds `Authorization: token <real-token>` → forwards to `https://github.com` → returns response.
|
|
128
133
|
|
|
129
|
-
**
|
|
134
|
+
**gh CLI flow:** `gh` configured with `http_unix_socket: /bubble/gh-proxy.sock` (via `GH_CONFIG_DIR=/etc/bubble/gh`) → sends requests through Unix socket → proxy validates token from `Authorization` header → enforces access level (REST repo-scoping, GraphQL mutation filtering) → adds real token → forwards to `https://api.github.com`.
|
|
130
135
|
|
|
131
|
-
**
|
|
136
|
+
**API security:** REST paths validated against `/repos/{owner}/{repo}/...` (repo-scoped). GraphQL scans ALL operations in a document and classifies by the most dangerous one — `query` allowed at level 3, `mutation` requires level 4. This prevents `operationName`-based bypasses where a query is listed first but a mutation is selected for execution. Batched requests, subscriptions, and malformed bodies rejected. Note: GraphQL is NOT repo-scoped — queries can access any data the host token can read (GitHub's GraphQL API doesn't support path-based scoping). API redirects (e.g. CI log downloads) followed with hardened rules: GET/HEAD only, HTTPS only, allowlisted hosts, max 2 hops, auth headers stripped. GitHub 4xx errors are passed through to clients (not collapsed to 502).
|
|
137
|
+
|
|
138
|
+
**Local bubbles:** Exposed via Incus proxy devices — TCP for git, Unix socket for gh (`listen=unix:/bubble/gh-proxy.sock`).
|
|
139
|
+
|
|
140
|
+
**Remote/cloud bubbles:** SSH reverse tunnel forwards the local proxy port. Incus proxy devices on the remote expose both TCP and Unix socket endpoints.
|
|
141
|
+
|
|
142
|
+
**Token management:** Per-container tokens in `~/.bubble/auth-tokens.json` map to `{container, owner, repo, level}`. Tokens are cleaned up on `bubble pop`. The daemon is managed via launchd/systemd.
|
|
132
143
|
|
|
133
144
|
### Security Model
|
|
134
145
|
The `user` account has no sudo and a locked password. Network allowlisting is applied on container creation. SSH keys are injected via `incus file push` (not shell interpolation). All user-supplied values in shell commands are quoted with `shlex.quote()`. Each container mounts only its specific bare repo, not the entire git store.
|
|
@@ -241,10 +252,6 @@ VS Code must be restarted after modifying this database.
|
|
|
241
252
|
### Pre-baked VS Code Server
|
|
242
253
|
When vscode is enabled as a tool (the default), the base image pre-installs the VS Code Server binary matching the host's `code --version` commit hash. On each `bubble open`, if the hash has changed (VS Code updated), a background `bubble images build base` is triggered. The current bubble proceeds immediately; the next one gets the pre-baked server.
|
|
243
254
|
|
|
244
|
-
## Changelog
|
|
245
|
-
|
|
246
|
-
`CHANGELOG.md` in the project root tracks user-visible changes by version. When making changes that will be released, add a brief entry under the current version heading. When tagging a new release, add a new version heading.
|
|
247
|
-
|
|
248
255
|
## PyPI Publishing
|
|
249
256
|
|
|
250
257
|
The package is published to PyPI as **`dev-bubble`** (the CLI command is still `bubble`). Users install with `pipx install dev-bubble` or `uv tool install dev-bubble`.
|
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1 — 2026-03-12
|
|
4
|
+
- Deprecate `config security`, `config lockdown`, `config accept-risks` in favor of `security` subcommands (#150)
|
|
5
|
+
- Add deprecation warnings (to stderr) when deprecated commands are used
|
|
6
|
+
- Deprecation notes explain behavioral differences (`config lockdown`/`accept-risks` only pin `auto` settings vs `security lockdown`/`permissive` which set all)
|
|
7
|
+
- Update error messages to suggest `bubble security set` instead of `bubble config set`
|
|
8
|
+
- Keep `config set security.<name>` as a supported alias
|
|
9
|
+
|
|
10
|
+
## 0.7.0 — 2026-03-12
|
|
11
|
+
- GitHub API access via auth proxy: `gh` CLI shim using `http_unix_socket` (#123)
|
|
12
|
+
- Graduated access level model: level 1 (git only), level 2 (REST read), level 3 (gh read-only, default), level 4 (gh read-write)
|
|
13
|
+
- `gh` CLI installed as a tool (`auto` mode, detected from host) and configured to route through auth proxy
|
|
14
|
+
- REST API requests validated against `/repos/{owner}/{repo}/...` (repo-scoped)
|
|
15
|
+
- GraphQL requests parsed and classified: `query` operations allowed at level 3, `mutation` operations blocked
|
|
16
|
+
- GraphQL security: scans ALL operations in multi-operation documents to prevent `operationName`-based mutation bypass
|
|
17
|
+
- GraphQL validation: rejects malformed JSON, batched requests, and subscriptions
|
|
18
|
+
- GraphQL is NOT repo-scoped (queries can access any data the host token can read)
|
|
19
|
+
- Redirect following for API responses (CI log downloads): GET/HEAD only, HTTPS only, allowlisted hosts, max 2 hops, auth headers stripped
|
|
20
|
+
- GitHub 4xx errors (401, 403, 404, 422) passed through to clients instead of being collapsed to 502
|
|
21
|
+
- Auth proxy accepts tokens from `Authorization` header (gh traffic) in addition to `X-Bubble-Token` (git traffic)
|
|
22
|
+
- Unix socket proxy device exposes auth proxy to `gh` at `/bubble/gh-proxy.sock` inside containers
|
|
23
|
+
- `GH_CONFIG_DIR` and `GH_TOKEN` configured via `/etc/profile.d/bubble-gh.sh`
|
|
24
|
+
- New `security.github_api` setting controls API access level (auto defaults to on = level 3)
|
|
25
|
+
- Host GitHub token never enters containers; all API access goes through the auth proxy
|
|
26
|
+
- Works with remote/cloud bubbles via existing SSH tunnel infrastructure
|
|
27
|
+
|
|
28
|
+
## 0.6.23 — 2026-03-12
|
|
29
|
+
- Add `bubble config show` command to display effective configuration with origin annotations (#149)
|
|
30
|
+
|
|
31
|
+
## 0.6.22 — 2026-03-12
|
|
32
|
+
- Add heartbeat messages for slow image builds (#145)
|
|
33
|
+
- Prints `still building...` / `still installing tools...` to stderr every 10s after 5s of silence
|
|
34
|
+
- Automatically disabled for non-TTY output (piped, `--machine-readable`, CI)
|
|
35
|
+
|
|
36
|
+
## 0.6.21 — 2026-03-12
|
|
37
|
+
- Hide `skill`, `claude`, `codex` from top-level help; hide `config security`, `config lockdown`, `config accept-risks` as deprecated aliases (#142)
|
|
38
|
+
- All commands remain fully functional, just not listed in `--help`
|
|
39
|
+
- Reduces visual noise: 15+ command groups → 12 visible in top-level help
|
|
40
|
+
|
|
3
41
|
## 0.6.20 — 2026-03-12
|
|
4
42
|
- Normalize CLI setting names to hyphens (#143)
|
|
5
43
|
- `bubble security set` and `bubble config set` now accept hyphenated names (e.g. `github-auth`, `claude-credentials`)
|
|
@@ -24,6 +62,10 @@
|
|
|
24
62
|
- Extracted shared `_power_on_and_wait()` helper used by both `start_server()` and `get_cloud_remote_host()`
|
|
25
63
|
|
|
26
64
|
## 0.6.16 — 2026-03-12
|
|
65
|
+
- Consistent indentation in `bubble open` progress output (#144)
|
|
66
|
+
- Introduce `step()` / `detail()` helpers in `output.py` for two-level output formatting
|
|
67
|
+
- Top-level steps have no indent; sub-details get 2-space indent
|
|
68
|
+
- Applied consistently across local, native, and remote code paths
|
|
27
69
|
- Print a welcome banner on first run when `config.toml` is created (#139)
|
|
28
70
|
- Split README Quick Start into a short 3-command section plus a detailed Examples section (#154)
|
|
29
71
|
- Standardize error handling across the codebase (#155)
|
|
@@ -236,8 +278,8 @@
|
|
|
236
278
|
- `bubble config accept-risks` silences on-by-default warnings; `bubble config lockdown` disables off-by-default features
|
|
237
279
|
- Security warnings printed to stderr for all `auto` settings during `bubble open`
|
|
238
280
|
- `BUBBLE_QUIET_SECURITY=1` env var suppresses warnings for CI/automation
|
|
239
|
-
- Settings: `shared_cache`, `user_mounts`, `
|
|
240
|
-
- When `shared_cache=off`, shared mounts are read-only
|
|
281
|
+
- Settings: `shared_cache`, `user_mounts`, `relay`, `claude_credentials`, `host_key_trust`, `git_manifest_trust`
|
|
282
|
+
- When `shared_cache=off`, shared mounts are read-only
|
|
241
283
|
- When `user_mounts=off` or `claude_credentials=off`, corresponding CLI flags are rejected
|
|
242
284
|
- Relay enable/disable migrated to `[security] relay` with backwards compatibility for `[relay] enabled`
|
|
243
285
|
- Replaces ad-hoc "Tip: use --claude-credentials" message with unified security warning system
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dev-bubble
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Containerized development environments powered by Incus
|
|
5
5
|
Author-email: Kim Morrison <kim@tqft.net>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -34,7 +34,10 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|
# bubble
|
|
36
36
|
|
|
37
|
-
Containerized development environments
|
|
37
|
+
Containerized development environments,
|
|
38
|
+
with opinionated presets for users of the [Lean](https://lean-lang.org/) programming language,
|
|
39
|
+
powered by [Incus](https://linuxcontainers.org/incus/).
|
|
40
|
+
We assume that you work using VSCode, emacs, or neovim, and that you collaborate via GitHub.
|
|
38
41
|
|
|
39
42
|
## Quick Start
|
|
40
43
|
|
|
@@ -51,6 +54,29 @@ bubble list
|
|
|
51
54
|
|
|
52
55
|
See [Examples](#examples) for branches, local repos, issues, remote, and non-interactive use.
|
|
53
56
|
|
|
57
|
+
## Use cases
|
|
58
|
+
|
|
59
|
+
You might be worried about:
|
|
60
|
+
* Reviewing and running Lean code from strangers.
|
|
61
|
+
* Running an AI agent like Claude in "yolo" mode.
|
|
62
|
+
* Managing work and AI threads across many repositories and branches.
|
|
63
|
+
|
|
64
|
+
The `bubble` tool attempts to provide a fast, low-friction, secure solution to these problems.
|
|
65
|
+
It launches containers pre-built for working with Lean (and your favorite AI tools),
|
|
66
|
+
and handles repository management and caches behind the scenes.
|
|
67
|
+
|
|
68
|
+
By working in containers, you can prevent AI tools from accidentally damaging your system
|
|
69
|
+
(or other repositories you have access to)
|
|
70
|
+
and handle potentially adversarial code from other humans or agents.
|
|
71
|
+
Opening Lean files from untrusted sources
|
|
72
|
+
(e.g. reviewing code, or supply chain attacks on your upstream dependencies)
|
|
73
|
+
can execute arbitrary code, so if you regularly do this you should be using containers.
|
|
74
|
+
|
|
75
|
+
`bubble` also allows you to limit how your GitHub access can be used:
|
|
76
|
+
containers don't have direct access to GitHub, and never see your authorization token,
|
|
77
|
+
but interact through a restricted proxy.
|
|
78
|
+
You can choose via the `bubble security` tool the capability/security trade-off you prefer.
|
|
79
|
+
|
|
54
80
|
## Examples
|
|
55
81
|
|
|
56
82
|
```bash
|
|
@@ -121,7 +147,7 @@ Each "bubble" is a lightweight Linux container (via Incus) with:
|
|
|
121
147
|
|
|
122
148
|
**Language hooks**: bubble automatically detects the project's language and selects the right image. For Lean 4 projects (detected via `lean-toolchain`), the container includes elan, pre-installed VS Code extensions, and auto-downloads the mathlib cache when needed.
|
|
123
149
|
|
|
124
|
-
**Network allowlisting**: Containers can only reach allowed domains (
|
|
150
|
+
**Network allowlisting**: Containers can only reach allowed domains (language-specific domains like `releases.lean-lang.org` for Lean, plus any configured in `~/.bubble/config.toml`). Direct GitHub access is blocked by iptables — all GitHub traffic is forced through the auth proxy, which enforces repo-scoping and rate limits. IPv6 is blocked, DNS is restricted to the container resolver, and outbound SSH is blocked.
|
|
125
151
|
|
|
126
152
|
## Requirements
|
|
127
153
|
|
|
@@ -148,21 +174,37 @@ Each "bubble" is a lightweight Linux container (via Incus) with:
|
|
|
148
174
|
| `bubble cloud default on\|off` | Set cloud as the default for all bubbles |
|
|
149
175
|
| `bubble cloud ssh` | SSH directly to the cloud server |
|
|
150
176
|
| `bubble tools list\|set\|status` | Manage tools installed in container images |
|
|
177
|
+
| `bubble security [set\|permissive\|lockdown\|default]` | Review and manage security posture |
|
|
151
178
|
| `bubble doctor` | Diagnose and fix common issues |
|
|
152
179
|
|
|
180
|
+
## Security
|
|
181
|
+
|
|
182
|
+
- **No sudo**: The `user` account has no sudo access and a locked password
|
|
183
|
+
- **Network allowlisting**: iptables rules restrict outbound connections to allowed domains only
|
|
184
|
+
- **IPv6 blocked**: All IPv6 traffic is dropped
|
|
185
|
+
- **DNS restricted**: DNS queries only go to the container's configured resolver
|
|
186
|
+
- **No outbound SSH**: Containers cannot SSH out (VSCode uses `incus exec` ProxyCommand)
|
|
187
|
+
- **SSH key-only auth**: Password authentication is disabled
|
|
188
|
+
- **Shell injection hardening**: All user-supplied values are quoted with `shlex.quote()`
|
|
189
|
+
- **Per-repo git mount**: Each container only sees its own bare repo, not the entire git store
|
|
190
|
+
- **Bubble-in-bubble relay**: Containers can open new bubbles on the host, but only for repos already cloned in `~/.bubble/git/`. Disable with `bubble security set relay off`
|
|
191
|
+
- **GitHub auth proxy**: Host token never enters containers. Git and REST API are repo-scoped. **GraphQL queries are read-only but account-wide** — can read any data the host token can access. API access can be set to `off`, `on` (read-only, the default), or `read-write` (enables mutations) via `bubble security set github-api`
|
|
192
|
+
- **Shared mathlib cache**: The mathlib cache at `~/.bubble/mathlib-cache/` is shared across Lean containers. Modes: `on` (default) = read-write (a compromised container could poison cached artifacts), `off` = read-only (prevents poisoning), `overlay` = read-only with per-container writable overlay. This cache is *not* shared with `lake exe cache` run outside a bubble. Configure with `bubble security set shared-cache`. If you suspect poisoning, delete `~/.bubble/mathlib-cache/`.
|
|
193
|
+
|
|
153
194
|
## Images
|
|
154
195
|
|
|
155
|
-
Images are built automatically on first use.
|
|
196
|
+
Images are built automatically on first use. Any enabled [tools](#tools) are installed in the `base` image and inherited by all derived images.
|
|
156
197
|
|
|
157
198
|
| Image | Contents |
|
|
158
199
|
|-------|----------|
|
|
159
|
-
| `base` | Ubuntu 24.04, git, openssh-server, build-essential,
|
|
160
|
-
| `lean` | base + elan
|
|
200
|
+
| `base` | Ubuntu 24.04, git, openssh-server, build-essential, plus configured tools |
|
|
201
|
+
| `lean` | base + leantar (+ elan fallback if not installed as a tool) |
|
|
202
|
+
| `python` | base + uv, ruff |
|
|
161
203
|
| `lean-v4.X.Y` | lean + specific toolchain pre-installed (built lazily on demand) |
|
|
162
204
|
|
|
163
|
-
`base` and `
|
|
205
|
+
`base`, `lean`, and `python` are static images you can rebuild with `bubble images build <name>`. Versioned `lean-v4.X.Y` images are built automatically in the background when a project uses a stable/RC toolchain not yet cached — the current bubble proceeds immediately with elan downloading the toolchain on demand, and the next bubble for that version starts instantly.
|
|
164
206
|
|
|
165
|
-
For mathlib or mathlib-dependent projects, a VS Code terminal automatically runs `lake exe cache get` when the workspace opens.
|
|
207
|
+
When VS Code is the configured editor and elan is installed, the base image also includes pre-baked VS Code Lean 4 extensions and an auto-cache extension. For mathlib or mathlib-dependent projects, a VS Code terminal automatically runs `lake exe cache get` when the workspace opens.
|
|
166
208
|
|
|
167
209
|
## Configuration
|
|
168
210
|
|
|
@@ -203,7 +245,7 @@ Tools like Claude Code and OpenAI Codex can be installed in container images. Ea
|
|
|
203
245
|
|
|
204
246
|
```bash
|
|
205
247
|
bubble tools list # show all tools and their settings
|
|
206
|
-
bubble tools set claude yes
|
|
248
|
+
bubble tools set claude yes # always install
|
|
207
249
|
bubble tools set codex no # never install
|
|
208
250
|
bubble tools status # show what would actually be installed
|
|
209
251
|
```
|
|
@@ -239,7 +281,7 @@ Run bubbles on auto-provisioned Hetzner Cloud servers. The server shuts down aut
|
|
|
239
281
|
2. Go to your project → Security → API Tokens → Generate API Token (read/write)
|
|
240
282
|
3. Set the token in your environment:
|
|
241
283
|
```bash
|
|
242
|
-
export
|
|
284
|
+
export HCLOUD_TOKEN="your-token-here"
|
|
243
285
|
```
|
|
244
286
|
4. Install the cloud dependency:
|
|
245
287
|
```bash
|
|
@@ -319,33 +361,33 @@ location = "fsn1" # datacenter: fsn1, nbg1, hel1, ash, hil
|
|
|
319
361
|
idle_timeout = 900 # seconds before idle shutdown (default: 900 = 15min)
|
|
320
362
|
```
|
|
321
363
|
|
|
322
|
-
The `
|
|
364
|
+
The `HCLOUD_TOKEN` environment variable is always required — the token is never stored on disk.
|
|
323
365
|
|
|
324
366
|
## Bubble-in-Bubble
|
|
325
367
|
|
|
326
|
-
You can run `bubble` from inside a container to open another bubble on the host. This is useful when reviewing a related PR while working on a feature branch.
|
|
368
|
+
You can run `bubble` from inside a container to open another bubble on the host. This is enabled by default and is useful when reviewing a related PR while working on a feature branch.
|
|
327
369
|
|
|
328
370
|
```bash
|
|
329
|
-
# On the host: enable the relay (one-time setup)
|
|
330
|
-
bubble relay enable
|
|
331
|
-
|
|
332
371
|
# Inside a container: open another bubble
|
|
333
372
|
bubble leanprover/lean4/pull/456
|
|
334
373
|
bubble mathlib4
|
|
335
374
|
```
|
|
336
375
|
|
|
337
|
-
The relay only allows opening repos already cloned in `~/.bubble/git/` — it cannot trigger cloning of new repos. Local paths are rejected.
|
|
376
|
+
The relay only allows opening repos already cloned in `~/.bubble/git/` — it cannot trigger cloning of new repos. Local paths are rejected. To disable the relay, run `bubble security set relay off`.
|
|
338
377
|
|
|
339
|
-
## Security
|
|
378
|
+
## Other Security Limitations
|
|
340
379
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
380
|
+
These are inherent consequences of the architecture, not bugs. Understanding them helps you make informed trust decisions.
|
|
381
|
+
|
|
382
|
+
1. **DNS exfiltration**: The network allowlist restricts IP connectivity, but DNS queries still reach the internet via the container resolver. Data can be encoded in DNS queries to exfiltrate information. This is inherent to any iptables-based approach that allows DNS.
|
|
383
|
+
|
|
384
|
+
2. **/24 CIDR over-allowance**: Domain allowlisting resolves to /24 CIDR blocks (256 IPs) to handle CDN IP rotation. Other services sharing the same /24 block are also reachable.
|
|
385
|
+
|
|
386
|
+
3. **iptables defense depth**: Network rules are enforced by iptables inside the container. The `user` account cannot modify them (no sudo), but a kernel exploit or other root escalation within the container could flush the rules. There is no external enforcement layer (e.g., Incus ACLs).
|
|
387
|
+
|
|
388
|
+
4. **Boot-time network window**: There is a brief window between container launch and iptables rule application during which the container has unrestricted network access. No user code runs during this window with stock images.
|
|
389
|
+
|
|
390
|
+
5. **Auth proxy token visibility**: The per-container auth proxy token (an internal bubble token, not a GitHub token) is stored in the user's git config and in `/etc/profile.d/bubble-gh.sh` (mode 644). Any process in the container can read it and use it to make requests through the auth proxy. The proxy enforces repo-scoping for git and REST API requests, but GraphQL queries (level 3+) are not repo-scoped and can read any data the host's GitHub token can access.
|
|
349
391
|
|
|
350
392
|
## License
|
|
351
393
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# bubble
|
|
2
2
|
|
|
3
|
-
Containerized development environments
|
|
3
|
+
Containerized development environments,
|
|
4
|
+
with opinionated presets for users of the [Lean](https://lean-lang.org/) programming language,
|
|
5
|
+
powered by [Incus](https://linuxcontainers.org/incus/).
|
|
6
|
+
We assume that you work using VSCode, emacs, or neovim, and that you collaborate via GitHub.
|
|
4
7
|
|
|
5
8
|
## Quick Start
|
|
6
9
|
|
|
@@ -17,6 +20,29 @@ bubble list
|
|
|
17
20
|
|
|
18
21
|
See [Examples](#examples) for branches, local repos, issues, remote, and non-interactive use.
|
|
19
22
|
|
|
23
|
+
## Use cases
|
|
24
|
+
|
|
25
|
+
You might be worried about:
|
|
26
|
+
* Reviewing and running Lean code from strangers.
|
|
27
|
+
* Running an AI agent like Claude in "yolo" mode.
|
|
28
|
+
* Managing work and AI threads across many repositories and branches.
|
|
29
|
+
|
|
30
|
+
The `bubble` tool attempts to provide a fast, low-friction, secure solution to these problems.
|
|
31
|
+
It launches containers pre-built for working with Lean (and your favorite AI tools),
|
|
32
|
+
and handles repository management and caches behind the scenes.
|
|
33
|
+
|
|
34
|
+
By working in containers, you can prevent AI tools from accidentally damaging your system
|
|
35
|
+
(or other repositories you have access to)
|
|
36
|
+
and handle potentially adversarial code from other humans or agents.
|
|
37
|
+
Opening Lean files from untrusted sources
|
|
38
|
+
(e.g. reviewing code, or supply chain attacks on your upstream dependencies)
|
|
39
|
+
can execute arbitrary code, so if you regularly do this you should be using containers.
|
|
40
|
+
|
|
41
|
+
`bubble` also allows you to limit how your GitHub access can be used:
|
|
42
|
+
containers don't have direct access to GitHub, and never see your authorization token,
|
|
43
|
+
but interact through a restricted proxy.
|
|
44
|
+
You can choose via the `bubble security` tool the capability/security trade-off you prefer.
|
|
45
|
+
|
|
20
46
|
## Examples
|
|
21
47
|
|
|
22
48
|
```bash
|
|
@@ -87,7 +113,7 @@ Each "bubble" is a lightweight Linux container (via Incus) with:
|
|
|
87
113
|
|
|
88
114
|
**Language hooks**: bubble automatically detects the project's language and selects the right image. For Lean 4 projects (detected via `lean-toolchain`), the container includes elan, pre-installed VS Code extensions, and auto-downloads the mathlib cache when needed.
|
|
89
115
|
|
|
90
|
-
**Network allowlisting**: Containers can only reach allowed domains (
|
|
116
|
+
**Network allowlisting**: Containers can only reach allowed domains (language-specific domains like `releases.lean-lang.org` for Lean, plus any configured in `~/.bubble/config.toml`). Direct GitHub access is blocked by iptables — all GitHub traffic is forced through the auth proxy, which enforces repo-scoping and rate limits. IPv6 is blocked, DNS is restricted to the container resolver, and outbound SSH is blocked.
|
|
91
117
|
|
|
92
118
|
## Requirements
|
|
93
119
|
|
|
@@ -114,21 +140,37 @@ Each "bubble" is a lightweight Linux container (via Incus) with:
|
|
|
114
140
|
| `bubble cloud default on\|off` | Set cloud as the default for all bubbles |
|
|
115
141
|
| `bubble cloud ssh` | SSH directly to the cloud server |
|
|
116
142
|
| `bubble tools list\|set\|status` | Manage tools installed in container images |
|
|
143
|
+
| `bubble security [set\|permissive\|lockdown\|default]` | Review and manage security posture |
|
|
117
144
|
| `bubble doctor` | Diagnose and fix common issues |
|
|
118
145
|
|
|
146
|
+
## Security
|
|
147
|
+
|
|
148
|
+
- **No sudo**: The `user` account has no sudo access and a locked password
|
|
149
|
+
- **Network allowlisting**: iptables rules restrict outbound connections to allowed domains only
|
|
150
|
+
- **IPv6 blocked**: All IPv6 traffic is dropped
|
|
151
|
+
- **DNS restricted**: DNS queries only go to the container's configured resolver
|
|
152
|
+
- **No outbound SSH**: Containers cannot SSH out (VSCode uses `incus exec` ProxyCommand)
|
|
153
|
+
- **SSH key-only auth**: Password authentication is disabled
|
|
154
|
+
- **Shell injection hardening**: All user-supplied values are quoted with `shlex.quote()`
|
|
155
|
+
- **Per-repo git mount**: Each container only sees its own bare repo, not the entire git store
|
|
156
|
+
- **Bubble-in-bubble relay**: Containers can open new bubbles on the host, but only for repos already cloned in `~/.bubble/git/`. Disable with `bubble security set relay off`
|
|
157
|
+
- **GitHub auth proxy**: Host token never enters containers. Git and REST API are repo-scoped. **GraphQL queries are read-only but account-wide** — can read any data the host token can access. API access can be set to `off`, `on` (read-only, the default), or `read-write` (enables mutations) via `bubble security set github-api`
|
|
158
|
+
- **Shared mathlib cache**: The mathlib cache at `~/.bubble/mathlib-cache/` is shared across Lean containers. Modes: `on` (default) = read-write (a compromised container could poison cached artifacts), `off` = read-only (prevents poisoning), `overlay` = read-only with per-container writable overlay. This cache is *not* shared with `lake exe cache` run outside a bubble. Configure with `bubble security set shared-cache`. If you suspect poisoning, delete `~/.bubble/mathlib-cache/`.
|
|
159
|
+
|
|
119
160
|
## Images
|
|
120
161
|
|
|
121
|
-
Images are built automatically on first use.
|
|
162
|
+
Images are built automatically on first use. Any enabled [tools](#tools) are installed in the `base` image and inherited by all derived images.
|
|
122
163
|
|
|
123
164
|
| Image | Contents |
|
|
124
165
|
|-------|----------|
|
|
125
|
-
| `base` | Ubuntu 24.04, git, openssh-server, build-essential,
|
|
126
|
-
| `lean` | base + elan
|
|
166
|
+
| `base` | Ubuntu 24.04, git, openssh-server, build-essential, plus configured tools |
|
|
167
|
+
| `lean` | base + leantar (+ elan fallback if not installed as a tool) |
|
|
168
|
+
| `python` | base + uv, ruff |
|
|
127
169
|
| `lean-v4.X.Y` | lean + specific toolchain pre-installed (built lazily on demand) |
|
|
128
170
|
|
|
129
|
-
`base` and `
|
|
171
|
+
`base`, `lean`, and `python` are static images you can rebuild with `bubble images build <name>`. Versioned `lean-v4.X.Y` images are built automatically in the background when a project uses a stable/RC toolchain not yet cached — the current bubble proceeds immediately with elan downloading the toolchain on demand, and the next bubble for that version starts instantly.
|
|
130
172
|
|
|
131
|
-
For mathlib or mathlib-dependent projects, a VS Code terminal automatically runs `lake exe cache get` when the workspace opens.
|
|
173
|
+
When VS Code is the configured editor and elan is installed, the base image also includes pre-baked VS Code Lean 4 extensions and an auto-cache extension. For mathlib or mathlib-dependent projects, a VS Code terminal automatically runs `lake exe cache get` when the workspace opens.
|
|
132
174
|
|
|
133
175
|
## Configuration
|
|
134
176
|
|
|
@@ -169,7 +211,7 @@ Tools like Claude Code and OpenAI Codex can be installed in container images. Ea
|
|
|
169
211
|
|
|
170
212
|
```bash
|
|
171
213
|
bubble tools list # show all tools and their settings
|
|
172
|
-
bubble tools set claude yes
|
|
214
|
+
bubble tools set claude yes # always install
|
|
173
215
|
bubble tools set codex no # never install
|
|
174
216
|
bubble tools status # show what would actually be installed
|
|
175
217
|
```
|
|
@@ -205,7 +247,7 @@ Run bubbles on auto-provisioned Hetzner Cloud servers. The server shuts down aut
|
|
|
205
247
|
2. Go to your project → Security → API Tokens → Generate API Token (read/write)
|
|
206
248
|
3. Set the token in your environment:
|
|
207
249
|
```bash
|
|
208
|
-
export
|
|
250
|
+
export HCLOUD_TOKEN="your-token-here"
|
|
209
251
|
```
|
|
210
252
|
4. Install the cloud dependency:
|
|
211
253
|
```bash
|
|
@@ -285,33 +327,33 @@ location = "fsn1" # datacenter: fsn1, nbg1, hel1, ash, hil
|
|
|
285
327
|
idle_timeout = 900 # seconds before idle shutdown (default: 900 = 15min)
|
|
286
328
|
```
|
|
287
329
|
|
|
288
|
-
The `
|
|
330
|
+
The `HCLOUD_TOKEN` environment variable is always required — the token is never stored on disk.
|
|
289
331
|
|
|
290
332
|
## Bubble-in-Bubble
|
|
291
333
|
|
|
292
|
-
You can run `bubble` from inside a container to open another bubble on the host. This is useful when reviewing a related PR while working on a feature branch.
|
|
334
|
+
You can run `bubble` from inside a container to open another bubble on the host. This is enabled by default and is useful when reviewing a related PR while working on a feature branch.
|
|
293
335
|
|
|
294
336
|
```bash
|
|
295
|
-
# On the host: enable the relay (one-time setup)
|
|
296
|
-
bubble relay enable
|
|
297
|
-
|
|
298
337
|
# Inside a container: open another bubble
|
|
299
338
|
bubble leanprover/lean4/pull/456
|
|
300
339
|
bubble mathlib4
|
|
301
340
|
```
|
|
302
341
|
|
|
303
|
-
The relay only allows opening repos already cloned in `~/.bubble/git/` — it cannot trigger cloning of new repos. Local paths are rejected.
|
|
342
|
+
The relay only allows opening repos already cloned in `~/.bubble/git/` — it cannot trigger cloning of new repos. Local paths are rejected. To disable the relay, run `bubble security set relay off`.
|
|
304
343
|
|
|
305
|
-
## Security
|
|
344
|
+
## Other Security Limitations
|
|
306
345
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
346
|
+
These are inherent consequences of the architecture, not bugs. Understanding them helps you make informed trust decisions.
|
|
347
|
+
|
|
348
|
+
1. **DNS exfiltration**: The network allowlist restricts IP connectivity, but DNS queries still reach the internet via the container resolver. Data can be encoded in DNS queries to exfiltrate information. This is inherent to any iptables-based approach that allows DNS.
|
|
349
|
+
|
|
350
|
+
2. **/24 CIDR over-allowance**: Domain allowlisting resolves to /24 CIDR blocks (256 IPs) to handle CDN IP rotation. Other services sharing the same /24 block are also reachable.
|
|
351
|
+
|
|
352
|
+
3. **iptables defense depth**: Network rules are enforced by iptables inside the container. The `user` account cannot modify them (no sudo), but a kernel exploit or other root escalation within the container could flush the rules. There is no external enforcement layer (e.g., Incus ACLs).
|
|
353
|
+
|
|
354
|
+
4. **Boot-time network window**: There is a brief window between container launch and iptables rule application during which the container has unrestricted network access. No user code runs during this window with stock images.
|
|
355
|
+
|
|
356
|
+
5. **Auth proxy token visibility**: The per-container auth proxy token (an internal bubble token, not a GitHub token) is stored in the user's git config and in `/etc/profile.d/bubble-gh.sh` (mode 644). Any process in the container can read it and use it to make requests through the auth proxy. The proxy enforces repo-scoping for git and REST API requests, but GraphQL queries (level 3+) are not repo-scoped and can read any data the host's GitHub token can access.
|
|
315
357
|
|
|
316
358
|
## License
|
|
317
359
|
|
|
@@ -57,8 +57,8 @@ equivalent to `bubble open <url>`.
|
|
|
57
57
|
| `--base` | string | | Base branch for `-b` |
|
|
58
58
|
| `--mount` | string (repeatable) | | Mount host dir into container |
|
|
59
59
|
| `--claude-config/--no-claude-config` | flag | enabled | Mount ~/.claude config read-only |
|
|
60
|
-
| `--claude-credentials/--no-claude-credentials` | flag |
|
|
61
|
-
| `--codex-credentials/--no-codex-credentials` | flag |
|
|
60
|
+
| `--claude-credentials/--no-claude-credentials` | flag | enabled | Mount Claude credentials |
|
|
61
|
+
| `--codex-credentials/--no-codex-credentials` | flag | enabled | Mount Codex credentials |
|
|
62
62
|
| `--ssh HOST` | string | | Run on remote host |
|
|
63
63
|
| `--cloud` | flag | | Run on Hetzner Cloud server |
|
|
64
64
|
| `--local` | flag | | Force local execution |
|
|
@@ -335,8 +335,16 @@ of the versioned image for next time.
|
|
|
335
335
|
- `~/.bubble/mathlib-cache/` ↔ `/shared/mathlib-cache/` (read-write)
|
|
336
336
|
- Environment variable `MATHLIB_CACHE_DIR=/shared/mathlib-cache`
|
|
337
337
|
|
|
338
|
+
> **Security note:** The shared cache is mounted read-write by default, so a
|
|
339
|
+
> compromised container could write poisoned `.olean` files that subsequent
|
|
340
|
+
> containers would pick up via `lake exe cache get`. The cache is *not* shared
|
|
341
|
+
> with `lake exe cache` run on the host — only bubble containers are affected.
|
|
342
|
+
> Set `shared-cache` to `off` (via `bubble security set shared-cache off`) to
|
|
343
|
+
> mount the cache read-only and prevent future poisoning. If you suspect the
|
|
344
|
+
> cache has already been compromised, delete `~/.bubble/mathlib-cache/`.
|
|
345
|
+
|
|
338
346
|
**Post-clone behavior:**
|
|
339
|
-
- Pre-populate Lake dependencies from `lake-manifest.json` (see 2.
|
|
347
|
+
- Pre-populate Lake dependencies from `lake-manifest.json` (see 2.4)
|
|
340
348
|
- Write auto-build command to `~/.bubble-fetch-cache` marker file
|
|
341
349
|
- For Mathlib-using projects: `cd <dir> && lake exe cache get && lake build`
|
|
342
350
|
- For the lean4 repo itself: cmake + make build
|
|
@@ -769,7 +777,7 @@ entry has a `remote_host` field. The command is forwarded via
|
|
|
769
777
|
|
|
770
778
|
### 8.1 Requirements
|
|
771
779
|
|
|
772
|
-
- `
|
|
780
|
+
- `HCLOUD_TOKEN` environment variable (never stored)
|
|
773
781
|
- `hcloud` Python package (optional dependency)
|
|
774
782
|
|
|
775
783
|
### 8.2 Commands
|
|
@@ -908,9 +916,8 @@ When `--claude-config` is enabled (default), mount specific items from
|
|
|
908
916
|
`~/.claude/` into `/home/user/.claude/` read-only:
|
|
909
917
|
- `CLAUDE.md`, `settings.json`, `skills/`, `keybindings.json`, `commands/`
|
|
910
918
|
|
|
911
|
-
Credential files (`.credentials.json`) are
|
|
912
|
-
`--claude-credentials`
|
|
913
|
-
is true.
|
|
919
|
+
Credential files (`.credentials.json`) are mounted by default. Disable with
|
|
920
|
+
`--no-claude-credentials` or set `claude.credentials = false` in config.
|
|
914
921
|
|
|
915
922
|
**Symlink safety:** Reject symlinks that escape `~/.claude/` to prevent
|
|
916
923
|
exposing arbitrary host files.
|
|
@@ -918,7 +925,7 @@ exposing arbitrary host files.
|
|
|
918
925
|
### 10.2 Codex config mounting
|
|
919
926
|
|
|
920
927
|
Similar to Claude. Config: `config.toml` (read-only). Credentials: `auth.json`
|
|
921
|
-
(
|
|
928
|
+
(mounted by default; disable via `--no-codex-credentials` or config).
|
|
922
929
|
|
|
923
930
|
### 10.3 Editor config mounting
|
|
924
931
|
|
|
@@ -933,8 +940,10 @@ Similar to Claude. Config: `config.toml` (read-only). Credentials: `auth.json`
|
|
|
933
940
|
|
|
934
941
|
### 10.4 GitHub auth proxy
|
|
935
942
|
|
|
936
|
-
An HTTP reverse proxy on the host provides
|
|
937
|
-
|
|
943
|
+
An HTTP reverse proxy on the host provides GitHub authentication without
|
|
944
|
+
exposing the host's token. Git and REST API requests are repo-scoped;
|
|
945
|
+
GraphQL requests are operation-validated (queries vs mutations) but not
|
|
946
|
+
repo-scoped — see access levels below.
|
|
938
947
|
|
|
939
948
|
**Port:** 7654 (default, configurable).
|
|
940
949
|
|
|
@@ -942,7 +951,7 @@ The host's GitHub token never enters the container.
|
|
|
942
951
|
1. Container git is configured with `url.insteadOf` to route HTTPS through the proxy
|
|
943
952
|
2. Container sends request with `X-Bubble-Token` header
|
|
944
953
|
3. Proxy validates token against `~/.bubble/auth-tokens.json` (mode 0600)
|
|
945
|
-
4.
|
|
954
|
+
4. For git/REST: proxy checks path matches the allowed `owner/repo`. For GraphQL: proxy validates operation type (queries allowed at level 3, mutations require level 4) but does not scope to a specific repo
|
|
946
955
|
5. Proxy adds `Authorization: token <real-token>` header
|
|
947
956
|
6. Proxy forwards to `https://github.com`
|
|
948
957
|
7. Response returned to container
|
|
@@ -960,6 +969,19 @@ The host's GitHub token never enters the container.
|
|
|
960
969
|
- `POST /git/{owner}/{repo}[.git]/git-upload-pack`
|
|
961
970
|
- `POST /git/{owner}/{repo}[.git]/git-receive-pack`
|
|
962
971
|
|
|
972
|
+
**Access levels (per-container):**
|
|
973
|
+
| Level | Description | Scope |
|
|
974
|
+
|-------|-------------|-------|
|
|
975
|
+
| 1 | Git smart HTTP only (push/pull) | Repo-scoped |
|
|
976
|
+
| 2 | Git + REST API read-only | Repo-scoped (REST paths validated against `/repos/{owner}/{repo}/...`) |
|
|
977
|
+
| 3 (default) | Git + gh read-only (REST read + GraphQL queries) | Git and REST are repo-scoped; **GraphQL is account-wide** — queries can read any data the host token can access |
|
|
978
|
+
| 4 | Git + gh read-write (REST + GraphQL + mutations) | Git and REST are repo-scoped; **GraphQL queries and mutations are account-wide** |
|
|
979
|
+
|
|
980
|
+
> **Note:** GitHub's GraphQL API does not support path-based scoping.
|
|
981
|
+
> At the default level 3, a container can query any repository, org membership,
|
|
982
|
+
> or user data readable by the host token. To restrict containers to git-only
|
|
983
|
+
> access, use `bubble security set github-api off`.
|
|
984
|
+
|
|
963
985
|
**Security:**
|
|
964
986
|
- Path canonicalization: reject encoded separators, dot-segments, duplicate slashes
|
|
965
987
|
- No redirect following (returns redirects as-is to prevent token leakage)
|
|
@@ -1046,10 +1068,10 @@ server_name = "bubble-cloud"
|
|
|
1046
1068
|
default = false
|
|
1047
1069
|
|
|
1048
1070
|
[claude]
|
|
1049
|
-
credentials =
|
|
1071
|
+
credentials = true
|
|
1050
1072
|
|
|
1051
1073
|
[codex]
|
|
1052
|
-
credentials =
|
|
1074
|
+
credentials = true
|
|
1053
1075
|
|
|
1054
1076
|
[security]
|
|
1055
1077
|
# All settings default to "auto"
|
|
@@ -1069,8 +1091,6 @@ get/set interface:
|
|
|
1069
1091
|
- `bubble codex credentials on|off` — toggle Codex credential mounting
|
|
1070
1092
|
- `bubble security set NAME on|off|auto` — configure security settings
|
|
1071
1093
|
- `bubble config set KEY VALUE` — set security settings (alias)
|
|
1072
|
-
- `bubble config lockdown` — disable all off-by-default security features
|
|
1073
|
-
- `bubble config accept-risks` — enable all on-by-default risk features
|
|
1074
1094
|
- `bubble config symlink-claude-projects` — symlink Claude projects directory
|
|
1075
1095
|
|
|
1076
1096
|
---
|
|
@@ -1084,13 +1104,14 @@ values: `auto`, `on`, `off`.
|
|
|
1084
1104
|
|
|
1085
1105
|
| Setting | Auto default | Description |
|
|
1086
1106
|
|---------|-------------|-------------|
|
|
1087
|
-
| `
|
|
1088
|
-
| `shared-cache` | on | Writable shared mounts (mathlib cache) |
|
|
1107
|
+
| `shared-cache` | on | Writable shared mounts (mathlib cache) — see [Lean 4 hook](#22-lean-4-hook) security note |
|
|
1089
1108
|
| `user-mounts` | on | `--mount` flag support |
|
|
1090
1109
|
| `git-manifest-trust` | on | Auto-clone Lake manifest dependencies |
|
|
1091
|
-
| `claude-credentials` |
|
|
1092
|
-
| `codex-credentials` |
|
|
1093
|
-
| `github-auth` | on | Repo-scoped GitHub auth via proxy |
|
|
1110
|
+
| `claude-credentials` | on | Mount Claude credentials into containers |
|
|
1111
|
+
| `codex-credentials` | on | Mount Codex credentials into containers |
|
|
1112
|
+
| `github-auth` | on | Repo-scoped GitHub auth via proxy (git push/pull) |
|
|
1113
|
+
| `github-api` | on | GitHub API access via auth proxy: REST is repo-scoped; **GraphQL queries are read-only but account-wide** (can read any repo the host token can access). Set to `off` for git-only, or `read-write` for mutations |
|
|
1114
|
+
| `github-token-inject` | off | Direct GitHub token injection (bypasses proxy) |
|
|
1094
1115
|
| `relay` | on | Bubble-in-bubble relay |
|
|
1095
1116
|
| `host-key-trust` | on | Disable SSH StrictHostKeyChecking |
|
|
1096
1117
|
|
|
@@ -1104,8 +1125,10 @@ user to `bubble security`. Suppressed by `BUBBLE_QUIET_SECURITY=1`.
|
|
|
1104
1125
|
- `bubble security lockdown` — set all to `off`
|
|
1105
1126
|
- `bubble security default` — reset all to `auto`
|
|
1106
1127
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1128
|
+
**GitHub network access:** Direct GitHub network access (via iptables) is only
|
|
1129
|
+
allowed when `github-token-inject` is enabled (level 5). At all other auth
|
|
1130
|
+
levels (0–4), iptables blocks direct GitHub traffic and forces it through the
|
|
1131
|
+
auth proxy on loopback, which enforces repo-scoping and rate limits.
|
|
1109
1132
|
|
|
1110
1133
|
---
|
|
1111
1134
|
|