boxdown 1.0.0 → 1.2.1
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.
- package/README.md +135 -12
- package/assets/devcontainer/README.md +65 -21
- package/assets/devcontainer/devcontainer.json +27 -15
- package/assets/devcontainer/hooks/initialize.sh +76 -22
- package/assets/devcontainer/hooks/post-create.sh +70 -12
- package/assets/devcontainer/hooks/post-start.sh +20 -13
- package/assets/devcontainer/ssh-config-install.sh +12 -3
- package/assets/devcontainer/start.sh +721 -44
- package/assets/devcontainer/utils/coding-agent-cli-update.sh +267 -3
- package/assets/devcontainer/utils/deps-install.sh +68 -0
- package/assets/devcontainer/utils/git-config-bootstrap.sh +128 -0
- package/assets/devcontainer/utils/git-signing-bootstrap.sh +109 -0
- package/assets/devcontainer/utils/python-bootstrap.sh +69 -0
- package/assets/devcontainer/utils/secret-env-bootstrap.sh +22 -0
- package/assets/devcontainer/utils/ssh-agent-proxy-bootstrap.sh +27 -0
- package/assets/devcontainer/utils/ssh-agent-proxy.mjs +39 -0
- package/assets/devcontainer/utils/ssh-bootstrap.sh +9 -0
- package/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/main-BDgyf2t5.cjs +5758 -0
- package/dist/main-J4_2Up3o.mjs +5718 -0
- package/dist/main-J4_2Up3o.mjs.map +1 -0
- package/dist/main.cjs +5 -2
- package/dist/main.d.cts +501 -4
- package/dist/main.d.cts.map +1 -1
- package/dist/main.d.mts +501 -4
- package/dist/main.d.mts.map +1 -1
- package/dist/main.mjs +2 -2
- package/docs/README.md +1 -0
- package/docs/architecture.md +32 -0
- package/docs/development.md +13 -6
- package/docs/features/README.md +2 -0
- package/docs/features/commit-signing.md +94 -0
- package/docs/features/generated-config-and-state.md +73 -5
- package/docs/features/github-auth-refresh.md +15 -2
- package/docs/features/lifecycle.md +103 -11
- package/docs/features/setup.md +66 -0
- package/docs/features/ssh-config-and-proxy.md +228 -7
- package/docs/features/start-and-shell.md +45 -5
- package/docs/superpowers/plans/2026-07-11-default-commit-signing.md +110 -0
- package/docs/superpowers/plans/2026-07-11-progress-ci-regression.md +125 -0
- package/docs/superpowers/plans/2026-07-17-node-ssh-agent-proxy-signing.md +132 -0
- package/docs/superpowers/plans/2026-07-17-runtime-secret-environment.md +174 -0
- package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +416 -0
- package/docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md +43 -0
- package/docs/superpowers/specs/2026-07-17-node-ssh-agent-proxy-design.md +63 -0
- package/docs/superpowers/specs/2026-07-17-runtime-secret-environment-design.md +194 -0
- package/docs/testing.md +35 -2
- package/docs/todo.md +2 -2
- package/package.json +1 -1
- package/src/claude-app-config.ts +304 -0
- package/src/cli-style.ts +43 -0
- package/src/codex-app-config.ts +656 -0
- package/src/config.ts +80 -10
- package/src/constants.ts +12 -0
- package/src/devcontainer.ts +511 -64
- package/src/doctor.ts +292 -30
- package/src/git-signing.ts +267 -0
- package/src/interactive-prompts.ts +692 -0
- package/src/list.ts +16 -2
- package/src/logging.ts +164 -0
- package/src/main.ts +1214 -63
- package/src/metadata.ts +52 -3
- package/src/package-info.ts +18 -0
- package/src/paths.ts +71 -11
- package/src/process.ts +80 -2
- package/src/progress.ts +593 -0
- package/src/purge.ts +216 -0
- package/src/shell.ts +25 -0
- package/src/ssh-config.ts +134 -16
- package/src/ssh-install-targets.ts +111 -0
- package/src/ssh-key.ts +53 -13
- package/src/status.ts +5 -0
- package/dist/main-BuEptwlL.cjs +0 -1707
- package/dist/main-ZFTrSVgt.mjs +0 -1685
- package/dist/main-ZFTrSVgt.mjs.map +0 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# Default Commit Signing Design
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Boxdown will attempt to enable SSH commit signing by default in every newly
|
|
6
|
+
created environment. It will forward the host SSH agent into the container,
|
|
7
|
+
select a signing identity without user interaction when that choice is
|
|
8
|
+
unambiguous, and configure container-global Git settings for SSH signing.
|
|
9
|
+
|
|
10
|
+
Signing is best-effort. If Boxdown cannot select or use a signing identity, it
|
|
11
|
+
will warn, disable signing in the container-global Git configuration, and allow
|
|
12
|
+
unsigned commits. Signing readiness will never make `boxdown setup` fail.
|
|
13
|
+
|
|
14
|
+
This design applies only to newly created Boxdown environments. It does not
|
|
15
|
+
migrate existing containers or repair repository-local Git settings written by
|
|
16
|
+
older Boxdown versions.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
- Sign commits by default without a separate Boxdown signing setup command.
|
|
21
|
+
- Keep private signing keys on the host and expose only an agent-backed signing
|
|
22
|
+
capability to the container.
|
|
23
|
+
- Provide the same signing capability through `boxdown setup`, `start`,
|
|
24
|
+
`codex`, `claude`, `opencode`, `antigravity`, SSH proxy, and tunnel flows.
|
|
25
|
+
- Continue allowing commits when signing cannot be configured or validated.
|
|
26
|
+
- Detect signing problems in `boxdown doctor` and the setup preflight without
|
|
27
|
+
making signing readiness a setup prerequisite.
|
|
28
|
+
- Explain the difference between a cryptographically signed commit and a
|
|
29
|
+
commit that GitHub displays as Verified.
|
|
30
|
+
|
|
31
|
+
## Non-goals
|
|
32
|
+
|
|
33
|
+
- Migrating existing Boxdown environments or legacy `.git/config` values.
|
|
34
|
+
- Copying a private GPG or SSH key into a container or Boxdown state.
|
|
35
|
+
- Automatically changing the user's GitHub account or registering signing
|
|
36
|
+
keys without explicit user action.
|
|
37
|
+
- Guaranteeing that a host agent remains available after an interactive
|
|
38
|
+
session has started.
|
|
39
|
+
- Intercepting `git commit` and automatically retrying it with
|
|
40
|
+
`--no-gpg-sign`.
|
|
41
|
+
|
|
42
|
+
## Approaches Considered
|
|
43
|
+
|
|
44
|
+
### SSH signing through the Docker agent bridge
|
|
45
|
+
|
|
46
|
+
This is the selected approach. Docker Desktop exposes the host SSH agent to a
|
|
47
|
+
container through `/run/host-services/ssh-auth.sock`, while native Linux Docker
|
|
48
|
+
can bind-mount the host path in `SSH_AUTH_SOCK`. Git supports signing through
|
|
49
|
+
that agent with `gpg.format=ssh`.
|
|
50
|
+
|
|
51
|
+
This approach works for both SSH sessions and Boxdown's primary
|
|
52
|
+
`devcontainer exec` execution path. It does not copy private key material.
|
|
53
|
+
|
|
54
|
+
### GPG-agent forwarding over SSH
|
|
55
|
+
|
|
56
|
+
GPG-agent forwarding was validated successfully, but the forwarded socket
|
|
57
|
+
exists only during an SSH connection. Boxdown launches shells and coding-agent
|
|
58
|
+
CLIs through `devcontainer exec`, so GPG forwarding would require an additional
|
|
59
|
+
long-lived host proxy and remote socket lifecycle management. It is not the
|
|
60
|
+
default design.
|
|
61
|
+
|
|
62
|
+
### Container-owned signing key
|
|
63
|
+
|
|
64
|
+
A key persisted in Boxdown state would remain available independently of the
|
|
65
|
+
host agent, but the container could read and copy the private key. Every new
|
|
66
|
+
key would also require GitHub registration. This weakens Boxdown's isolation
|
|
67
|
+
boundary and is rejected.
|
|
68
|
+
|
|
69
|
+
## Signing Policy
|
|
70
|
+
|
|
71
|
+
Signing is default-on but best-effort:
|
|
72
|
+
|
|
73
|
+
1. Boxdown attempts to resolve one host SSH signing identity.
|
|
74
|
+
2. Boxdown attempts to expose the host agent to the container.
|
|
75
|
+
3. The container verifies that the selected identity is loaded and can create
|
|
76
|
+
a disposable signed commit.
|
|
77
|
+
4. If all checks pass, container-global `commit.gpgsign` is set to `true`.
|
|
78
|
+
5. If any check fails, container-global `commit.gpgsign` is set to `false` and
|
|
79
|
+
Boxdown reports a warning.
|
|
80
|
+
|
|
81
|
+
Boxdown must never select the first of multiple ambiguous candidates. If it
|
|
82
|
+
cannot identify exactly one intended key, signing is disabled and commits
|
|
83
|
+
continue unsigned. This behavior must be documented in the README, signing
|
|
84
|
+
feature documentation, `boxdown doctor` output, and setup warnings.
|
|
85
|
+
|
|
86
|
+
Boxdown will no longer write `commit.gpgsign` into the repository-local
|
|
87
|
+
`.git/config`. Repository-local configuration is shared with the host and is
|
|
88
|
+
not container-owned state.
|
|
89
|
+
|
|
90
|
+
An explicit repository-local `commit.gpgsign=true` remains the user's policy
|
|
91
|
+
and can override Boxdown's container-global fallback. Boxdown does not rewrite
|
|
92
|
+
explicit repository policy to guarantee unsigned fallback.
|
|
93
|
+
|
|
94
|
+
## Host Signing Resolution
|
|
95
|
+
|
|
96
|
+
A new host-side module will produce a typed signing plan before generated
|
|
97
|
+
devcontainer configuration is written. The plan records whether signing is
|
|
98
|
+
enabled, the selected public key, its fingerprint, the agent mount source, and
|
|
99
|
+
a stable reason when signing is unavailable.
|
|
100
|
+
|
|
101
|
+
Public keys are compared by algorithm and base64 key data. Comments are not
|
|
102
|
+
part of identity matching.
|
|
103
|
+
|
|
104
|
+
Boxdown resolves a signing identity in this order:
|
|
105
|
+
|
|
106
|
+
1. If the host Git configuration uses `gpg.format=ssh`, resolve its
|
|
107
|
+
`user.signingKey`. Select it only if the corresponding public key is loaded
|
|
108
|
+
in the host agent.
|
|
109
|
+
2. Otherwise, when authenticated GitHub CLI access and the network are
|
|
110
|
+
available, intersect the loaded agent identities with the current GitHub
|
|
111
|
+
user's SSH authentication keys. Select the key only when exactly one loaded
|
|
112
|
+
identity matches.
|
|
113
|
+
3. Otherwise, select the loaded identity only when the agent contains exactly
|
|
114
|
+
one identity.
|
|
115
|
+
4. With zero or multiple unresolved identities, produce a disabled signing
|
|
116
|
+
plan and a warning reason. Do not guess.
|
|
117
|
+
|
|
118
|
+
`user.signingKey` accepts an inline SSH public key, Git's `key::` public-key
|
|
119
|
+
form, an absolute path, a `~/` path, or a path relative to the workspace.
|
|
120
|
+
Boxdown reads public-key files only. An explicitly configured key is
|
|
121
|
+
authoritative: unreadable, malformed, or unloaded configured keys disable
|
|
122
|
+
signing and never fall through to GitHub or single-identity selection.
|
|
123
|
+
|
|
124
|
+
Failure to execute `ssh-add`, query GitHub, parse a public key, or resolve a
|
|
125
|
+
single identity is non-fatal.
|
|
126
|
+
|
|
127
|
+
The selected public key is written outside the repository under:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
~/.local/share/boxdown/workspaces/<workspace-id>/git-signing/signing-key.pub
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The signing state directory contains public material only. It is mounted
|
|
134
|
+
read-only at `/opt/boxdown/state/git-signing`.
|
|
135
|
+
|
|
136
|
+
## Agent Socket Resolution
|
|
137
|
+
|
|
138
|
+
The generated configuration uses the fixed container path:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
/run/boxdown/ssh-agent.sock
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
and sets:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
SSH_AUTH_SOCK=/run/boxdown/ssh-agent.sock
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
On Docker Desktop for macOS and Linux, the mount source is Docker Desktop's
|
|
151
|
+
host-service socket:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
/run/host-services/ssh-auth.sock
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
On native Linux Docker, the source is the host value of `SSH_AUTH_SOCK`.
|
|
158
|
+
|
|
159
|
+
Boxdown probes a candidate source through Docker before adding it to generated
|
|
160
|
+
configuration. If the probe fails, the mount and `SSH_AUTH_SOCK` container
|
|
161
|
+
environment value are omitted and the signing plan is disabled. This prevents
|
|
162
|
+
an unavailable bind source from blocking container creation.
|
|
163
|
+
|
|
164
|
+
Because mounts are create-time state, an environment created while the agent
|
|
165
|
+
bridge is unavailable remains unsigned until it is recreated. Boxdown warns
|
|
166
|
+
about this limitation; it does not mutate existing container mounts.
|
|
167
|
+
|
|
168
|
+
## Generated Devcontainer Configuration
|
|
169
|
+
|
|
170
|
+
When signing is enabled, the generated configuration includes mounts
|
|
171
|
+
equivalent to:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"mounts": [
|
|
176
|
+
"type=bind,source=/run/host-services/ssh-auth.sock,target=/run/boxdown/ssh-agent.sock",
|
|
177
|
+
"type=bind,source=<workspace-signing-state>,target=/opt/boxdown/state/git-signing,readonly"
|
|
178
|
+
],
|
|
179
|
+
"containerEnv": {
|
|
180
|
+
"SSH_AUTH_SOCK": "/run/boxdown/ssh-agent.sock"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Native Linux substitutes the resolved host agent socket for the first source.
|
|
186
|
+
The signing mount and environment value are absent from a disabled plan.
|
|
187
|
+
|
|
188
|
+
Generated configuration accepts the resolved signing plan as input rather
|
|
189
|
+
than discovering host or Docker state inside the pure configuration builder.
|
|
190
|
+
This keeps mount generation deterministic and unit-testable.
|
|
191
|
+
|
|
192
|
+
## Container Git Configuration
|
|
193
|
+
|
|
194
|
+
Container Git bootstrap will stop unconditionally disabling commit signing.
|
|
195
|
+
When the selected identity and socket validate, it will configure the writable
|
|
196
|
+
container-global Git configuration with:
|
|
197
|
+
|
|
198
|
+
```ini
|
|
199
|
+
[gpg]
|
|
200
|
+
format = ssh
|
|
201
|
+
[user]
|
|
202
|
+
signingKey = /opt/boxdown/state/git-signing/signing-key.pub
|
|
203
|
+
[commit]
|
|
204
|
+
gpgSign = true
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The bootstrap removes incompatible copied host settings such as
|
|
208
|
+
`gpg.program=/opt/homebrew/bin/gpg`. It does not need a global
|
|
209
|
+
`gpg.ssh.allowedSignersFile` to create signatures. A disposable repository may
|
|
210
|
+
use a temporary allowed-signers file when validating its own signature.
|
|
211
|
+
|
|
212
|
+
When validation fails, the container-global configuration uses:
|
|
213
|
+
|
|
214
|
+
```ini
|
|
215
|
+
[commit]
|
|
216
|
+
gpgSign = false
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
and removes Boxdown-owned SSH signing values that would otherwise leave an
|
|
220
|
+
inconsistent configuration.
|
|
221
|
+
|
|
222
|
+
Tag-signing behavior is not changed beyond removing the current unconditional
|
|
223
|
+
`tag.gpgsign=false`. When a copied host configuration explicitly enables tag
|
|
224
|
+
signing, the selected SSH signing configuration can satisfy it. Commit signing
|
|
225
|
+
is the feature guaranteed by this design.
|
|
226
|
+
|
|
227
|
+
## Lifecycle Consistency
|
|
228
|
+
|
|
229
|
+
The Docker agent mount makes the host agent available to all container
|
|
230
|
+
processes. It therefore covers both SSH access and the `devcontainer exec`
|
|
231
|
+
path used by `boxdown start` and coding-agent commands.
|
|
232
|
+
|
|
233
|
+
A focused container script owns signing configuration and validation. A shared
|
|
234
|
+
`ensureContainerGitSigning` lifecycle helper invokes it after a container is
|
|
235
|
+
started or reused and before control is handed to a shell, coding agent, SSH
|
|
236
|
+
proxy, tunnel, or setup completion.
|
|
237
|
+
|
|
238
|
+
All entry points use the same sequence:
|
|
239
|
+
|
|
240
|
+
1. Resolve the host signing plan.
|
|
241
|
+
2. Write signing-aware generated configuration.
|
|
242
|
+
3. Start or reuse the container.
|
|
243
|
+
4. Refresh container signing readiness.
|
|
244
|
+
5. Continue normally whether signing is enabled or disabled.
|
|
245
|
+
|
|
246
|
+
The signing refresh performs a disposable signed-commit test. A failed test
|
|
247
|
+
changes the container-global fallback to unsigned and reports a warning. The
|
|
248
|
+
test does not write to the user's repository.
|
|
249
|
+
|
|
250
|
+
If the host agent or selected key disappears after an interactive process has
|
|
251
|
+
started, a later `git commit` can still fail. Boxdown will document this narrow
|
|
252
|
+
runtime limitation instead of replacing or wrapping the Git executable.
|
|
253
|
+
|
|
254
|
+
## Doctor and Setup Preflight
|
|
255
|
+
|
|
256
|
+
Signing readiness is optional. Every signing-related doctor result has `pass`
|
|
257
|
+
or `warn` severity and never contributes a required setup failure.
|
|
258
|
+
|
|
259
|
+
Host and preliminary checks cover:
|
|
260
|
+
|
|
261
|
+
1. The host SSH agent can be queried.
|
|
262
|
+
2. A signing identity can be selected unambiguously.
|
|
263
|
+
3. Docker can expose the candidate agent socket to a container.
|
|
264
|
+
4. When GitHub CLI and the network are available, the selected key is
|
|
265
|
+
registered as a GitHub SSH signing key.
|
|
266
|
+
|
|
267
|
+
Container checks cover:
|
|
268
|
+
|
|
269
|
+
1. `SSH_AUTH_SOCK` is reachable.
|
|
270
|
+
2. The selected public key is present in `ssh-add -L`.
|
|
271
|
+
3. A disposable Git repository can create a signed commit.
|
|
272
|
+
4. The disposable commit contains a valid SSH signature from the selected
|
|
273
|
+
fingerprint.
|
|
274
|
+
|
|
275
|
+
`boxdown setup` runs relevant host checks during its existing preliminary
|
|
276
|
+
doctor stage and prints warnings before container creation. After the
|
|
277
|
+
container starts, the shared signing refresh supplies the full container
|
|
278
|
+
validation.
|
|
279
|
+
|
|
280
|
+
`boxdown doctor` runs the host checks on every invocation. It runs full
|
|
281
|
+
container validation in an existing Boxdown container. When no Boxdown
|
|
282
|
+
container exists, it may use an already available local Boxdown image for a
|
|
283
|
+
disposable probe; it does not pull an image solely for signing diagnostics.
|
|
284
|
+
If a full probe is unavailable, doctor reports that limitation as a warning.
|
|
285
|
+
|
|
286
|
+
## GitHub Verification
|
|
287
|
+
|
|
288
|
+
A commit is cryptographically signed when Git creates and validates its SSH
|
|
289
|
+
signature. GitHub displays that commit as Verified only when the corresponding
|
|
290
|
+
public key is registered in the user's GitHub account as an SSH signing key.
|
|
291
|
+
|
|
292
|
+
GitHub treats authentication and signing registrations separately. A public
|
|
293
|
+
key already registered for SSH authentication must be uploaded a second time
|
|
294
|
+
with type `signing`.
|
|
295
|
+
|
|
296
|
+
Boxdown checks the selected key against `user/ssh_signing_keys` when possible.
|
|
297
|
+
If it is absent, Boxdown keeps local signing enabled and warns that GitHub will
|
|
298
|
+
not display Verified until the user completes the one-time registration. The
|
|
299
|
+
warning includes an actionable command such as:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
gh ssh-key add <public-key-file> --type signing --title "Boxdown commit signing"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Boxdown does not execute this account mutation automatically. GitHub or
|
|
306
|
+
network unavailability never disables a locally usable signing identity after
|
|
307
|
+
that identity has already been selected.
|
|
308
|
+
|
|
309
|
+
## Security Model
|
|
310
|
+
|
|
311
|
+
The agent bridge does not expose private key bytes, but any process in the
|
|
312
|
+
container that can access the socket can request operations from every identity
|
|
313
|
+
exposed by that host agent. Enabling signing therefore grants the container a
|
|
314
|
+
signing oracle and potentially SSH authentication capability for the lifetime
|
|
315
|
+
of the mount.
|
|
316
|
+
|
|
317
|
+
Documentation will recommend a dedicated signing identity or an agent that
|
|
318
|
+
requires confirmation for sensitive keys. The capability is default-on by
|
|
319
|
+
product decision, but doctor output and security documentation must describe
|
|
320
|
+
the exposure accurately.
|
|
321
|
+
|
|
322
|
+
## Error Reporting
|
|
323
|
+
|
|
324
|
+
Signing warnings use stable reasons so CLI and JSON doctor output remain
|
|
325
|
+
testable. At minimum, reasons distinguish:
|
|
326
|
+
|
|
327
|
+
- host agent unavailable;
|
|
328
|
+
- no loaded identities;
|
|
329
|
+
- ambiguous loaded identities;
|
|
330
|
+
- configured host SSH signing key unreadable or malformed;
|
|
331
|
+
- configured host SSH signing key not loaded;
|
|
332
|
+
- host agent socket unavailable;
|
|
333
|
+
- no local Docker image available for the agent mount probe;
|
|
334
|
+
- Docker agent bridge unavailable;
|
|
335
|
+
- public-key snapshot failure;
|
|
336
|
+
- selected identity missing in the container;
|
|
337
|
+
- disposable signing failure;
|
|
338
|
+
- GitHub signing registration missing;
|
|
339
|
+
- GitHub registration check unavailable.
|
|
340
|
+
|
|
341
|
+
Human-readable warnings state that commits will remain unsigned but are not
|
|
342
|
+
blocked. A missing GitHub registration warning instead states that commits are
|
|
343
|
+
signed locally but will not appear Verified on GitHub.
|
|
344
|
+
|
|
345
|
+
User-facing lifecycle commands print the concise warning. The workspace
|
|
346
|
+
command log additionally records the stable reason code and sanitized detail,
|
|
347
|
+
without public-key material or secrets. Internal SSH proxy startup remains
|
|
348
|
+
log-only. Generated configuration passes the stable host reason into container
|
|
349
|
+
bootstrap so later lifecycle warnings preserve the original diagnosis.
|
|
350
|
+
|
|
351
|
+
Docker Desktop may expose the mounted host-agent socket only to root. Boxdown
|
|
352
|
+
therefore relays it through a root-owned Unix-socket proxy to a node-owned
|
|
353
|
+
socket. Explicit user Git-signing preferences remain authoritative; Boxdown
|
|
354
|
+
uses SSH signing only as the default when no preference exists.
|
|
355
|
+
|
|
356
|
+
## Testing Strategy
|
|
357
|
+
|
|
358
|
+
### Unit tests
|
|
359
|
+
|
|
360
|
+
- Resolve a host-configured SSH signing key that is loaded in the agent.
|
|
361
|
+
- Resolve exactly one loaded key that matches GitHub authentication keys.
|
|
362
|
+
- Resolve a single loaded key without GitHub access.
|
|
363
|
+
- Disable signing for zero keys, multiple ambiguous keys, malformed keys, and
|
|
364
|
+
command or API failures.
|
|
365
|
+
- Compare public keys without considering comments.
|
|
366
|
+
- Generate Docker Desktop and native Linux socket mounts correctly.
|
|
367
|
+
- Omit signing mounts and environment values for disabled plans.
|
|
368
|
+
- Generate the signing public-key state mount as read-only.
|
|
369
|
+
- Configure SSH signing after a successful container probe.
|
|
370
|
+
- Fall back to unsigned global configuration after every validation failure.
|
|
371
|
+
- Prove that post-create no longer writes repository-local
|
|
372
|
+
`commit.gpgsign`.
|
|
373
|
+
- Report all signing preflight failures as warnings rather than failures.
|
|
374
|
+
- Exercise `setup`, `start`, coding-agent, SSH proxy, and tunnel paths through
|
|
375
|
+
the shared signing lifecycle.
|
|
376
|
+
|
|
377
|
+
### Shell-script tests
|
|
378
|
+
|
|
379
|
+
Container signing bootstrap tests use temporary Git homes, repositories, fake
|
|
380
|
+
agent output, and disposable public keys. They verify both the enabled Git
|
|
381
|
+
configuration and the non-blocking unsigned fallback.
|
|
382
|
+
|
|
383
|
+
### Docker integration test
|
|
384
|
+
|
|
385
|
+
A gated manual or integration test mounts a real agent bridge into a disposable
|
|
386
|
+
container, creates an empty signed commit, and verifies the signature and
|
|
387
|
+
fingerprint. It is skipped when Docker or a usable agent identity is absent.
|
|
388
|
+
It does not push a commit or change a GitHub account.
|
|
389
|
+
|
|
390
|
+
### Manual acceptance
|
|
391
|
+
|
|
392
|
+
Create a new Boxdown environment and verify signed commits from:
|
|
393
|
+
|
|
394
|
+
- `boxdown start`;
|
|
395
|
+
- `boxdown codex`;
|
|
396
|
+
- an SSH session through the generated Boxdown alias.
|
|
397
|
+
|
|
398
|
+
Push a test branch only when explicitly authorized, then verify GitHub's badge
|
|
399
|
+
before and after registering the selected public key as a signing key.
|
|
400
|
+
|
|
401
|
+
## Documentation
|
|
402
|
+
|
|
403
|
+
Implementation updates will include:
|
|
404
|
+
|
|
405
|
+
- a dedicated commit-signing feature document;
|
|
406
|
+
- README behavior and security notes;
|
|
407
|
+
- setup and doctor documentation;
|
|
408
|
+
- architecture documentation for signing state, generated mounts, and trust
|
|
409
|
+
boundaries;
|
|
410
|
+
- troubleshooting for ambiguity, absent agents, Docker mount failures,
|
|
411
|
+
unsigned fallback, and missing GitHub verification;
|
|
412
|
+
- a changeset describing default-on best-effort signing.
|
|
413
|
+
|
|
414
|
+
The documentation will explicitly state that multiple ambiguous agent keys
|
|
415
|
+
disable signing rather than causing Boxdown to guess, and that this fallback
|
|
416
|
+
allows unsigned commits.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Progress CI Regression Design
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Restore the test suite by fixing progress checklist cleanup in every output mode
|
|
6
|
+
and updating stale spinner-label assertions without changing user-facing output.
|
|
7
|
+
|
|
8
|
+
## Root Cause
|
|
9
|
+
|
|
10
|
+
`ProgressReporter.end()` stops timers before checking the output mode, but it
|
|
11
|
+
clears checklist state only after the interactive-only rendering guards. In
|
|
12
|
+
`none` and `verbose` modes, `isChecklistActive()` therefore remains true after
|
|
13
|
+
the reporter ends.
|
|
14
|
+
|
|
15
|
+
The SSH spinner labels remain present and are used when no checklist is active.
|
|
16
|
+
Their source expressions became conditional when checklist-owned output was
|
|
17
|
+
introduced, so the source-text test's direct-property regular expressions no
|
|
18
|
+
longer match them.
|
|
19
|
+
|
|
20
|
+
## Design
|
|
21
|
+
|
|
22
|
+
Move internal checklist cleanup ahead of the output-mode and section-rendering
|
|
23
|
+
guards in `ProgressReporter.end()`. Ending a reporter will always clear its
|
|
24
|
+
steps and rendered-line count, while only interactive reporters with an open
|
|
25
|
+
section will write the visual terminator.
|
|
26
|
+
|
|
27
|
+
Keep the existing source-presence test because it guards friendly spinner copy,
|
|
28
|
+
but make the two SSH assertions check for the label text rather than requiring
|
|
29
|
+
a particular property-expression shape. Existing runtime tests continue to
|
|
30
|
+
verify that checklist mode suppresses the standalone spinners.
|
|
31
|
+
|
|
32
|
+
## Scope
|
|
33
|
+
|
|
34
|
+
- Modify `src/progress.ts` only to make state cleanup mode-independent.
|
|
35
|
+
- Modify the two SSH label assertions in `__tests__/app.test.ts`.
|
|
36
|
+
- Do not change progress APIs, spinner copy, output formatting, or command flow.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
|
|
40
|
+
Use the two CI failures as the red phase, then run the targeted progress tests,
|
|
41
|
+
the full test suite, lint, and build. Verification must use a supported Node 24
|
|
42
|
+
runtime because the current host Node 26 runtime is incompatible with one of
|
|
43
|
+
the installed CommonJS test dependencies.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Node SSH-Agent Proxy for Commit Signing
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Make Boxdown's default SSH commit signing usable by the container's `node`
|
|
6
|
+
user when Docker Desktop mounts the host agent socket as `root:root` with mode
|
|
7
|
+
`0660`. Signing must remain best-effort and never expose a private key.
|
|
8
|
+
|
|
9
|
+
## Root Cause
|
|
10
|
+
|
|
11
|
+
The host preflight and the raw socket mount both succeed, but lifecycle hooks
|
|
12
|
+
run as `node`. In the reproduced environment, `ssh-add -L` succeeds as root
|
|
13
|
+
and fails as `node` with `Permission denied`. The signing bootstrap therefore
|
|
14
|
+
sets `commit.gpgsign=false` and reports `container-agent-unavailable`.
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
Boxdown continues to bind-mount the host-facing agent socket at
|
|
19
|
+
`/run/boxdown/ssh-agent.sock`. A small Node Unix-socket proxy runs as root and
|
|
20
|
+
forwards each connection from that raw socket to
|
|
21
|
+
`/run/boxdown/ssh-agent-node.sock`. The proxy creates the latter socket with
|
|
22
|
+
ownership and permissions suitable for `node`. Generated configuration sets
|
|
23
|
+
`SSH_AUTH_SOCK` to the node-facing socket and exposes the raw path only through
|
|
24
|
+
`BOXDOWN_GIT_SIGNING_SOURCE_SOCKET` for the proxy.
|
|
25
|
+
|
|
26
|
+
The post-create hook starts the proxy with passwordless `sudo`, waits briefly
|
|
27
|
+
for its socket, verifies `ssh-add -L` as `node`, then invokes the existing
|
|
28
|
+
signing bootstrap. A proxy failure is non-blocking and has its own stable
|
|
29
|
+
reason code.
|
|
30
|
+
|
|
31
|
+
## Git Configuration Precedence
|
|
32
|
+
|
|
33
|
+
**Decision:** Boxdown does not make its SSH configuration authoritative over
|
|
34
|
+
explicit user Git signing preferences. This is intentional because the
|
|
35
|
+
container begins from a writable copy of the user's Git configuration.
|
|
36
|
+
|
|
37
|
+
- With no explicit global or repository-local signing preference, Boxdown
|
|
38
|
+
configures SSH signing as the default.
|
|
39
|
+
- With an explicit SSH preference, Boxdown preserves the selected identity;
|
|
40
|
+
it only translates an inaccessible host public-key path to the mounted
|
|
41
|
+
public-key snapshot in the container.
|
|
42
|
+
- With `commit.gpgsign=false`, a non-SSH `gpg.format`, or an explicit
|
|
43
|
+
`gpg.program`, Boxdown preserves that preference and does not replace it.
|
|
44
|
+
|
|
45
|
+
Repository-local settings retain Git's normal precedence. In particular, a
|
|
46
|
+
local `commit.gpgsign=false` keeps commits unsigned even when agent forwarding
|
|
47
|
+
is healthy. A future product decision may deliberately reverse this policy and
|
|
48
|
+
make Boxdown authoritative; that would be a behavior change, not a bug fix.
|
|
49
|
+
|
|
50
|
+
## Failure Handling
|
|
51
|
+
|
|
52
|
+
The proxy never reads, copies, or logs private-key material. It reports
|
|
53
|
+
`container-agent-proxy-unavailable` when `sudo`, Node, proxy readiness, or the
|
|
54
|
+
node-user agent probe fails. The signing bootstrap remains non-blocking and
|
|
55
|
+
preserves explicit user configuration rather than setting a contradictory
|
|
56
|
+
global value.
|
|
57
|
+
|
|
58
|
+
## Validation
|
|
59
|
+
|
|
60
|
+
Tests cover forwarding a real Unix-socket request through the proxy, a proxy
|
|
61
|
+
startup failure, generated socket environment values, successful default SSH
|
|
62
|
+
signing, and preservation of global and local user preferences. Documentation
|
|
63
|
+
will require `--recreate` after upgrade.
|