Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .booth/tools/coding-booth.lock

This file was deleted.

8 changes: 7 additions & 1 deletion .github/workflows/release-binary-and-wrapper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,23 @@ jobs:
- name: Generate SHA256 checksums for all binaries
run: |
cd bin
echo "BIN"
ls -la
for binary in codingbooth-*; do
sha256sum "$binary" > "${binary}.sha256"
done
cd ..
echo "ROOT"
ls -la

# ---------------------------------------------------------
# Build example artifacts
# ---------------------------------------------------------
- name: Update booth wrappers
run: ./examples/update-booth.sh
run: |
cd ./examples
./update-booth.sh
cd ..

- name: Create example-list.txt
run: |
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ tests/basic/test*.sh.log
tests/basic/test--dockerfile
tests/dryrun/test--.env
tests/dryrun/test--config.sh
tests/dryrun/test--config.toml
**/.Dockerfile.generated

playground/

Expand Down Expand Up @@ -121,3 +123,5 @@ variants/base/_stage/

!/build/
/codingbooth
**/booth
!/booth
141 changes: 93 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ CodingBooth provides a command-line interface with the following structure:
| `--pull` | Force pull latest image |
| `--dind` | Enable Docker-in-Docker mode |
| `--keep-alive` | Keep container after exit |
| `--writable-booth` | Allow writing to `.booth/` inside the container (read-only by default) |
| `--silence-build` | Suppress build/startup output |
| `--dryrun` | Print docker commands without executing |
| `--verbose` | Enable debug output |
Expand Down Expand Up @@ -415,6 +416,8 @@ my-project/

> 💡 **Tip:** When both `Boothfile` and `Dockerfile` exist, Boothfile takes precedence. Use `--dockerfile` to force using the Dockerfile.

> 🔒 **Read-only by default:** The `.booth/` folder is mounted **read-only** inside the container to prevent accidental or malicious modifications to your configuration. Use `--writable-booth` if you need to edit `.booth/` files from inside the container (e.g., during development).

> ⚠️ **Note on `cmds`:** When you pass commands via CLI (`-- <cmd>`), they **override** the `cmds` in config.toml (they don't append).

---
Expand Down Expand Up @@ -453,17 +456,17 @@ env APP_ENV=production

### Boothfile Commands

| Command | Example | Compiles to |
|---------|---------|-------------|
| `run` | `run apt-get update` | `RUN apt-get update` |
| `setup` | `setup python 3.12` | `RUN python--setup.sh 3.12` |
| `install` | `install pip django` | `RUN pip--install.sh django` |
| `copy` | `copy ./config /opt` | `COPY ./config /opt` |
| `env` | `env DEBUG=true` | `ENV DEBUG=true` |
| `arg` | `arg VERSION=1.0` | `ARG VERSION=1.0` |
| `workdir` | `workdir /app` | `WORKDIR /app` |
| `expose` | `expose 8080` | `EXPOSE 8080` |
| `label` | `label maintainer="me"` | `LABEL maintainer="me"` |
| Command | Example | Compiles to |
|-----------|-------------------------|------------------------------|
| `run` | `run apt-get update` | `RUN apt-get update` |
| `setup` | `setup python 3.12` | `RUN python--setup.sh 3.12` |
| `install` | `install pip django` | `RUN pip--install.sh django` |
| `copy` | `copy ./config /opt` | `COPY ./config /opt` |
| `env` | `env DEBUG=true` | `ENV DEBUG=true` |
| `arg` | `arg VERSION=1.0` | `ARG VERSION=1.0` |
| `workdir` | `workdir /app` | `WORKDIR /app` |
| `expose` | `expose 8080` | `EXPOSE 8080` |
| `label` | `label maintainer="me"` | `LABEL maintainer="me"` |

### Multi-line Commands (Heredocs)

Expand Down Expand Up @@ -525,11 +528,11 @@ The compiler automatically adds a `COPY` to bring your script into the image.

### CLI Flags

| Flag | Description |
|------|-------------|
| `--boothfile <path>` | Use a specific Boothfile |
| `--emit-dockerfile` | Print generated Dockerfile without building |
| `--strict` | Treat warnings as errors |
| Flag | Description |
|----------------------|---------------------------------------------|
| `--boothfile <path>` | Use a specific Boothfile |
| `--emit-dockerfile` | Print generated Dockerfile without building |
| `--strict` | Treat warnings as errors |

### File Precedence

Expand Down Expand Up @@ -575,13 +578,14 @@ env DJANGO_SETTINGS_MODULE=myproject.settings

CodingBooth is designed for development environments, not production workloads. Key security aspects:

| Aspect | Behavior |
|---------------------|------------------------------------------------------------------------|
| **User privileges** | Processes run as unprivileged `coder` user, not root |
| **Sudo access** | `coder` has passwordless sudo (for installing packages) |
| **File ownership** | Files match your host UID/GID — no root-owned files |
| **Network** | Full network access by default; use Network Whitelist for restrictions |
| **DinD mode** | Requires `--privileged` flag (elevated permissions) |
| Aspect | Behavior |
|----------------------|---------------------------------------------------------------------------|
| **User privileges** | Processes run as unprivileged `coder` user, not root |
| **Sudo access** | `coder` has passwordless sudo (for installing packages) |
| **File ownership** | Files match your host UID/GID — no root-owned files |
| **`.booth/` config** | Read-only inside the container by default (`--writable-booth` to opt out) |
| **Network** | Full network access by default; use Network Whitelist for restrictions |
| **DinD mode** | Requires `--privileged` flag (elevated permissions) |

**Best practices:**
- Don't run untrusted code in CodingBooth containers
Expand Down Expand Up @@ -619,41 +623,41 @@ The `booth` wrapper script is **location-based**: it operates relative to its ow


```
host # your machine
├── ~/.cache/codingbooth/ # shared binary cache
host # your machine
├── ~/.cache/codingbooth/ # shared binary cache
| └── versions/
| └── 0.13.0/ # version-specific binaries
| └── 0.13.0/ # version-specific binaries
| ├── codingbooth.sha256
| └── codingbooth-* # platform binaries
├── project/ # your project folder on the host
| ├── booth # booth wrapper script
| ├── .booth # booth internal folder
| └── codingbooth-* # platform binaries
├── project/ # your project folder on the host
| ├── booth # booth wrapper script
| ├── .booth # booth internal folder
| | └── tools/
| | └── codingbooth.lock # version reference
| ├── ... # other project files
| | └── codingbooth.lock # version reference
| ├── ... # other project files
...

container
├── home/
| ├── coder/
| | ├── code/ # your project folder inside the container
| | | ├── booth # booth wrapper script
| | | ├── .booth # booth internal folder
| | ├── code/ # your project folder inside the container
| | | ├── booth # booth wrapper script
| | | ├── .booth # booth internal folder
| | | | └── tools/
| | | | └── codingbooth.lock # version reference
| | ├── ... # other project files
| ├── ... # other home files
| | ├── ... # other project files
| ├── ... # other home files
├── etc/
| ├── profile.d/ # profile script folder
| ├── profile.d/ # profile script folder
├── opt/
| ├── codingbooth/
| | ├── setups/ # setup script folder
| | | ├── ... # setup scripts
| | ├── setups/ # setup script folder
| | | ├── ... # setup scripts
├── usr/
| ├── local/
| | ├── bin/ # program file folder
| | ├── bin/ # program file folder
| ├── share/
| | ├── startup.d/ # startup script folder
| | ├── startup.d/ # startup script folder
...
```

Expand Down Expand Up @@ -1204,10 +1208,55 @@ This allows the booth to run Docker commands that execute inside the isolated Di
- The sidecar approach offers stronger isolation but can be slower and more complex to manage.

> 💡 **Tip:**
> See `examples/dind-example` for basic DinD usage, or `examples/kind-example` for running Kubernetes with KinD inside the booth.
> See `examples/workspaces/dind-example` for basic DinD usage, `examples/workspaces/kind-example` for KinD, and `examples/workspaces/firewall-example` for `--sandboxed` egress enforcement.
> **Security note (2026-02-06):** `--sandboxed` with `--dind` is **not supported**. The DinD sidecar can bypass the egress firewall by running a privileged container in the shared network namespace. Use `--sandboxed` **without** `--dind` until further research.


### 12. Egress Sandbox (`--sandboxed`)

CodingBooth includes an **egress sandbox** that restricts outbound traffic to an allowlist.
When enabled, traffic must pass through an Envoy proxy sidecar and is enforced with firewall rules.

**How It Works**
- Envoy forward proxy enforces a domain allowlist.
- iptables rules force all HTTP/HTTPS traffic through the proxy.
- Default policy is **deny**; only allowlisted domains are reachable.

**Configuration**
- Enable with:
```bash
./booth --sandboxed
```
- Policy is provided by **one** of:
- `.booth/sandbox/allowlist.txt` (simple allowlist), or
- `.booth/sandbox/envoy.yaml` (advanced/custom).
- These are **mutually exclusive**; if both exist, startup fails with a clear error.
- Optional: add extra domains with `sandbox-allowlist` in `.booth/config.toml`:
```toml
sandbox-allowlist = [
"example.com",
"registry.npmjs.org"
]
```
This list is merged into the active allowlist (default or file-based). It cannot be used with `sandbox-policy-file`.

**Default Allowlist (embedded)**
- If `--sandboxed` is enabled and no policy files exist, CodingBooth materializes a default allowlist at:
- `.booth/sandbox/allowlist.txt`
- This default content is embedded in the CLI binary and is based on `docs/implementations/example-allowlist.txt`.

**Important Security Note (2026-02-06)**
- `--sandboxed` with `--dind` is **not supported** due to a known firewall bypass via privileged DinD containers.
- Use `--sandboxed` **without** `--dind` until further research.

**Quick Example**
```bash
mkdir -p .booth/sandbox
cp docs/implementations/example-allowlist.txt .booth/sandbox/allowlist.txt
./booth --sandboxed
```

### 12. Network Whitelist
### 13. Network Whitelist

CodingBooth includes a **network whitelist** feature that restricts container internet access to only approved domains. This is useful for:
- Security-conscious environments
Expand Down Expand Up @@ -1520,7 +1569,3 @@ Stay in touch or follow updates, insights, and development notes:

> 🙏 Every issue, idea, and pull request — big or small — helps make CodingBooth better for everyone.
> Thank you for being part of the community!




6 changes: 6 additions & 0 deletions cli/src/cmd/codingbooth/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ USAGE:
%s prune [--yes] (remove stopped booth containers)
%s example <subcommand> (manage examples)
%s emit-dockerfile [options] (compile Boothfile to Dockerfile)
%s print-default-allowlist.txt (print built-in egress allowlist)

BOOTSTRAP OPTIONS (CLI or defaults; evaluated before environmental variable and config file):
--code <path> Host code path to mount at /home/coder/code
Expand Down Expand Up @@ -81,7 +82,9 @@ RUNTIME OPTIONS:
CONTAINER MODE:
--daemon Run the booth container in the background
--dind Enable a Docker-in-Docker sidecar and set DOCKER_HOST
--sandboxed Enable egress sandbox defaults (proxy + enforcement setup)
--keep-alive Do not remove the container when stopped
--writable-booth Allow writing to .booth/ inside the container (read-only by default)

COMMANDS:
All arguments after '--' are executed *inside* the container instead of starting
Expand All @@ -102,6 +105,8 @@ NOTES:

- With --dind, a docker:dind sidecar runs on a private network and the main
container uses DOCKER_HOST=tcp://<sidecar>:2375.
- With --sandboxed, booth enables egress policy defaults. If --dind is also set,
the existing DinD sidecar network namespace is reused.

EXAMPLES:
# Prebuilt, foreground
Expand Down Expand Up @@ -139,5 +144,6 @@ EXAMPLES:
scriptName,
scriptName,
scriptName,
scriptName,
)
}
3 changes: 3 additions & 0 deletions cli/src/cmd/codingbooth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func main() {
case "emit-dockerfile":
emitDockerfile()
return
case "print-default-allowlist.txt":
printDefaultAllowlist()
return
default:
// If it starts with --, treat as run with options
if len(command) > 0 && command[0] == '-' {
Expand Down
15 changes: 15 additions & 0 deletions cli/src/cmd/codingbooth/print_default_allowlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2025-2026 : Nawa Manusitthipol
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

package main

import (
"fmt"

"github.com/nawaman/codingbooth/src/pkg/defaults"
)

func printDefaultAllowlist() {
fmt.Print(defaults.ExampleAllowlist)
}
22 changes: 22 additions & 0 deletions cli/src/pkg/appctx/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ type AppConfig struct {
Daemon bool `toml:"daemon,omitempty" envconfig:"CB_DAEMON" default:"false"`
Pull bool `toml:"pull,omitempty" envconfig:"CB_PULL" default:"false"`
Dind bool `toml:"dind,omitempty" envconfig:"CB_DIND" default:"false"`
Sandbox bool `toml:"sandboxed,omitempty" envconfig:"CB_SANDBOX" default:"false"`
WritableBooth bool `toml:"writable-booth,omitempty" envconfig:"CB_WRITABLE_BOOTH" default:"false"`
SandboxAllowlistFile string `toml:"sandbox-allowlist-file,omitempty" envconfig:"CB_SANDBOX_ALLOWLIST_FILE"`
SandboxPolicyFile string `toml:"sandbox-policy-file,omitempty" envconfig:"CB_SANDBOX_POLICY_FILE"`
SandboxAllowlist []string `toml:"sandbox-allowlist,omitempty" envconfig:"CB_SANDBOX_ALLOWLIST"`

// --------------------
// Image configuration
Expand Down Expand Up @@ -67,6 +72,11 @@ type AppConfig struct {
BuildArgs ilist.SemicolonStringList `toml:"build-args,omitempty" envconfig:"CB_BUILD_ARGS"`
RunArgs ilist.SemicolonStringList `toml:"run-args,omitempty" envconfig:"CB_RUN_ARGS"`
Cmds ilist.SemicolonStringList `toml:"cmds,omitempty" envconfig:"CB_CMDS"`

// --------------------
// Nested TOML configuration
// --------------------
Egress EgressConfig `toml:"egress,omitempty" ignored:"true"`
}

// Clone the content of the app config.
Expand Down Expand Up @@ -113,6 +123,11 @@ func (config AppConfig) String() string {
fmt.Fprintf(&str, " Daemon: %t\n", config.Daemon)
fmt.Fprintf(&str, " Pull: %t\n", config.Pull)
fmt.Fprintf(&str, " Dind: %t\n", config.Dind)
fmt.Fprintf(&str, " Sandbox: %t\n", config.Sandbox)
fmt.Fprintf(&str, " WritableBooth: %t\n", config.WritableBooth)
fmt.Fprintf(&str, " SandboxAllowlist: %q\n", config.SandboxAllowlistFile)
fmt.Fprintf(&str, " SandboxPolicy: %q\n", config.SandboxPolicyFile)
fmt.Fprintf(&str, " SandboxAllowlist+: %v\n", config.SandboxAllowlist)

fmt.Fprintf(&str, "# Image Configuration -----------\n")
fmt.Fprintf(&str, " Dockerfile: %q\n", config.Dockerfile)
Expand Down Expand Up @@ -140,6 +155,13 @@ func (config AppConfig) String() string {
formatList(&str, "RunArgs", config.RunArgs.List, " ")
formatList(&str, "Cmds", config.Cmds.List, " ")

fmt.Fprintf(&str, "# Egress Configuration ----------\n")
fmt.Fprintf(&str, " Egress.Mode: %q\n", config.Egress.Mode)
fmt.Fprintf(&str, " Egress.Enforcement:%q\n", config.Egress.Enforcement)
fmt.Fprintf(&str, " Egress.Default: %q\n", config.Egress.Default)
fmt.Fprintf(&str, " Egress.Allowlist: %q\n", config.Egress.AllowlistFile)
fmt.Fprintf(&str, " Egress.Policy: %q\n", config.Egress.PolicyFile)

str.WriteString("==================================================================\n")

return str.String()
Expand Down
Loading