dev-bubble 0.6.20__tar.gz → 0.7.2__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 (132) hide show
  1. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/.claude/CLAUDE.md +17 -10
  2. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/CHANGELOG.md +42 -0
  3. {dev_bubble-0.6.20/dev_bubble.egg-info → dev_bubble-0.7.2}/PKG-INFO +17 -1
  4. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/README.md +16 -0
  5. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/SPEC.md +40 -18
  6. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/__init__.py +1 -1
  7. dev_bubble-0.7.2/bubble/auth_proxy.py +1028 -0
  8. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/claude.py +6 -4
  9. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/cli.py +82 -31
  10. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/clone.py +8 -9
  11. dev_bubble-0.7.2/bubble/commands/completion.py +119 -0
  12. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/images.py +4 -3
  13. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/list_cmd.py +89 -12
  14. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/relay_cmd.py +2 -4
  15. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/security_cmd.py +14 -8
  16. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/settings.py +155 -65
  17. dev_bubble-0.7.2/bubble/commands/status_cmd.py +202 -0
  18. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/config.py +0 -11
  19. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/container_helpers.py +9 -8
  20. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/finalization.py +34 -7
  21. dev_bubble-0.7.2/bubble/github_token.py +582 -0
  22. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/hooks/lean.py +2 -4
  23. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/image_management.py +12 -13
  24. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/builder.py +17 -8
  25. dev_bubble-0.7.2/bubble/images/scripts/tools/gh.sh +32 -0
  26. dev_bubble-0.7.2/bubble/lean.py +7 -0
  27. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/lifecycle.py +31 -0
  28. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/native.py +11 -10
  29. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/network.py +0 -81
  30. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/notices.py +0 -5
  31. dev_bubble-0.7.2/bubble/output.py +18 -0
  32. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/provisioning.py +68 -15
  33. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/runtime/colima.py +21 -0
  34. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/security.py +89 -44
  35. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/setup.py +0 -21
  36. dev_bubble-0.7.2/bubble/spinner.py +46 -0
  37. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/target.py +36 -48
  38. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/tools.py +7 -10
  39. {dev_bubble-0.6.20 → dev_bubble-0.7.2/dev_bubble.egg-info}/PKG-INFO +17 -1
  40. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/dev_bubble.egg-info/SOURCES.txt +10 -0
  41. dev_bubble-0.7.2/tests/test_auth_proxy.py +1242 -0
  42. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_branch_no_target.py +11 -0
  43. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_build_lock.py +37 -8
  44. dev_bubble-0.7.2/tests/test_completion.py +122 -0
  45. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_config.py +127 -0
  46. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_customize.py +7 -7
  47. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_github_token.py +159 -4
  48. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_integration.py +4 -19
  49. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_lifecycle.py +35 -0
  50. dev_bubble-0.7.2/tests/test_list_columns.py +83 -0
  51. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_mounts.py +151 -24
  52. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_notices.py +0 -6
  53. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_security.py +288 -113
  54. dev_bubble-0.7.2/tests/test_spinner.py +65 -0
  55. dev_bubble-0.7.2/tests/test_status.py +243 -0
  56. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_tools.py +8 -32
  57. dev_bubble-0.6.20/bubble/auth_proxy.py +0 -543
  58. dev_bubble-0.6.20/bubble/github_token.py +0 -318
  59. dev_bubble-0.6.20/tests/test_auth_proxy.py +0 -498
  60. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/.github/workflows/ci.yml +0 -0
  61. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/.github/workflows/publish.yml +0 -0
  62. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/.gitignore +0 -0
  63. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/LICENSE +0 -0
  64. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/__main__.py +0 -0
  65. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/automation.py +0 -0
  66. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/clean.py +0 -0
  67. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/cloud.py +0 -0
  68. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/cloud_types.py +0 -0
  69. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/__init__.py +0 -0
  70. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/cloud_cmd.py +0 -0
  71. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/doctor.py +0 -0
  72. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/infrastructure.py +0 -0
  73. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/lifecycle.py +0 -0
  74. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/commands/remote_cmd.py +0 -0
  75. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/data/skill.md +0 -0
  76. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/default_repos.json +0 -0
  77. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/git_store.py +0 -0
  78. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/hooks/__init__.py +0 -0
  79. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/hooks/python.py +0 -0
  80. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/__init__.py +0 -0
  81. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/base.sh +0 -0
  82. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/cloud-init.sh +0 -0
  83. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/lean-toolchain.sh +0 -0
  84. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/lean.sh +0 -0
  85. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/python.sh +0 -0
  86. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/tools/claude.sh +0 -0
  87. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/tools/codex.sh +0 -0
  88. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/tools/elan.sh +0 -0
  89. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/tools/emacs.sh +0 -0
  90. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/tools/neovim.sh +0 -0
  91. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/tools/pins.json +0 -0
  92. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/images/scripts/tools/vscode.sh +0 -0
  93. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/naming.py +0 -0
  94. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/relay.py +0 -0
  95. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/remote.py +0 -0
  96. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/repo_registry.py +0 -0
  97. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/runtime/__init__.py +0 -0
  98. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/runtime/base.py +0 -0
  99. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/runtime/incus.py +0 -0
  100. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/skill.py +0 -0
  101. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/token_store.py +0 -0
  102. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/tunnel.py +0 -0
  103. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/bubble/vscode.py +0 -0
  104. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/config/com.bubble.git-update.plist +0 -0
  105. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/config/com.bubble.image-refresh.plist +0 -0
  106. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/config/com.bubble.relay-daemon.plist +0 -0
  107. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/conftest.py +0 -0
  108. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/dev_bubble.egg-info/dependency_links.txt +0 -0
  109. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/dev_bubble.egg-info/entry_points.txt +0 -0
  110. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/dev_bubble.egg-info/requires.txt +0 -0
  111. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/dev_bubble.egg-info/top_level.txt +0 -0
  112. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/pyproject.toml +0 -0
  113. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/setup.cfg +0 -0
  114. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/conftest.py +0 -0
  115. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_claude.py +0 -0
  116. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_claude_projects_symlink.py +0 -0
  117. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_cloud.py +0 -0
  118. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_editor.py +0 -0
  119. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_git_store.py +0 -0
  120. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_hooks.py +0 -0
  121. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_list_remote.py +0 -0
  122. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_multi_target.py +0 -0
  123. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_naming.py +0 -0
  124. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_native.py +0 -0
  125. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_network.py +0 -0
  126. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_relay.py +0 -0
  127. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_remote.py +0 -0
  128. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_repo_registry.py +0 -0
  129. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_skill.py +0 -0
  130. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_target.py +0 -0
  131. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/tests/test_tunnel.py +0 -0
  132. {dev_bubble-0.6.20 → dev_bubble-0.7.2}/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.
@@ -120,15 +120,26 @@ Bubbles can run on a remote machine instead of locally. The `--ssh HOST` flag (o
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
- **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.
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
- **Local bubbles:** Exposed into containers via Incus proxy device (`incus config device add ... proxy connect=tcp:host:7654 listen=tcp:127.0.0.1:7654`).
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
- **Remote/cloud bubbles:** An SSH reverse tunnel (`ssh -R 7654:127.0.0.1:7654 remote`) forwards the local proxy port to the remote host. An Incus proxy device on the remote then exposes it into the container. Tunnels are per-remote-host (shared across containers) with PID files in `~/.bubble/tunnels/`. Code is in `tunnel.py`.
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
- **Token management:** Per-container tokens in `~/.bubble/auth-tokens.json` map to `{container, owner, repo}`. Tokens are cleaned up on `bubble pop`. The daemon is managed via launchd/systemd.
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dev-bubble
3
- Version: 0.6.20
3
+ Version: 0.7.2
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
@@ -346,6 +346,22 @@ The relay only allows opening repos already cloned in `~/.bubble/git/` — it ca
346
346
  - **SSH key-only auth**: Password authentication is disabled
347
347
  - **Shell injection hardening**: All user-supplied values are quoted with `shlex.quote()`
348
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/`.
351
+
352
+ ### Known Limitations
353
+
354
+ These are inherent consequences of the architecture, not bugs. Understanding them helps you make informed trust decisions.
355
+
356
+ 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.
357
+
358
+ 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.
359
+
360
+ 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).
361
+
362
+ 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
+
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.
349
365
 
350
366
  ## License
351
367
 
@@ -312,6 +312,22 @@ The relay only allows opening repos already cloned in `~/.bubble/git/` — it ca
312
312
  - **SSH key-only auth**: Password authentication is disabled
313
313
  - **Shell injection hardening**: All user-supplied values are quoted with `shlex.quote()`
314
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/`.
317
+
318
+ ### Known Limitations
319
+
320
+ These are inherent consequences of the architecture, not bugs. Understanding them helps you make informed trust decisions.
321
+
322
+ 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.
323
+
324
+ 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.
325
+
326
+ 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).
327
+
328
+ 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
+
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.
315
331
 
316
332
  ## License
317
333
 
@@ -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 | disabled | Mount Claude credentials |
61
- | `--codex-credentials/--no-codex-credentials` | flag | disabled | Mount Codex credentials |
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.3)
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
@@ -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 only mounted when
912
- `--claude-credentials` is explicitly enabled or the config `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
- (opt-in via `--codex-credentials` or config).
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 repo-scoped GitHub authentication.
937
- The host's GitHub token never enters the container.
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. Proxy checks path matches the allowed `owner/repo`
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 = false
1071
+ credentials = true
1050
1072
 
1051
1073
  [codex]
1052
- credentials = false
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
  ---
@@ -1085,12 +1105,14 @@ values: `auto`, `on`, `off`.
1085
1105
  | Setting | Auto default | Description |
1086
1106
  |---------|-------------|-------------|
1087
1107
  | `network-github` | on | GitHub domains in network allowlist |
1088
- | `shared-cache` | on | Writable shared mounts (mathlib cache) |
1108
+ | `shared-cache` | on | Writable shared mounts (mathlib cache) — see [Lean 4 hook](#22-lean-4-hook) security note |
1089
1109
  | `user-mounts` | on | `--mount` flag support |
1090
1110
  | `git-manifest-trust` | on | Auto-clone Lake manifest dependencies |
1091
- | `claude-credentials` | off | Mount Claude credentials into containers |
1092
- | `codex-credentials` | off | Mount Codex credentials into containers |
1093
- | `github-auth` | on | Repo-scoped GitHub auth via proxy |
1111
+ | `claude-credentials` | on | Mount Claude credentials into containers |
1112
+ | `codex-credentials` | on | Mount Codex credentials into containers |
1113
+ | `github-auth` | on | Repo-scoped GitHub auth via proxy (git push/pull) |
1114
+ | `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 |
1115
+ | `github-token-inject` | off | Direct GitHub token injection (bypasses proxy) |
1094
1116
  | `relay` | on | Bubble-in-bubble relay |
1095
1117
  | `host-key-trust` | on | Disable SSH StrictHostKeyChecking |
1096
1118
 
@@ -1,3 +1,3 @@
1
1
  """bubble: Containerized development environments."""
2
2
 
3
- __version__ = "0.6.20"
3
+ __version__ = "0.7.2"