boxdown 1.2.0 → 1.3.0
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 +10 -0
- package/assets/devcontainer/README.md +29 -6
- package/assets/devcontainer/devcontainer.json +4 -9
- package/assets/devcontainer/hooks/initialize.sh +49 -27
- package/assets/devcontainer/hooks/post-create.sh +23 -1
- package/assets/devcontainer/hooks/post-start.sh +0 -10
- package/assets/devcontainer/utils/git-signing-bootstrap.sh +58 -5
- 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/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/{main-Df4E8ARj.cjs → main-CT2n9qcb.cjs} +907 -266
- package/dist/{main-XMBsKjIK.mjs → main-O__JaiXe.mjs} +891 -268
- package/dist/main-O__JaiXe.mjs.map +1 -0
- package/dist/main.cjs +4 -1
- package/dist/main.d.cts +98 -26
- package/dist/main.d.cts.map +1 -1
- package/dist/main.d.mts +98 -26
- package/dist/main.d.mts.map +1 -1
- package/dist/main.mjs +2 -2
- package/docs/features/commit-signing.md +71 -0
- package/docs/features/generated-config-and-state.md +19 -4
- package/docs/features/github-auth-refresh.md +3 -2
- package/docs/features/lifecycle.md +19 -0
- package/docs/features/setup.md +5 -0
- package/docs/features/start-and-shell.md +10 -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/plans/2026-07-18-architecture-aware-1password-installer.md +163 -0
- package/docs/superpowers/plans/2026-07-18-devcontainer-node-image-digest.md +341 -0
- package/docs/superpowers/plans/2026-07-19-container-runtime-readiness.md +1536 -0
- package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +20 -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/superpowers/specs/2026-07-18-architecture-aware-1password-installer-design.md +63 -0
- package/docs/superpowers/specs/2026-07-18-devcontainer-node-image-digest-design.md +108 -0
- package/docs/superpowers/specs/2026-07-19-container-runtime-readiness-design.md +353 -0
- package/package.json +1 -1
- package/src/config.ts +14 -2
- package/src/constants.ts +7 -0
- package/src/container-runtime.ts +295 -0
- package/src/devcontainer.ts +57 -30
- package/src/doctor.ts +169 -46
- package/src/git-signing.ts +205 -25
- package/src/logging.ts +11 -1
- package/src/main.ts +97 -12
- package/src/paths.ts +21 -1
- package/src/process.ts +50 -10
- package/src/progress.ts +63 -8
- package/src/purge.ts +8 -0
- package/src/shell.ts +6 -0
- package/dist/main-XMBsKjIK.mjs.map +0 -1
|
@@ -115,6 +115,12 @@ Boxdown resolves a signing identity in this order:
|
|
|
115
115
|
4. With zero or multiple unresolved identities, produce a disabled signing
|
|
116
116
|
plan and a warning reason. Do not guess.
|
|
117
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
|
+
|
|
118
124
|
Failure to execute `ssh-add`, query GitHub, parse a public key, or resolve a
|
|
119
125
|
single identity is non-fatal.
|
|
120
126
|
|
|
@@ -321,7 +327,10 @@ testable. At minimum, reasons distinguish:
|
|
|
321
327
|
- host agent unavailable;
|
|
322
328
|
- no loaded identities;
|
|
323
329
|
- ambiguous loaded identities;
|
|
330
|
+
- configured host SSH signing key unreadable or malformed;
|
|
324
331
|
- configured host SSH signing key not loaded;
|
|
332
|
+
- host agent socket unavailable;
|
|
333
|
+
- no local Docker image available for the agent mount probe;
|
|
325
334
|
- Docker agent bridge unavailable;
|
|
326
335
|
- public-key snapshot failure;
|
|
327
336
|
- selected identity missing in the container;
|
|
@@ -333,6 +342,17 @@ Human-readable warnings state that commits will remain unsigned but are not
|
|
|
333
342
|
blocked. A missing GitHub registration warning instead states that commits are
|
|
334
343
|
signed locally but will not appear Verified on GitHub.
|
|
335
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
|
+
|
|
336
356
|
## Testing Strategy
|
|
337
357
|
|
|
338
358
|
### Unit tests
|
|
@@ -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.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Runtime Secret Environment Design
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Keep Boxdown-provided secrets available as ordinary environment variables in
|
|
6
|
+
container user sessions without placing their values in Docker container
|
|
7
|
+
configuration, the workspace, or Boxdown's persistent state and logs.
|
|
8
|
+
|
|
9
|
+
This design covers `ANTHROPIC_API_KEY`, `SNYK_TOKEN`, and
|
|
10
|
+
`OP_SERVICE_ACCOUNT_TOKEN`. It also prevents the Docker inspection command
|
|
11
|
+
used for image metadata from logging the complete container configuration.
|
|
12
|
+
|
|
13
|
+
## Non-goals
|
|
14
|
+
|
|
15
|
+
- Migrating, deleting, or rewriting legacy `.env.development` files.
|
|
16
|
+
- Adding a `boxdown doctor --fix-secrets` command.
|
|
17
|
+
- Preventing a user who can enter the container from reading secrets that are
|
|
18
|
+
deliberately available to that user's session.
|
|
19
|
+
- Changing GitHub CLI authentication refresh, which already passes its host
|
|
20
|
+
token over stdin and redacts it from the workspace log.
|
|
21
|
+
|
|
22
|
+
## Security Model
|
|
23
|
+
|
|
24
|
+
The user has requested compatibility with the current developer experience:
|
|
25
|
+
the three values above remain ordinary environment variables in Bash-based
|
|
26
|
+
container sessions. This intentionally permits session processes to read them.
|
|
27
|
+
|
|
28
|
+
The protection boundary is against accidental exposure outside that session:
|
|
29
|
+
|
|
30
|
+
- Docker inspection must not contain secret values.
|
|
31
|
+
- The repository and its `.env.development` file must not be created,
|
|
32
|
+
modified, or removed by Boxdown secret handling.
|
|
33
|
+
- Boxdown's persistent workspace data and `boxdown.log` must not contain
|
|
34
|
+
secret values.
|
|
35
|
+
- Secret files are short-lived, host-private runtime state and are mounted
|
|
36
|
+
read-only into the container.
|
|
37
|
+
|
|
38
|
+
## Architecture
|
|
39
|
+
|
|
40
|
+
### Host runtime secret state
|
|
41
|
+
|
|
42
|
+
`WorkspaceContext` gains a per-workspace runtime-secret directory under a
|
|
43
|
+
user-private runtime root. The root uses `XDG_RUNTIME_DIR` when available and
|
|
44
|
+
a user-private directory under the system temporary directory otherwise. It
|
|
45
|
+
is distinct from `workspaceDataDir`, which is persistent and contains logs and
|
|
46
|
+
metadata.
|
|
47
|
+
|
|
48
|
+
The directory and each secret file are created with owner-only permissions
|
|
49
|
+
(`0700` directory, `0600` files). The three files use fixed names matching
|
|
50
|
+
their environment variables. No secret value appears in generated
|
|
51
|
+
configuration, command arguments, progress output, metadata, or diagnostics.
|
|
52
|
+
|
|
53
|
+
The host-side `initializeCommand` refreshes this directory before each
|
|
54
|
+
Dev Container startup:
|
|
55
|
+
|
|
56
|
+
1. It creates the runtime directory safely.
|
|
57
|
+
2. It writes `ANTHROPIC_API_KEY` and `SNYK_TOKEN` only when each corresponding
|
|
58
|
+
host environment value is non-empty.
|
|
59
|
+
3. It retrieves `OP_SERVICE_ACCOUNT_TOKEN` from the existing 1Password item
|
|
60
|
+
only when `op` is available and can read it.
|
|
61
|
+
4. It atomically replaces files for available values and removes files for
|
|
62
|
+
unavailable values, preventing a stale token from a previous start.
|
|
63
|
+
|
|
64
|
+
Failures to retrieve 1Password or an absent host variable are informational
|
|
65
|
+
and non-blocking. The affected variable is absent from the next container
|
|
66
|
+
session.
|
|
67
|
+
|
|
68
|
+
### Generated Dev Container configuration
|
|
69
|
+
|
|
70
|
+
The base `runArgs` no longer includes:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
--env-file ${localWorkspaceFolder}/.env.development
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The base `containerEnv` no longer includes secret values via `localEnv`.
|
|
77
|
+
`NODE_ENV` remains ordinary non-secret configuration.
|
|
78
|
+
|
|
79
|
+
Boxdown's generated configuration adds a read-only bind mount from the host
|
|
80
|
+
runtime-secret directory to `/run/boxdown/secrets`. It may contain non-secret
|
|
81
|
+
bootstrap configuration such as `BASH_ENV` and the mounted directory path,
|
|
82
|
+
but never a secret value.
|
|
83
|
+
|
|
84
|
+
Because mounts are create-time settings, `boxdown start --recreate` is needed
|
|
85
|
+
for an already-created container to receive the new mount. New containers get
|
|
86
|
+
it automatically through every Boxdown lifecycle path (`setup`, `start`,
|
|
87
|
+
`codex`, and the other coding-agent commands).
|
|
88
|
+
|
|
89
|
+
### Container session bootstrap
|
|
90
|
+
|
|
91
|
+
A mounted Boxdown bootstrap script reads only the fixed secret files, then
|
|
92
|
+
exports values that are present. It never echoes values or shell source lines.
|
|
93
|
+
It handles missing or unreadable files by leaving the corresponding variable
|
|
94
|
+
unset.
|
|
95
|
+
|
|
96
|
+
The generated configuration sets the non-secret `BASH_ENV` path so
|
|
97
|
+
non-interactive Bash processes source the bootstrap. The container setup also
|
|
98
|
+
installs the bootstrap in the `node` user's Bash startup path for interactive
|
|
99
|
+
shells and SSH sessions. Boxdown's direct shell and coding-agent launch
|
|
100
|
+
scripts source it explicitly before `exec` as an additional deterministic
|
|
101
|
+
path.
|
|
102
|
+
|
|
103
|
+
Consequently, an ordinary Bash session can use, for example,
|
|
104
|
+
`$ANTHROPIC_API_KEY`, `$SNYK_TOKEN`, and `$OP_SERVICE_ACCOUNT_TOKEN` as it can
|
|
105
|
+
today. Docker's configured environment contains only the bootstrap path and
|
|
106
|
+
other non-secret values, so `docker inspect` cannot reveal secret contents.
|
|
107
|
+
|
|
108
|
+
### Workspace file handling
|
|
109
|
+
|
|
110
|
+
The initialization hook stops creating or editing `.env.development`. The
|
|
111
|
+
post-start hook stops deleting it. Boxdown therefore treats that file as
|
|
112
|
+
project-owned in all cases.
|
|
113
|
+
|
|
114
|
+
This release deliberately does not modify legacy files. A pre-existing
|
|
115
|
+
`OP_SERVICE_ACCOUNT_TOKEN` value remains the user's responsibility to remove
|
|
116
|
+
or rotate. Documentation will state that Boxdown no longer reads the file and
|
|
117
|
+
that users should remove a legacy accidental entry manually.
|
|
118
|
+
|
|
119
|
+
### Logging and Docker inspection
|
|
120
|
+
|
|
121
|
+
Boxdown currently runs `docker inspect --format '{{json .}}'` only to record
|
|
122
|
+
the image ID and image name. The full payload includes `.Config.Env`, and the
|
|
123
|
+
command logger writes all child output even when it is not mirrored to the
|
|
124
|
+
terminal.
|
|
125
|
+
|
|
126
|
+
The implementation replaces that command with a narrow template that returns
|
|
127
|
+
only the image ID and configured image name. Its parser accepts that narrow,
|
|
128
|
+
non-sensitive output; it never parses a complete inspect object.
|
|
129
|
+
|
|
130
|
+
The workspace logger also gains structural redaction for assignments of the
|
|
131
|
+
three Boxdown secret variable names. This is a defense-in-depth backstop for
|
|
132
|
+
legacy logs or future accidental command output, not the primary protection.
|
|
133
|
+
Existing value-based redaction remains for dynamically acquired GitHub tokens.
|
|
134
|
+
|
|
135
|
+
## Lifecycle and Cleanup
|
|
136
|
+
|
|
137
|
+
- `initializeCommand` is the only writer of runtime secret files.
|
|
138
|
+
- `down` and `purge` remove the runtime-secret directory after stopping or
|
|
139
|
+
removing the associated container.
|
|
140
|
+
- A new start refreshes values atomically, so no previous value remains when a
|
|
141
|
+
host value is absent or 1Password lookup fails.
|
|
142
|
+
- Runtime secret state is never written below `workspaceDataDir` and is never
|
|
143
|
+
included in status output.
|
|
144
|
+
- Existing workspaces require `boxdown start --recreate` to replace the old
|
|
145
|
+
Docker environment configuration. Boxdown will not automatically migrate
|
|
146
|
+
existing containers or `.env.development` files.
|
|
147
|
+
|
|
148
|
+
## Diagnostics and Documentation
|
|
149
|
+
|
|
150
|
+
Startup output may state whether the optional 1Password token was available,
|
|
151
|
+
but must not print values, file contents, or paths that encode secret data.
|
|
152
|
+
`boxdown doctor` verifies that the runtime-secret mount is constructible and
|
|
153
|
+
that the generated configuration contains neither the legacy `--env-file`
|
|
154
|
+
argument nor secret `containerEnv` entries. Missing optional values are
|
|
155
|
+
warnings, not failures.
|
|
156
|
+
|
|
157
|
+
Documentation will explain:
|
|
158
|
+
|
|
159
|
+
- which host values Boxdown forwards;
|
|
160
|
+
- that they are exposed to Bash sessions but not Docker configuration;
|
|
161
|
+
- that `.env.development` is project-owned and ignored by Boxdown;
|
|
162
|
+
- that `--recreate` is required for existing containers; and
|
|
163
|
+
- how to manually remove and rotate a legacy 1Password token if it was
|
|
164
|
+
previously written to a workspace or log.
|
|
165
|
+
|
|
166
|
+
## Test Strategy
|
|
167
|
+
|
|
168
|
+
Tests will cover:
|
|
169
|
+
|
|
170
|
+
- host secret-state creation, owner-only modes, atomic refresh, and removal of
|
|
171
|
+
stale files without printing values;
|
|
172
|
+
- success, missing host values, missing `op`, and failed 1Password lookup as
|
|
173
|
+
non-blocking cases;
|
|
174
|
+
- generated configuration contains the read-only runtime mount and contains no
|
|
175
|
+
`--env-file`, secret `containerEnv` keys, or secret values;
|
|
176
|
+
- `.env.development` is neither created nor altered by initialization or
|
|
177
|
+
post-start hooks;
|
|
178
|
+
- interactive, non-interactive, SSH, and Boxdown coding-agent Bash launch
|
|
179
|
+
paths export available values and leave unavailable values unset;
|
|
180
|
+
- image inspection uses only a narrow projection and workspace logs never
|
|
181
|
+
receive a synthetic inspect environment value;
|
|
182
|
+
- logger structural redaction for all three known keys;
|
|
183
|
+
- doctor warnings and mount probes, with optional-secret absence non-fatal;
|
|
184
|
+
- focused tests first, then the full test suite, lint, build, Bash syntax
|
|
185
|
+
checks, and `git diff --check`.
|
|
186
|
+
|
|
187
|
+
## Decision Record
|
|
188
|
+
|
|
189
|
+
The chosen design favors compatibility over strict command-level least
|
|
190
|
+
privilege. The three values are intentionally ordinary environment variables
|
|
191
|
+
inside Bash sessions. If this causes unacceptable in-container exposure or
|
|
192
|
+
developer-experience problems, a future design may replace session bootstrap
|
|
193
|
+
with per-command wrappers. That change would be a deliberate compatibility
|
|
194
|
+
break and is not part of this work.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Architecture-Aware 1Password Installer Design
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Install the pinned 1Password CLI archive that matches the devcontainer's Linux
|
|
6
|
+
architecture, and add fast CI coverage for both supported architecture paths.
|
|
7
|
+
|
|
8
|
+
## Current Problem
|
|
9
|
+
|
|
10
|
+
`assets/devcontainer/hooks/post-create.sh` always downloads the 1Password
|
|
11
|
+
`op_linux_arm64` archive. On amd64 devcontainers this installs an incompatible
|
|
12
|
+
binary. The existing CI job runs on amd64, but its tests never execute the
|
|
13
|
+
installer's architecture selection, so the mismatch is not detected.
|
|
14
|
+
|
|
15
|
+
## Design
|
|
16
|
+
|
|
17
|
+
Keep the existing pinned 1Password CLI version and prebuilt zip installation
|
|
18
|
+
flow. Inside `install_1password_cli`, map the value from `uname -m` to the
|
|
19
|
+
1Password archive architecture:
|
|
20
|
+
|
|
21
|
+
- `x86_64` and `amd64` select `amd64`.
|
|
22
|
+
- `aarch64` and `arm64` select `arm64`.
|
|
23
|
+
- Any other value prints a warning and returns successfully without downloading
|
|
24
|
+
or installing a binary.
|
|
25
|
+
|
|
26
|
+
The selected architecture becomes part of the existing archive URL. No source
|
|
27
|
+
build, Buildx configuration, QEMU emulation, version change, or installer
|
|
28
|
+
refactor is required.
|
|
29
|
+
|
|
30
|
+
## Test Coverage
|
|
31
|
+
|
|
32
|
+
Add behavioral smoke tests that source the real post-create hook and invoke
|
|
33
|
+
`install_1password_cli` with controlled command shims. The shims replace
|
|
34
|
+
network and privileged filesystem operations while recording the requested
|
|
35
|
+
download URL.
|
|
36
|
+
|
|
37
|
+
The tests cover:
|
|
38
|
+
|
|
39
|
+
- `x86_64` selecting the `op_linux_amd64` archive.
|
|
40
|
+
- `aarch64` selecting the `op_linux_arm64` archive.
|
|
41
|
+
- An unsupported architecture returning successfully, warning, and performing
|
|
42
|
+
no download.
|
|
43
|
+
|
|
44
|
+
These tests run in the existing `pnpm run test` CI step on `ubuntu-latest`.
|
|
45
|
+
This gives the amd64 CI runner explicit coverage of both supported selection
|
|
46
|
+
branches without downloading 1Password or building a multi-architecture
|
|
47
|
+
container.
|
|
48
|
+
|
|
49
|
+
## Scope
|
|
50
|
+
|
|
51
|
+
- Modify `assets/devcontainer/hooks/post-create.sh` only for 1Password archive
|
|
52
|
+
selection and unsupported-architecture handling.
|
|
53
|
+
- Add focused tests to `__tests__/app.test.ts` using the repository's existing
|
|
54
|
+
shell-script test conventions.
|
|
55
|
+
- Do not alter the Snyk installer, devcontainer image, CI runner matrix, or
|
|
56
|
+
other lifecycle behavior.
|
|
57
|
+
|
|
58
|
+
## Verification
|
|
59
|
+
|
|
60
|
+
Use test-driven development: add the focused architecture tests and confirm
|
|
61
|
+
they fail against the hard-coded arm64 URL, implement the minimum shell change,
|
|
62
|
+
then rerun the focused tests. Finish with the repository's full test, lint,
|
|
63
|
+
build, shell syntax, and diff checks.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Devcontainer Node Image Digest Pinning
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Make Boxdown's packaged devcontainer base image reproducible across rebuilds
|
|
6
|
+
and host architectures while preserving the existing Node 24, Debian trixie,
|
|
7
|
+
and slim-image track. Keep the immutable image reference current through
|
|
8
|
+
reviewable automated pull requests.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
This change applies to the reusable devcontainer template at
|
|
13
|
+
`assets/devcontainer/devcontainer.json`. It does not change Boxdown's runtime
|
|
14
|
+
generation model, the Dev Container Feature pins, npm dependency automation,
|
|
15
|
+
or GitHub Actions dependency automation.
|
|
16
|
+
|
|
17
|
+
## Image Reference
|
|
18
|
+
|
|
19
|
+
The template will replace the mutable reference:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
node:24-trixie-slim
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
with the same tag plus `@sha256:` and the verified 64-hex-character
|
|
26
|
+
multi-platform index digest.
|
|
27
|
+
|
|
28
|
+
The implementation will resolve the current digest from the upstream Node
|
|
29
|
+
image registry and verify that it identifies the multi-platform image index,
|
|
30
|
+
not an architecture-specific child manifest. Keeping the tag documents the
|
|
31
|
+
intended Node 24 and Debian trixie slim update track. The digest makes the
|
|
32
|
+
actual selected index immutable, so AMD64 and ARM64 hosts resolve their
|
|
33
|
+
platform image from the same pinned release set.
|
|
34
|
+
|
|
35
|
+
The tag deliberately remains at Node major-version precision. Node patch
|
|
36
|
+
releases and upstream rebuilds stay explicit through digest-update pull
|
|
37
|
+
requests without moving Boxdown to another Node major or Debian variant.
|
|
38
|
+
|
|
39
|
+
## Update Automation
|
|
40
|
+
|
|
41
|
+
A root `renovate.json` will configure Renovate's native devcontainer manager.
|
|
42
|
+
The configuration will:
|
|
43
|
+
|
|
44
|
+
- enable only the devcontainer manager, avoiding overlap with the existing
|
|
45
|
+
Dependabot npm and GitHub Actions configuration;
|
|
46
|
+
- extend devcontainer file matching to
|
|
47
|
+
`assets/devcontainer/devcontainer.json`, which is outside Renovate's default
|
|
48
|
+
root `.devcontainer` locations;
|
|
49
|
+
- disable Dev Container Feature updates so the existing digest-pinned Feature
|
|
50
|
+
set remains outside this change;
|
|
51
|
+
- match only the `node` image dependency in the packaged template;
|
|
52
|
+
- preserve the `24-trixie-slim` tag and update its digest; and
|
|
53
|
+
- allow digest-refresh pull requests on a monthly schedule.
|
|
54
|
+
|
|
55
|
+
The Renovate GitHub app performs the registry lookup and opens the pull
|
|
56
|
+
request. Boxdown does not add a repository-owned scheduled workflow or a
|
|
57
|
+
repository token. Enabling the app for the repository is an operational
|
|
58
|
+
prerequisite outside the committed code.
|
|
59
|
+
|
|
60
|
+
## Documentation
|
|
61
|
+
|
|
62
|
+
The packaged devcontainer README will describe the tag-plus-index-digest
|
|
63
|
+
reference, its cross-platform reproducibility property, and Renovate's role in
|
|
64
|
+
refreshing it through auditable pull requests. Existing documentation about
|
|
65
|
+
the Node 24 trixie slim base and digest-pinned Features remains accurate.
|
|
66
|
+
|
|
67
|
+
## Test Strategy
|
|
68
|
+
|
|
69
|
+
A focused repository test will read the packaged devcontainer source and
|
|
70
|
+
assert that:
|
|
71
|
+
|
|
72
|
+
- its base image is exactly the intended `node:24-trixie-slim` tag followed by
|
|
73
|
+
a 64-hex-character SHA-256 digest;
|
|
74
|
+
- the Renovate configuration enables only the devcontainer manager;
|
|
75
|
+
- the additional manager file pattern covers the packaged template;
|
|
76
|
+
- Feature dependencies are disabled; and
|
|
77
|
+
- the Node image rule enables digest pinning on the monthly schedule.
|
|
78
|
+
|
|
79
|
+
The test will be added before the production configuration changes and must be
|
|
80
|
+
observed failing for the missing digest and Renovate configuration. After the
|
|
81
|
+
change, verification will run the focused test, full test suite, lint, build,
|
|
82
|
+
JSON and JSON-with-comments parsing, registry inspection of the pinned index,
|
|
83
|
+
and `git diff --check`.
|
|
84
|
+
|
|
85
|
+
## Alternatives Considered
|
|
86
|
+
|
|
87
|
+
### Dependabot through a Dockerfile
|
|
88
|
+
|
|
89
|
+
Moving the base reference into a Dockerfile would let Dependabot's Docker
|
|
90
|
+
ecosystem update it, but it would change the devcontainer architecture solely
|
|
91
|
+
to accommodate the updater. GitHub's `devcontainers` ecosystem updates
|
|
92
|
+
Features rather than the `image` field, so it cannot directly maintain the
|
|
93
|
+
current template.
|
|
94
|
+
|
|
95
|
+
### Repository-owned scheduled workflow
|
|
96
|
+
|
|
97
|
+
A custom workflow could resolve the tag, edit the digest, and open pull
|
|
98
|
+
requests. It would duplicate registry, scheduling, authentication, and pull
|
|
99
|
+
request behavior already provided by Renovate and would add workflow and token
|
|
100
|
+
maintenance.
|
|
101
|
+
|
|
102
|
+
## Decision Record
|
|
103
|
+
|
|
104
|
+
Use Renovate's native devcontainer support and retain the human-readable
|
|
105
|
+
`node:24-trixie-slim` tag. Pin the verified multi-platform index digest and
|
|
106
|
+
scope automation to only that image in the packaged template. This yields
|
|
107
|
+
immutable rebuild inputs without broadening Boxdown's dependency automation
|
|
108
|
+
or changing its generated-devcontainer architecture.
|