dev-bubble 0.7.2__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.
Files changed (129) hide show
  1. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/.claude/CLAUDE.md +1 -1
  2. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/CHANGELOG.md +2 -2
  3. {dev_bubble-0.7.2/dev_bubble.egg-info → dev_bubble-0.7.3}/PKG-INFO +57 -31
  4. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/README.md +56 -30
  5. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/SPEC.md +5 -4
  6. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/__init__.py +1 -1
  7. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/auth_proxy.py +160 -27
  8. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/automation.py +3 -0
  9. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/cli.py +28 -0
  10. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/cloud.py +2 -2
  11. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/container_helpers.py +5 -2
  12. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/finalization.py +7 -26
  13. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/github_token.py +32 -4
  14. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/image_management.py +2 -2
  15. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/builder.py +8 -3
  16. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/gh.sh +2 -0
  17. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/relay.py +7 -2
  18. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/runtime/colima.py +42 -0
  19. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/security.py +19 -19
  20. {dev_bubble-0.7.2 → dev_bubble-0.7.3/dev_bubble.egg-info}/PKG-INFO +57 -31
  21. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_auth_proxy.py +5 -4
  22. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_github_token.py +93 -0
  23. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_security.py +38 -3
  24. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/.github/workflows/ci.yml +0 -0
  25. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/.github/workflows/publish.yml +0 -0
  26. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/.gitignore +0 -0
  27. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/LICENSE +0 -0
  28. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/__main__.py +0 -0
  29. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/claude.py +0 -0
  30. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/clean.py +0 -0
  31. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/clone.py +0 -0
  32. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/cloud_types.py +0 -0
  33. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/__init__.py +0 -0
  34. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/cloud_cmd.py +0 -0
  35. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/completion.py +0 -0
  36. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/doctor.py +0 -0
  37. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/images.py +0 -0
  38. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/infrastructure.py +0 -0
  39. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/lifecycle.py +0 -0
  40. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/list_cmd.py +0 -0
  41. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/relay_cmd.py +0 -0
  42. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/remote_cmd.py +0 -0
  43. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/security_cmd.py +0 -0
  44. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/settings.py +0 -0
  45. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/commands/status_cmd.py +0 -0
  46. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/config.py +0 -0
  47. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/data/skill.md +0 -0
  48. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/default_repos.json +0 -0
  49. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/git_store.py +0 -0
  50. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/hooks/__init__.py +0 -0
  51. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/hooks/lean.py +0 -0
  52. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/hooks/python.py +0 -0
  53. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/__init__.py +0 -0
  54. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/base.sh +0 -0
  55. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/cloud-init.sh +0 -0
  56. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/lean-toolchain.sh +0 -0
  57. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/lean.sh +0 -0
  58. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/python.sh +0 -0
  59. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/claude.sh +0 -0
  60. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/codex.sh +0 -0
  61. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/elan.sh +0 -0
  62. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/emacs.sh +0 -0
  63. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/neovim.sh +0 -0
  64. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/pins.json +0 -0
  65. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/images/scripts/tools/vscode.sh +0 -0
  66. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/lean.py +0 -0
  67. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/lifecycle.py +0 -0
  68. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/naming.py +0 -0
  69. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/native.py +0 -0
  70. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/network.py +0 -0
  71. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/notices.py +0 -0
  72. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/output.py +0 -0
  73. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/provisioning.py +0 -0
  74. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/remote.py +0 -0
  75. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/repo_registry.py +0 -0
  76. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/runtime/__init__.py +0 -0
  77. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/runtime/base.py +0 -0
  78. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/runtime/incus.py +0 -0
  79. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/setup.py +0 -0
  80. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/skill.py +0 -0
  81. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/spinner.py +0 -0
  82. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/target.py +0 -0
  83. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/token_store.py +0 -0
  84. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/tools.py +0 -0
  85. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/tunnel.py +0 -0
  86. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/bubble/vscode.py +0 -0
  87. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/config/com.bubble.git-update.plist +0 -0
  88. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/config/com.bubble.image-refresh.plist +0 -0
  89. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/config/com.bubble.relay-daemon.plist +0 -0
  90. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/conftest.py +0 -0
  91. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/dev_bubble.egg-info/SOURCES.txt +0 -0
  92. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/dev_bubble.egg-info/dependency_links.txt +0 -0
  93. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/dev_bubble.egg-info/entry_points.txt +0 -0
  94. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/dev_bubble.egg-info/requires.txt +0 -0
  95. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/dev_bubble.egg-info/top_level.txt +0 -0
  96. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/pyproject.toml +0 -0
  97. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/setup.cfg +0 -0
  98. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/conftest.py +0 -0
  99. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_branch_no_target.py +0 -0
  100. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_build_lock.py +0 -0
  101. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_claude.py +0 -0
  102. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_claude_projects_symlink.py +0 -0
  103. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_cloud.py +0 -0
  104. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_completion.py +0 -0
  105. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_config.py +0 -0
  106. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_customize.py +0 -0
  107. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_editor.py +0 -0
  108. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_git_store.py +0 -0
  109. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_hooks.py +0 -0
  110. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_integration.py +0 -0
  111. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_lifecycle.py +0 -0
  112. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_list_columns.py +0 -0
  113. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_list_remote.py +0 -0
  114. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_mounts.py +0 -0
  115. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_multi_target.py +0 -0
  116. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_naming.py +0 -0
  117. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_native.py +0 -0
  118. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_network.py +0 -0
  119. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_notices.py +0 -0
  120. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_relay.py +0 -0
  121. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_remote.py +0 -0
  122. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_repo_registry.py +0 -0
  123. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_skill.py +0 -0
  124. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_spinner.py +0 -0
  125. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_status.py +0 -0
  126. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_target.py +0 -0
  127. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_tools.py +0 -0
  128. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_tunnel.py +0 -0
  129. {dev_bubble-0.7.2 → dev_bubble-0.7.3}/tests/test_vscode.py +0 -0
@@ -114,7 +114,7 @@ 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 `HETZNER_TOKEN` env var (never stored).
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()`).
@@ -278,8 +278,8 @@
278
278
  - `bubble config accept-risks` silences on-by-default warnings; `bubble config lockdown` disables off-by-default features
279
279
  - Security warnings printed to stderr for all `auto` settings during `bubble open`
280
280
  - `BUBBLE_QUIET_SECURITY=1` env var suppresses warnings for CI/automation
281
- - Settings: `shared_cache`, `user_mounts`, `network_github`, `relay`, `claude_credentials`, `host_key_trust`, `git_manifest_trust`
282
- - When `shared_cache=off`, shared mounts are read-only; when `network_github=off`, GitHub domains stripped from allowlist
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
283
283
  - When `user_mounts=off` or `claude_credentials=off`, corresponding CLI flags are rejected
284
284
  - Relay enable/disable migrated to `[security] relay` with backwards compatibility for `[relay] enabled`
285
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.7.2
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 for the Lean language, powered by [Incus](https://linuxcontainers.org/incus/).
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 (GitHub by default, plus language-specific domains like `releases.lean-lang.org` for Lean). IPv6 is blocked, DNS is restricted to the container resolver, and outbound SSH is blocked. Configurable in `~/.bubble/config.toml`.
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, pre-baked VS Code Server |
160
- | `lean` | base + elan, leantar, VS Code Lean 4 extension, auto-cache extension |
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 `lean` 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.
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 # always install
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 HETZNER_TOKEN="your-token-here"
284
+ export HCLOUD_TOKEN="your-token-here"
243
285
  ```
244
286
  4. Install the cloud dependency:
245
287
  ```bash
@@ -319,37 +361,21 @@ 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 `HETZNER_TOKEN` environment variable is always required — the token is never stored on disk.
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. Existing bubbles need to be recreated after enabling the relay to get the relay socket.
338
-
339
- ## Security
340
-
341
- - **No sudo**: The `user` account has no sudo access and a locked password
342
- - **Network allowlisting**: iptables rules restrict outbound connections to allowed domains only
343
- - **IPv6 blocked**: All IPv6 traffic is dropped
344
- - **DNS restricted**: DNS queries only go to the container's configured resolver
345
- - **No outbound SSH**: Containers cannot SSH out (VSCode uses `incus exec` ProxyCommand)
346
- - **SSH key-only auth**: Password authentication is disabled
347
- - **Shell injection hardening**: All user-supplied values are quoted with `shlex.quote()`
348
- - **Per-repo git mount**: Each container only sees its own bare repo, not the entire git store
349
- - **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. Disable API access with `bubble security set github-api off`
350
- - **Shared mathlib cache**: When `shared_cache` is enabled (the default), the mathlib cache at `~/.bubble/mathlib-cache/` is mounted **read-write** into every Mathlib-using Lean container. A compromised container could write poisoned build artifacts that would be picked up by subsequent containers running `lake exe cache get`. This cache is *not* shared with `lake exe cache` run outside a bubble — it only affects bubble containers. To prevent future poisoning, run `bubble security set shared-cache off`, which mounts the cache read-only. If you suspect the cache has already been compromised, delete `~/.bubble/mathlib-cache/`.
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`.
351
377
 
352
- ### Known Limitations
378
+ ## Other Security Limitations
353
379
 
354
380
  These are inherent consequences of the architecture, not bugs. Understanding them helps you make informed trust decisions.
355
381
 
@@ -361,7 +387,7 @@ These are inherent consequences of the architecture, not bugs. Understanding the
361
387
 
362
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.
363
389
 
364
- 5. **Auth proxy token visibility**: The per-container auth proxy 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. The token is scoped to one repository and access level for git and REST API requests, but GraphQL queries (level 3+) are not repo-scoped and can read any data the host token can access.
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.
365
391
 
366
392
  ## License
367
393
 
@@ -1,6 +1,9 @@
1
1
  # bubble
2
2
 
3
- Containerized development environments for the Lean language, powered by [Incus](https://linuxcontainers.org/incus/).
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 (GitHub by default, plus language-specific domains like `releases.lean-lang.org` for Lean). IPv6 is blocked, DNS is restricted to the container resolver, and outbound SSH is blocked. Configurable in `~/.bubble/config.toml`.
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, pre-baked VS Code Server |
126
- | `lean` | base + elan, leantar, VS Code Lean 4 extension, auto-cache extension |
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 `lean` 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.
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 # always install
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 HETZNER_TOKEN="your-token-here"
250
+ export HCLOUD_TOKEN="your-token-here"
209
251
  ```
210
252
  4. Install the cloud dependency:
211
253
  ```bash
@@ -285,37 +327,21 @@ 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 `HETZNER_TOKEN` environment variable is always required — the token is never stored on disk.
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. Existing bubbles need to be recreated after enabling the relay to get the relay socket.
304
-
305
- ## Security
306
-
307
- - **No sudo**: The `user` account has no sudo access and a locked password
308
- - **Network allowlisting**: iptables rules restrict outbound connections to allowed domains only
309
- - **IPv6 blocked**: All IPv6 traffic is dropped
310
- - **DNS restricted**: DNS queries only go to the container's configured resolver
311
- - **No outbound SSH**: Containers cannot SSH out (VSCode uses `incus exec` ProxyCommand)
312
- - **SSH key-only auth**: Password authentication is disabled
313
- - **Shell injection hardening**: All user-supplied values are quoted with `shlex.quote()`
314
- - **Per-repo git mount**: Each container only sees its own bare repo, not the entire git store
315
- - **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. Disable API access with `bubble security set github-api off`
316
- - **Shared mathlib cache**: When `shared_cache` is enabled (the default), the mathlib cache at `~/.bubble/mathlib-cache/` is mounted **read-write** into every Mathlib-using Lean container. A compromised container could write poisoned build artifacts that would be picked up by subsequent containers running `lake exe cache get`. This cache is *not* shared with `lake exe cache` run outside a bubble — it only affects bubble containers. To prevent future poisoning, run `bubble security set shared-cache off`, which mounts the cache read-only. If you suspect the cache has already been compromised, delete `~/.bubble/mathlib-cache/`.
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`.
317
343
 
318
- ### Known Limitations
344
+ ## Other Security Limitations
319
345
 
320
346
  These are inherent consequences of the architecture, not bugs. Understanding them helps you make informed trust decisions.
321
347
 
@@ -327,7 +353,7 @@ These are inherent consequences of the architecture, not bugs. Understanding the
327
353
 
328
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.
329
355
 
330
- 5. **Auth proxy token visibility**: The per-container auth proxy 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. The token is scoped to one repository and access level for git and REST API requests, but GraphQL queries (level 3+) are not repo-scoped and can read any data the host token can access.
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.
331
357
 
332
358
  ## License
333
359
 
@@ -777,7 +777,7 @@ entry has a `remote_host` field. The command is forwarded via
777
777
 
778
778
  ### 8.1 Requirements
779
779
 
780
- - `HETZNER_TOKEN` environment variable (never stored)
780
+ - `HCLOUD_TOKEN` environment variable (never stored)
781
781
  - `hcloud` Python package (optional dependency)
782
782
 
783
783
  ### 8.2 Commands
@@ -1104,7 +1104,6 @@ values: `auto`, `on`, `off`.
1104
1104
 
1105
1105
  | Setting | Auto default | Description |
1106
1106
  |---------|-------------|-------------|
1107
- | `network-github` | on | GitHub domains in network allowlist |
1108
1107
  | `shared-cache` | on | Writable shared mounts (mathlib cache) — see [Lean 4 hook](#22-lean-4-hook) security note |
1109
1108
  | `user-mounts` | on | `--mount` flag support |
1110
1109
  | `git-manifest-trust` | on | Auto-clone Lake manifest dependencies |
@@ -1126,8 +1125,10 @@ user to `bubble security`. Suppressed by `BUBBLE_QUIET_SECURITY=1`.
1126
1125
  - `bubble security lockdown` — set all to `off`
1127
1126
  - `bubble security default` — reset all to `auto`
1128
1127
 
1129
- When `network-github` is `off`, all GitHub-related domains are stripped from the
1130
- network allowlist.
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.
1131
1132
 
1132
1133
  ---
1133
1134
 
@@ -1,3 +1,3 @@
1
1
  """bubble: Containerized development environments."""
2
2
 
3
- __version__ = "0.7.2"
3
+ __version__ = "0.7.3"