@jgamaraalv/ts-dev-kit 3.0.0 → 3.1.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.
@@ -5,14 +5,14 @@
5
5
  "url": "https://github.com/jgamaraalv"
6
6
  },
7
7
  "metadata": {
8
- "description": "15 agents and 21 skills for TypeScript fullstack development with Claude Code"
8
+ "description": "15 agents and 22 skills for TypeScript fullstack development with Claude Code"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "ts-dev-kit",
13
13
  "source": "./",
14
- "description": "15 specialized agents and 21 skills for TypeScript fullstack development",
15
- "version": "3.0.0",
14
+ "description": "15 specialized agents and 22 skills for TypeScript fullstack development",
15
+ "version": "3.1.0",
16
16
  "author": {
17
17
  "name": "jgamaraalv"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ts-dev-kit",
3
- "version": "3.0.0",
4
- "description": "15 specialized agents and 21 skills for TypeScript fullstack development with Fastify, Next.js, PostgreSQL, Redis, and more.",
3
+ "version": "3.1.0",
4
+ "description": "15 specialized agents and 22 skills for TypeScript fullstack development with Fastify, Next.js, PostgreSQL, Redis, and more.",
5
5
  "author": {
6
6
  "name": "jgamaraalv",
7
7
  "url": "https://github.com/jgamaraalv"
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.1.0] - 2026-02-27
9
+
10
+ ### Added
11
+
12
+ - `/yolo` skill: devcontainer setup and launch workflow for running Claude Code with `--dangerously-skip-permissions` in a secure, sandboxed environment — detects existing devcontainer, checks Docker status, scaffolds the Claude Code reference `.devcontainer/` (Dockerfile, firewall, config), starts Docker if needed, and opens VS Code to launch the container
13
+ - Dynamic context injection to `/yolo`: project root, devcontainer presence, Docker daemon status, Docker and VS Code installation are pre-injected before Claude receives the prompt
14
+
15
+ ## [3.0.1] - 2026-02-27
16
+
17
+ ### Fixed
18
+
19
+ - Normalize `repository.url` in `package.json` to `git+https://` format as required by npm
20
+
8
21
  ## [3.0.0] - 2026-02-27
9
22
 
10
23
  ### Added
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ts-dev-kit
2
2
 
3
- > 15 specialized agents + 21 curated skills for TypeScript fullstack development
3
+ > 15 specialized agents + 22 curated skills for TypeScript fullstack development
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@jgamaraalv/ts-dev-kit)](https://www.npmjs.com/package/@jgamaraalv/ts-dev-kit)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
@@ -30,7 +30,7 @@
30
30
  | typescript-pro | Generics, type inference, conditional types |
31
31
  | ux-optimizer | User flows, form UX, friction reduction |
32
32
 
33
- ### Skills (21)
33
+ ### Skills (22)
34
34
 
35
35
  | Skill | Slug | Domain |
36
36
  | ---------------------- | ------------------------- | ------------------------------------------------- |
@@ -55,6 +55,7 @@
55
55
  | TanStack Query | `/tanstack-query` | React Query v5, caching, SSR/hydration |
56
56
  | TypeScript Conventions | `/typescript-conventions` | Strict config, patterns, best practices |
57
57
  | UI/UX Guidelines | `/ui-ux-guidelines` | Accessibility, layout, forms |
58
+ | Yolo | `/yolo` | Devcontainer setup for autonomous Claude Code |
58
59
 
59
60
  ---
60
61
 
@@ -206,6 +207,7 @@ Several skills use the `!`command`` syntax to pre-inject live data before Claude
206
207
  | `/conventional-commits` | Staged diff summary, full staged diff, last 8 commit messages |
207
208
  | `/debug` | Last 10 git commits, working tree status |
208
209
  | `/codebase-adapter` | Working directory, lockfile, installed agents, MCP servers, `package.json` |
210
+ | `/yolo` | Project root, devcontainer presence, Docker status, VS Code installation |
209
211
 
210
212
  ---
211
213
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@jgamaraalv/ts-dev-kit",
3
- "version": "3.0.0",
4
- "description": "Claude Code plugin: 15 agents + 21 skills for TypeScript fullstack development",
3
+ "version": "3.1.0",
4
+ "description": "Claude Code plugin: 15 agents + 22 skills for TypeScript fullstack development",
5
5
  "author": "jgamaraalv",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/jgamaraalv/ts-dev-kit.git"
9
+ "url": "git+https://github.com/jgamaraalv/ts-dev-kit.git"
10
10
  },
11
11
  "keywords": [
12
12
  "claude-code",
@@ -22,6 +22,10 @@ allowed-tools: Bash(git *)
22
22
  - "Debug the notification queue — jobs are stuck"
23
23
  </trigger_examples>
24
24
 
25
+ <task>
26
+ $ARGUMENTS
27
+ </task>
28
+
25
29
  <workflow>
26
30
  Follow each phase in order. Each one feeds the next.
27
31
 
@@ -233,7 +237,3 @@ When complete, produce a debug report using the template in [template.md](templa
233
237
 
234
238
  Do not add explanations, caveats, or follow-up suggestions unless the user asks.
235
239
  </output>
236
-
237
- <task>
238
- $ARGUMENTS
239
- </task>
@@ -0,0 +1,319 @@
1
+ ---
2
+ name: yolo
3
+ description: "Sets up and launches a Claude Code devcontainer with --dangerously-skip-permissions for fully autonomous, unattended operation inside a sandboxed environment. Use when: (1) user wants to run Claude Code without permission prompts, (2) setting up a secure development container for autonomous coding, (3) user says 'yolo', 'yolo mode', 'run without permissions', 'autonomous mode', 'skip permissions safely', or 'devcontainer setup'."
4
+ argument-hint: "[optional: path to project root — defaults to current directory]"
5
+ allowed-tools: Bash(docker *), Bash(ls *), Bash(cat *), Bash(cp *), Bash(mkdir *), Bash(which *), Bash(open *), Bash(code *), Bash(command *)
6
+ ---
7
+
8
+ <live_context>
9
+ **Project root:**
10
+ !`pwd`
11
+
12
+ **Devcontainer present:**
13
+ !`ls -la .devcontainer/devcontainer.json 2>/dev/null && echo "YES" || echo "NO"`
14
+
15
+ **Docker daemon status:**
16
+ !`docker info --format '{{.ServerVersion}}' 2>/dev/null && echo "RUNNING" || echo "NOT RUNNING"`
17
+
18
+ **Docker Desktop installed:**
19
+ !`command -v docker 2>/dev/null || echo "NOT FOUND"`
20
+
21
+ **VS Code installed:**
22
+ !`command -v code 2>/dev/null || echo "NOT FOUND"`
23
+ </live_context>
24
+
25
+ <trigger_examples>
26
+ - "yolo"
27
+ - "Enter yolo mode"
28
+ - "Set up a devcontainer so Claude can run autonomously"
29
+ - "I want to run Claude Code without permission prompts"
30
+ - "Skip permissions safely with a devcontainer"
31
+ - "Set up autonomous mode"
32
+ </trigger_examples>
33
+
34
+ <system>
35
+ You are a devcontainer setup specialist. Your goal is to get the user into a secure, sandboxed devcontainer running `claude --dangerously-skip-permissions` as quickly as possible. You follow a strict decision tree and never skip safety checks.
36
+
37
+ **IMPORTANT SECURITY NOTE:** While the devcontainer provides substantial protections (network firewall, isolation), it is NOT immune to all attacks. Only use devcontainers with **trusted repositories**. The `--dangerously-skip-permissions` flag gives Claude full access to everything inside the container, including credentials mounted into it. Always inform the user of this trade-off.
38
+ </system>
39
+
40
+ <task>
41
+ $ARGUMENTS
42
+ </task>
43
+
44
+ <workflow>
45
+ Follow this decision tree strictly. Each phase gates the next.
46
+
47
+ ```
48
+ START
49
+
50
+ ├─ Phase 1: Detect devcontainer
51
+ │ ├─ EXISTS → Phase 2
52
+ │ └─ MISSING → Phase 5 (install) → Phase 2
53
+
54
+ ├─ Phase 2: Check Docker
55
+ │ ├─ RUNNING → Phase 3
56
+ │ └─ NOT RUNNING → Phase 4 (start Docker) → Phase 3
57
+
58
+ ├─ Phase 3: Launch devcontainer
59
+ │ └─ claude --dangerously-skip-permissions
60
+
61
+ ├─ Phase 4: Start Docker
62
+ │ └─ Start Docker Desktop/daemon → Phase 3
63
+
64
+ └─ Phase 5: Install devcontainer
65
+ └─ Scaffold .devcontainer/ → Phase 2
66
+ ```
67
+
68
+ <phase_1_detect>
69
+
70
+ ## Phase 1 — Detect devcontainer
71
+
72
+ Check whether `.devcontainer/devcontainer.json` exists in the project root.
73
+
74
+ 1. Read the `<live_context>` above for the pre-injected detection result.
75
+ 2. If **YES** — announce to the user and proceed to Phase 2:
76
+
77
+ > **DEVCONTAINER DETECTED** — `.devcontainer/` found. Checking Docker status...
78
+
79
+ 3. If **NO** — announce and proceed to Phase 5:
80
+
81
+ > **NO DEVCONTAINER FOUND** — I'll set one up using the Claude Code reference implementation.
82
+
83
+ </phase_1_detect>
84
+
85
+ <phase_2_check_docker>
86
+
87
+ ## Phase 2 — Check Docker
88
+
89
+ Verify the Docker daemon is running and accessible.
90
+
91
+ 1. Read the `<live_context>` above for the pre-injected Docker status.
92
+ 2. If `docker` command is **NOT FOUND**:
93
+
94
+ > **DOCKER NOT INSTALLED** — Docker Desktop is required for devcontainers. Please install it from https://www.docker.com/products/docker-desktop/ and re-run `/yolo`.
95
+
96
+ Stop here. Do not proceed.
97
+
98
+ 3. If Docker is **RUNNING** — proceed to Phase 3:
99
+
100
+ > **DOCKER RUNNING** (version X.X.X) — Ready to launch devcontainer.
101
+
102
+ 4. If Docker is **NOT RUNNING** — proceed to Phase 4.
103
+
104
+ </phase_2_check_docker>
105
+
106
+ <phase_3_launch>
107
+
108
+ ## Phase 3 — Launch devcontainer
109
+
110
+ Open the project in VS Code with the devcontainer.
111
+
112
+ **Prerequisites check:**
113
+
114
+ 1. Verify VS Code is installed (`command -v code`).
115
+ 2. If VS Code is not installed, inform the user:
116
+
117
+ > **VS Code is required** for devcontainer support. Install it from https://code.visualstudio.com/ and the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
118
+
119
+ **Launch sequence:**
120
+
121
+ 1. Open the project in VS Code:
122
+
123
+ ```bash
124
+ code .
125
+ ```
126
+
127
+ 2. Instruct the user:
128
+
129
+ > **LAUNCHING DEVCONTAINER**
130
+ >
131
+ > VS Code is opening your project. Follow these steps:
132
+ >
133
+ > 1. VS Code should prompt **"Reopen in Container"** — click it.
134
+ > - If no prompt appears: open Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`) → type **"Dev Containers: Reopen in Container"** → select it.
135
+ > 2. Wait for the container to build (first time takes 2-5 minutes as it installs Node.js 20, Claude Code, ZSH, and configures the firewall).
136
+ > 3. Once inside the container, open a terminal and run:
137
+ > ```
138
+ > claude --dangerously-skip-permissions
139
+ > ```
140
+ >
141
+ > **What's happening under the hood:**
142
+ >
143
+ > - The container runs Node.js 20 with Claude Code pre-installed
144
+ > - A firewall restricts outbound traffic to: npm registry, GitHub, Claude API, Sentry, and VS Code marketplace only
145
+ > - All other internet access is blocked (verified on startup)
146
+ > - Your project is mounted at `/workspace`
147
+ > - Shell history and Claude config persist between container restarts
148
+ >
149
+ > **Security trade-off:** `--dangerously-skip-permissions` means Claude can execute ANY command inside the container without asking. The firewall and container isolation are your safety net. Only use this with trusted code.
150
+
151
+ </phase_3_launch>
152
+
153
+ <phase_4_start_docker>
154
+
155
+ ## Phase 4 — Start Docker
156
+
157
+ The Docker daemon is installed but not running.
158
+
159
+ **macOS:**
160
+
161
+ ```bash
162
+ open -a Docker
163
+ ```
164
+
165
+ **Linux:**
166
+
167
+ ```bash
168
+ sudo systemctl start docker
169
+ ```
170
+
171
+ After issuing the start command:
172
+
173
+ 1. Wait for Docker to become ready (poll up to 30 seconds):
174
+
175
+ ```bash
176
+ for i in $(seq 1 15); do
177
+ docker info --format '{{.ServerVersion}}' 2>/dev/null && break
178
+ sleep 2
179
+ done
180
+ ```
181
+
182
+ 2. Re-verify Docker is running:
183
+
184
+ ```bash
185
+ docker info --format '{{.ServerVersion}}' 2>/dev/null
186
+ ```
187
+
188
+ 3. If Docker started successfully — proceed to Phase 3:
189
+
190
+ > **DOCKER STARTED** — Docker daemon is now running. Launching devcontainer...
191
+
192
+ 4. If Docker failed to start after 30 seconds:
193
+
194
+ > **DOCKER FAILED TO START** — Please start Docker Desktop manually and re-run `/yolo`.
195
+
196
+ Stop here.
197
+
198
+ </phase_4_start_docker>
199
+
200
+ <phase_5_install_devcontainer>
201
+
202
+ ## Phase 5 — Install devcontainer
203
+
204
+ Scaffold the `.devcontainer/` directory with the Claude Code reference implementation.
205
+
206
+ ### Step 1 — Create directory
207
+
208
+ ```bash
209
+ mkdir -p .devcontainer
210
+ ```
211
+
212
+ ### Step 2 — Create `devcontainer.json`
213
+
214
+ Write the file using the reference config. See [references/devcontainer-json.md](references/devcontainer-json.md) for the full file content.
215
+
216
+ Key settings:
217
+
218
+ - Name: `"Claude Code Sandbox"`
219
+ - Dockerfile build with Node.js 20
220
+ - `NET_ADMIN` + `NET_RAW` capabilities (required for firewall)
221
+ - Claude Code VS Code extension pre-installed
222
+ - Volume mounts for bash history and Claude config persistence
223
+ - Firewall initialization via `postStartCommand`
224
+ - Workspace mounted at `/workspace`
225
+
226
+ ### Step 3 — Create `Dockerfile`
227
+
228
+ Write the file using the reference Dockerfile. See [references/dockerfile.md](references/dockerfile.md) for the full file content.
229
+
230
+ Key features:
231
+
232
+ - Base: `node:20`
233
+ - Installs: git, ZSH, fzf, gh CLI, iptables/ipset, nano, vim
234
+ - Installs `@anthropic-ai/claude-code` globally
235
+ - Sets up non-root `node` user
236
+ - Configures ZSH with Powerlevel10k theme
237
+ - Copies and configures firewall script with sudo access
238
+
239
+ ### Step 4 — Create `init-firewall.sh`
240
+
241
+ Write the file using the reference firewall script. See [references/init-firewall.sh.md](references/init-firewall.sh.md) for the full file content.
242
+
243
+ Key security features:
244
+
245
+ - Default-deny policy (DROP all INPUT, OUTPUT, FORWARD)
246
+ - Whitelisted outbound only: npm registry, GitHub (dynamic IP fetch), Claude API, Sentry, StatsIG, VS Code marketplace
247
+ - DNS and SSH allowed
248
+ - Localhost and host network allowed
249
+ - Startup verification: confirms `example.com` is blocked and `api.github.com` is reachable
250
+ - Docker DNS rules preserved
251
+
252
+ ### Step 5 — Add `.devcontainer` to `.gitignore` (optional)
253
+
254
+ Check if `.gitignore` exists and whether `.devcontainer/` is already listed. If not, ask the user:
255
+
256
+ > **Should I add `.devcontainer/` to `.gitignore`?**
257
+ >
258
+ > - **Yes** — keep devcontainer config local to your machine
259
+ > - **No** — commit it so the whole team can use it (recommended for teams)
260
+
261
+ ### Step 6 — Verify installation
262
+
263
+ ```bash
264
+ ls -la .devcontainer/
265
+ cat .devcontainer/devcontainer.json | head -5
266
+ ```
267
+
268
+ Announce completion:
269
+
270
+ > **DEVCONTAINER INSTALLED** — `.devcontainer/` is ready with Dockerfile, firewall script, and configuration. Proceeding to check Docker...
271
+
272
+ Return to Phase 2.
273
+
274
+ </phase_5_install_devcontainer>
275
+
276
+ </workflow>
277
+
278
+ <customization_notes>
279
+
280
+ ## Customizing the devcontainer
281
+
282
+ After installation, the user may want to customize:
283
+
284
+ **Adding allowed domains to the firewall:**
285
+ Edit `.devcontainer/init-firewall.sh` and add domains to the `for domain in` loop:
286
+
287
+ ```bash
288
+ for domain in \
289
+ "registry.npmjs.org" \
290
+ "api.anthropic.com" \
291
+ "your-custom-domain.com" \ # <-- add here
292
+ ...
293
+ ```
294
+
295
+ **Adding VS Code extensions:**
296
+ Edit `.devcontainer/devcontainer.json` → `customizations.vscode.extensions[]`:
297
+
298
+ ```json
299
+ "extensions": [
300
+ "anthropic.claude-code",
301
+ "your-extension.id"
302
+ ]
303
+ ```
304
+
305
+ **Changing Node.js version:**
306
+ Edit `.devcontainer/Dockerfile` — change the `FROM` line:
307
+
308
+ ```dockerfile
309
+ FROM node:22
310
+ ```
311
+
312
+ **Adding project-specific dependencies:**
313
+ Add `RUN` commands to the Dockerfile before the `USER node` line:
314
+
315
+ ```dockerfile
316
+ RUN apt-get update && apt-get install -y your-package
317
+ ```
318
+
319
+ </customization_notes>
@@ -0,0 +1,74 @@
1
+ # devcontainer.json — Reference
2
+
3
+ Write this file to `.devcontainer/devcontainer.json`:
4
+
5
+ ```json
6
+ {
7
+ "name": "Claude Code Sandbox",
8
+ "build": {
9
+ "dockerfile": "Dockerfile",
10
+ "args": {
11
+ "TZ": "${localEnv:TZ:America/Los_Angeles}",
12
+ "CLAUDE_CODE_VERSION": "latest",
13
+ "GIT_DELTA_VERSION": "0.18.2",
14
+ "ZSH_IN_DOCKER_VERSION": "1.2.0"
15
+ }
16
+ },
17
+ "runArgs": [
18
+ "--cap-add=NET_ADMIN",
19
+ "--cap-add=NET_RAW"
20
+ ],
21
+ "customizations": {
22
+ "vscode": {
23
+ "extensions": [
24
+ "anthropic.claude-code",
25
+ "dbaeumer.vscode-eslint",
26
+ "esbenp.prettier-vscode",
27
+ "eamodio.gitlens"
28
+ ],
29
+ "settings": {
30
+ "editor.formatOnSave": true,
31
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
32
+ "editor.codeActionsOnSave": {
33
+ "source.fixAll.eslint": "explicit"
34
+ },
35
+ "terminal.integrated.defaultProfile.linux": "zsh",
36
+ "terminal.integrated.profiles.linux": {
37
+ "bash": {
38
+ "path": "bash",
39
+ "icon": "terminal-bash"
40
+ },
41
+ "zsh": {
42
+ "path": "zsh"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ },
48
+ "remoteUser": "node",
49
+ "mounts": [
50
+ "source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
51
+ "source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
52
+ ],
53
+ "containerEnv": {
54
+ "NODE_OPTIONS": "--max-old-space-size=4096",
55
+ "CLAUDE_CONFIG_DIR": "/home/node/.claude",
56
+ "POWERLEVEL9K_DISABLE_GITSTATUS": "true"
57
+ },
58
+ "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
59
+ "workspaceFolder": "/workspace",
60
+ "postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
61
+ "waitFor": "postStartCommand"
62
+ }
63
+ ```
64
+
65
+ ## Key settings explained
66
+
67
+ | Setting | Purpose |
68
+ |---------|---------|
69
+ | `runArgs: --cap-add=NET_ADMIN,NET_RAW` | Required for the firewall script to configure iptables |
70
+ | `remoteUser: node` | Runs as non-root for security |
71
+ | `mounts` (bashhistory, config) | Persists shell history and Claude config between container restarts |
72
+ | `postStartCommand` | Initializes the firewall on every container start |
73
+ | `waitFor: postStartCommand` | Ensures firewall is active before VS Code connects |
74
+ | `workspaceMount` | Binds the local project into `/workspace` with delegated consistency for performance |
@@ -0,0 +1,108 @@
1
+ # Dockerfile — Reference
2
+
3
+ Write this file to `.devcontainer/Dockerfile`:
4
+
5
+ ```dockerfile
6
+ FROM node:20
7
+
8
+ ARG TZ
9
+ ENV TZ="$TZ"
10
+
11
+ ARG CLAUDE_CODE_VERSION=latest
12
+
13
+ # Install basic development tools and iptables/ipset
14
+ RUN apt-get update && apt-get install -y --no-install-recommends \
15
+ less \
16
+ git \
17
+ procps \
18
+ sudo \
19
+ fzf \
20
+ zsh \
21
+ man-db \
22
+ unzip \
23
+ gnupg2 \
24
+ gh \
25
+ iptables \
26
+ ipset \
27
+ iproute2 \
28
+ dnsutils \
29
+ aggregate \
30
+ jq \
31
+ nano \
32
+ vim \
33
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
34
+
35
+ # Ensure default node user has access to /usr/local/share
36
+ RUN mkdir -p /usr/local/share/npm-global && \
37
+ chown -R node:node /usr/local/share
38
+
39
+ ARG USERNAME=node
40
+
41
+ # Persist bash history.
42
+ RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
43
+ && mkdir /commandhistory \
44
+ && touch /commandhistory/.bash_history \
45
+ && chown -R $USERNAME /commandhistory
46
+
47
+ # Set `DEVCONTAINER` environment variable to help with orientation
48
+ ENV DEVCONTAINER=true
49
+
50
+ # Create workspace and config directories and set permissions
51
+ RUN mkdir -p /workspace /home/node/.claude && \
52
+ chown -R node:node /workspace /home/node/.claude
53
+
54
+ WORKDIR /workspace
55
+
56
+ ARG GIT_DELTA_VERSION=0.18.2
57
+ RUN ARCH=$(dpkg --print-architecture) && \
58
+ wget "https://github.com/dandavison/delta/releases/download/${GIT_DELTA_VERSION}/git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
59
+ sudo dpkg -i "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb" && \
60
+ rm "git-delta_${GIT_DELTA_VERSION}_${ARCH}.deb"
61
+
62
+ # Set up non-root user
63
+ USER node
64
+
65
+ # Install global packages
66
+ ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
67
+ ENV PATH=$PATH:/usr/local/share/npm-global/bin
68
+
69
+ # Set the default shell to zsh rather than sh
70
+ ENV SHELL=/bin/zsh
71
+
72
+ # Set the default editor and visual
73
+ ENV EDITOR=nano
74
+ ENV VISUAL=nano
75
+
76
+ # Default powerline10k theme
77
+ ARG ZSH_IN_DOCKER_VERSION=1.2.0
78
+ RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v${ZSH_IN_DOCKER_VERSION}/zsh-in-docker.sh)" -- \
79
+ -p git \
80
+ -p fzf \
81
+ -a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
82
+ -a "source /usr/share/doc/fzf/examples/completion.zsh" \
83
+ -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
84
+ -x
85
+
86
+ # Install Claude
87
+ RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
88
+
89
+ # Copy and set up firewall script
90
+ COPY init-firewall.sh /usr/local/bin/
91
+ USER root
92
+ RUN chmod +x /usr/local/bin/init-firewall.sh && \
93
+ echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \
94
+ chmod 0440 /etc/sudoers.d/node-firewall
95
+ USER node
96
+ ```
97
+
98
+ ## What this Dockerfile provides
99
+
100
+ | Layer | Details |
101
+ |-------|---------|
102
+ | **Base image** | Node.js 20 (LTS) |
103
+ | **Dev tools** | git, ZSH, fzf, gh CLI, jq, nano, vim, delta (git diff) |
104
+ | **Network tools** | iptables, ipset, iproute2, dnsutils, aggregate (for firewall) |
105
+ | **User** | Non-root `node` user with sudo for firewall script only |
106
+ | **Shell** | ZSH with Powerlevel10k theme, fzf key bindings |
107
+ | **Claude Code** | Installed globally via npm (version configurable via build arg) |
108
+ | **Firewall** | `init-firewall.sh` copied and configured with passwordless sudo |
@@ -0,0 +1,167 @@
1
+ # init-firewall.sh — Reference
2
+
3
+ Write this file to `.devcontainer/init-firewall.sh`:
4
+
5
+ ```bash
6
+ #!/bin/bash
7
+ set -euo pipefail # Exit on error, undefined vars, and pipeline failures
8
+ IFS=$'\n\t' # Stricter word splitting
9
+
10
+ # 1. Extract Docker DNS info BEFORE any flushing
11
+ DOCKER_DNS_RULES=$(iptables-save -t nat | grep "127\.0\.0\.11" || true)
12
+
13
+ # Flush existing rules and delete existing ipsets
14
+ iptables -F
15
+ iptables -X
16
+ iptables -t nat -F
17
+ iptables -t nat -X
18
+ iptables -t mangle -F
19
+ iptables -t mangle -X
20
+ ipset destroy allowed-domains 2>/dev/null || true
21
+
22
+ # 2. Selectively restore ONLY internal Docker DNS resolution
23
+ if [ -n "$DOCKER_DNS_RULES" ]; then
24
+ echo "Restoring Docker DNS rules..."
25
+ iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
26
+ iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
27
+ echo "$DOCKER_DNS_RULES" | xargs -L 1 iptables -t nat
28
+ else
29
+ echo "No Docker DNS rules to restore"
30
+ fi
31
+
32
+ # First allow DNS and localhost before any restrictions
33
+ # Allow outbound DNS
34
+ iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
35
+ # Allow inbound DNS responses
36
+ iptables -A INPUT -p udp --sport 53 -j ACCEPT
37
+ # Allow outbound SSH
38
+ iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
39
+ # Allow inbound SSH responses
40
+ iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
41
+ # Allow localhost
42
+ iptables -A INPUT -i lo -j ACCEPT
43
+ iptables -A OUTPUT -o lo -j ACCEPT
44
+
45
+ # Create ipset with CIDR support
46
+ ipset create allowed-domains hash:net
47
+
48
+ # Fetch GitHub meta information and aggregate + add their IP ranges
49
+ echo "Fetching GitHub IP ranges..."
50
+ gh_ranges=$(curl -s https://api.github.com/meta)
51
+ if [ -z "$gh_ranges" ]; then
52
+ echo "ERROR: Failed to fetch GitHub IP ranges"
53
+ exit 1
54
+ fi
55
+
56
+ if ! echo "$gh_ranges" | jq -e '.web and .api and .git' >/dev/null; then
57
+ echo "ERROR: GitHub API response missing required fields"
58
+ exit 1
59
+ fi
60
+
61
+ echo "Processing GitHub IPs..."
62
+ while read -r cidr; do
63
+ if [[ ! "$cidr" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
64
+ echo "ERROR: Invalid CIDR range from GitHub meta: $cidr"
65
+ exit 1
66
+ fi
67
+ echo "Adding GitHub range $cidr"
68
+ ipset add allowed-domains "$cidr"
69
+ done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | aggregate -q)
70
+
71
+ # Resolve and add other allowed domains
72
+ for domain in \
73
+ "registry.npmjs.org" \
74
+ "api.anthropic.com" \
75
+ "sentry.io" \
76
+ "statsig.anthropic.com" \
77
+ "statsig.com" \
78
+ "marketplace.visualstudio.com" \
79
+ "vscode.blob.core.windows.net" \
80
+ "update.code.visualstudio.com"; do
81
+ echo "Resolving $domain..."
82
+ ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}')
83
+ if [ -z "$ips" ]; then
84
+ echo "ERROR: Failed to resolve $domain"
85
+ exit 1
86
+ fi
87
+
88
+ while read -r ip; do
89
+ if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
90
+ echo "ERROR: Invalid IP from DNS for $domain: $ip"
91
+ exit 1
92
+ fi
93
+ echo "Adding $ip for $domain"
94
+ ipset add allowed-domains "$ip"
95
+ done < <(echo "$ips")
96
+ done
97
+
98
+ # Get host IP from default route
99
+ HOST_IP=$(ip route | grep default | cut -d" " -f3)
100
+ if [ -z "$HOST_IP" ]; then
101
+ echo "ERROR: Failed to detect host IP"
102
+ exit 1
103
+ fi
104
+
105
+ HOST_NETWORK=$(echo "$HOST_IP" | sed "s/\.[0-9]*$/.0\/24/")
106
+ echo "Host network detected as: $HOST_NETWORK"
107
+
108
+ # Set up remaining iptables rules
109
+ iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT
110
+ iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT
111
+
112
+ # Set default policies to DROP first
113
+ iptables -P INPUT DROP
114
+ iptables -P FORWARD DROP
115
+ iptables -P OUTPUT DROP
116
+
117
+ # First allow established connections for already approved traffic
118
+ iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
119
+ iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
120
+
121
+ # Then allow only specific outbound traffic to allowed domains
122
+ iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT
123
+
124
+ # Explicitly REJECT all other outbound traffic for immediate feedback
125
+ iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
126
+
127
+ echo "Firewall configuration complete"
128
+ echo "Verifying firewall rules..."
129
+ if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
130
+ echo "ERROR: Firewall verification failed - was able to reach https://example.com"
131
+ exit 1
132
+ else
133
+ echo "Firewall verification passed - unable to reach https://example.com as expected"
134
+ fi
135
+
136
+ # Verify GitHub API access
137
+ if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
138
+ echo "ERROR: Firewall verification failed - unable to reach https://api.github.com"
139
+ exit 1
140
+ else
141
+ echo "Firewall verification passed - able to reach https://api.github.com as expected"
142
+ fi
143
+ ```
144
+
145
+ ## Firewall rules summary
146
+
147
+ | Rule | Direction | Purpose |
148
+ |------|-----------|---------|
149
+ | DNS (UDP 53) | Outbound | Domain name resolution |
150
+ | SSH (TCP 22) | Outbound | Git over SSH |
151
+ | Localhost | Both | Container-internal communication |
152
+ | Host network | Both | Docker host ↔ container communication |
153
+ | GitHub IPs | Outbound | Git operations, GitHub API (dynamically fetched from api.github.com/meta) |
154
+ | npm registry | Outbound | Package installation |
155
+ | api.anthropic.com | Outbound | Claude API calls |
156
+ | sentry.io | Outbound | Error reporting |
157
+ | statsig.anthropic.com | Outbound | Feature flags |
158
+ | VS Code marketplace | Outbound | Extension downloads |
159
+ | **Everything else** | **BLOCKED** | Default-deny with REJECT |
160
+
161
+ ## Verification on startup
162
+
163
+ The script verifies the firewall by:
164
+ 1. Confirming `https://example.com` is **blocked** (should fail)
165
+ 2. Confirming `https://api.github.com` is **reachable** (should succeed)
166
+
167
+ If either check fails, the script exits with an error and the container will not start.