Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2b459f7
feat(boundary): add boundary module with installation and configurati…
35C4n0r Apr 13, 2026
e4e059b
chore: bun fmt
35C4n0r Apr 13, 2026
12ca1b1
feat(boundary): enhance installation with wrapper script and pre/post…
35C4n0r Apr 14, 2026
abba320
Merge branch 'main' into 35C4n0r/feat-boundary-module
35C4n0r Apr 14, 2026
0a7aa71
feat(boundary): add tests and mock scripts for boundary module instal…
35C4n0r Apr 14, 2026
8dc8c8b
docs: update README with script synchronization details for boundary …
35C4n0r Apr 14, 2026
d9d0b3c
fix(boundary): fix test failures caused by script path collision and …
Apr 14, 2026
ce15e06
chore: description fix
35C4n0r Apr 14, 2026
946204d
chore(boundary): remove unused boundary-mock.sh
Apr 14, 2026
8b88931
fix(boundary): improve command check for 'coder' to handle errors mor…
35C4n0r Apr 14, 2026
f45b2e9
fix(boundary): remove unnecessary argument passing in boundary wrappe…
35C4n0r Apr 15, 2026
81df58f
fix: add '--' separator in boundary wrapper scripts
Apr 15, 2026
f081d87
Revert "fix: add '--' separator in boundary wrapper scripts"
Apr 15, 2026
ec2e16f
fix(tests): update mock and test to not require '--' separator
Apr 15, 2026
920f954
feat(boundary): refactor boundary wrapper setup to use BOUNDARY_WRAPP…
35C4n0r Apr 23, 2026
d2e528d
fix(tests): rename AGENTAPI_BOUNDARY_PREFIX to BOUNDARY_WRAPPER_PATH
Apr 23, 2026
74c4947
fix: rename remaining AGENTAPI_BOUNDARY_PREFIX references to BOUNDARY…
Apr 23, 2026
b16208f
feat(boundary): update coder_utils module source and version, add dis…
35C4n0r Apr 23, 2026
f22770d
feat(boundary): update command execution example to include config an…
35C4n0r Apr 23, 2026
06b039f
fix(tests): update sync_script_names assertions for flat list output
Apr 23, 2026
fef86ab
docs: set module version to 0.0.1 in README examples
Apr 23, 2026
9f16eca
feat(boundary): update boundary wrapper path and output name in modul…
35C4n0r Apr 23, 2026
dc95e45
fix(tests): update paths for scripts/ and logs/ directories, rename o…
Apr 23, 2026
bb21940
fix: create scripts/ directory before writing boundary wrapper
Apr 23, 2026
74e58dc
fix: resolve coder-no-caps path in wrapper script
Apr 23, 2026
cf1d1ab
refactor: move all scripts into scripts/ subdirectory
Apr 23, 2026
4a4670e
Merge branch 'main' into 35C4n0r/feat-boundary-module
35C4n0r Apr 23, 2026
155362f
feat(boundary): update boundary script destination and simplify execu…
35C4n0r Apr 23, 2026
3aa6333
feat(boundary): rename local variables for clarity in main.tf
35C4n0r Apr 23, 2026
fbaaab5
debug
35C4n0r Apr 23, 2026
5953687
debug
35C4n0r Apr 23, 2026
3bb1a3b
refactor: rename boundary-install.sh to install.sh, fix coder-utils ref
Apr 23, 2026
f3c2c55
bun fmt
35C4n0r Apr 23, 2026
5dd0370
feat(boundary): update coder-utils source and adjust config paths in …
35C4n0r Apr 23, 2026
4dd0176
docs: update README to include link for boundary installation
35C4n0r Apr 23, 2026
056a111
Merge branch 'main' into 35C4n0r/feat-boundary-module
35C4n0r Apr 24, 2026
d79446a
refactor: convert install.sh to tftpl template
Apr 24, 2026
6d828df
style: run bun fmt
Apr 24, 2026
12183e5
feat(boundary): update coder-utils source to registry and specify ver…
35C4n0r Apr 24, 2026
256953e
docs: fix version in README examples to 0.0.1
Apr 24, 2026
f006b83
Merge branch 'main' into 35C4n0r/feat-boundary-module
35C4n0r Apr 24, 2026
3a2dbd6
docs: add config.yaml, Claude Code example, fix main.tf comments
Apr 27, 2026
266eb1d
feat(boundary): add boundary_config and boundary_config_path variables
Apr 28, 2026
cd71b21
fix(boundary): base64 encode config content for template safety
Apr 28, 2026
c33ae2f
fix(boundary): decode b64 at variable init, add printf debug lines
Apr 28, 2026
f70ac78
refactor(boundary): address review feedback from matifali
Apr 28, 2026
559f4ea
refactor(boundary): convert config.yaml to tftpl, inject coder_domain…
Apr 28, 2026
01d1800
bun fmt
35C4n0r Apr 28, 2026
8c91560
test(boundary): add coverage for coder_domain auto-fill, scripts and …
Apr 28, 2026
123c48b
fix(boundary): validate coder boundary with a real invocation, not --…
Apr 28, 2026
e2b69a1
fix(boundary): only fail on license entitlement error, ignore other c…
Apr 28, 2026
b918896
chore(boundary): trim verbose comment
Apr 28, 2026
4fdad04
docs(boundary): update Claude Code example to coder_app, suppress syn…
Apr 28, 2026
39f0a5a
debug
35C4n0r Apr 28, 2026
50db520
debug
35C4n0r Apr 28, 2026
f0a469b
debug
35C4n0r Apr 28, 2026
b15c197
debug
35C4n0r Apr 28, 2026
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
186 changes: 186 additions & 0 deletions registry/coder/modules/boundary/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
---
display_name: Boundary
description: Configures boundary for network isolation in Coder workspaces
icon: ../../../../.icons/coder.svg
verified: true
tags: [boundary, ai, agents, firewall]
---

# Boundary

Installs [boundary](https://coder.com/docs/ai-coder/agent-firewall) for network isolation in Coder workspaces.

This module:

- Installs boundary (via coder subcommand, direct installation, or compilation from source)
- Creates a wrapper script at `$HOME/.coder-modules/coder/boundary/scripts/boundary-wrapper.sh`
- Writes a default boundary config to `$HOME/.coder-modules/coder/boundary/config/config.yaml` (customizable)
- Automatically adds your Coder deployment domain to the config allowlist
- Exports `BOUNDARY_CONFIG` as a workspace environment variable
- Provides the wrapper path, config path, and script names via outputs

```tf
module "boundary" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/boundary/coder"
version = "0.0.1"
agent_id = coder_agent.main.id
}
```

## Configuration

The module ships with a comprehensive default config based on the
[Coder dogfood allowlist](./config.yaml). It covers Anthropic services,
OpenAI services, version control, package managers, container registries,
cloud platforms, and common development tools.

The Coder deployment domain is automatically added to the allowlist using
`data.coder_workspace.me.access_url`.

By default the config is written to
`$HOME/.coder-modules/coder/boundary/config/config.yaml` and the
`BOUNDARY_CONFIG` env var points there. You can override it in two ways:

### Inline config

Pass the full YAML content directly:

```tf
module "boundary" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/boundary/coder"
version = "0.0.1"
agent_id = coder_agent.main.id

boundary_config = <<-YAML
allowlist:
- domain=your-deployment.coder.com
- domain=api.anthropic.com
- domain=api.openai.com
log_dir: /tmp/boundary_logs
proxy_port: 8087
log_level: warn
YAML
}
```

### External config file

Point to an existing config file in the workspace. The module will not
write any config and `BOUNDARY_CONFIG` will point to your path:

```tf
module "boundary" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/boundary/coder"
version = "0.0.1"
agent_id = coder_agent.main.id

boundary_config_path = "/workspace/my-boundary-config.yaml"
}
```

> **Note:** `boundary_config` and `boundary_config_path` are mutually
> exclusive — setting both produces a validation error.

See the [Agent Firewall docs](https://coder.com/docs/ai-coder/agent-firewall)
for the full config reference.

## Usage

Use the `boundary_wrapper_path` output to access the wrapper path in Terraform
and pass it to scripts that should run commands in network isolation:

```tf
module "boundary" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/boundary/coder"
version = "0.0.1"
agent_id = coder_agent.main.id
}

resource "coder_script" "my_app" {
agent_id = coder_agent.main.id
script = <<-EOT
WRAPPER="${module.boundary[0].boundary_wrapper_path}"
"$WRAPPER" -- my-command --args
EOT
}
```
Comment thread
35C4n0r marked this conversation as resolved.

### Script Synchronization

The `scripts` output provides a list of script names that can be used with `coder exp sync` to coordinate script execution. This is useful when your scripts need to wait for boundary installation to complete before running.

The list may contain the following script names:

- `coder-boundary-pre_install_script` - Pre-installation script (if configured)
- `coder-boundary-install_script` - Main boundary installation script
- `coder-boundary-post_install_script` - Post-installation script (if configured)

## Examples

### With Claude Code

Use boundary alongside the `claude-code` module to run Claude in a
network-isolated environment. The `coder_app` below waits for both
modules to finish installing before launching Claude behind the boundary
wrapper.

```tf
module "boundary" {
source = "registry.coder.com/coder/boundary/coder"
version = "0.0.1"
agent_id = coder_agent.main.id
}

module "claude_code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "5.3.0"
agent_id = coder_agent.main.id
}

resource "coder_app" "claude_with_boundary" {
agent_id = coder_agent.main.id
slug = "claude-cli"
display_name = "Claude (Boundary)"
command = <<-EOT
# Wait for boundary and claude-code install scripts to complete.
coder exp sync want claude-boundary \
${join(" ", module.boundary.scripts)} \
${join(" ", module.claude_code.scripts)} > /dev/null 2>&1
coder exp sync start claude-boundary > /dev/null 2>&1

# Run Claude inside the boundary wrapper.
"${module.boundary.boundary_wrapper_path}" \
--config="${module.boundary.boundary_config_path}" -- claude
EOT
}
```

### Compile from source

```tf
module "boundary" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/boundary/coder"
version = "0.0.1"
agent_id = coder_agent.main.id
compile_boundary_from_source = true
boundary_version = "main"
}
```

### Use release binary

```tf
module "boundary" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/boundary/coder"
version = "0.0.1"
agent_id = coder_agent.main.id
use_boundary_directly = true
boundary_version = "latest"
}
```
179 changes: 179 additions & 0 deletions registry/coder/modules/boundary/boundary.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Test for boundary module

run "plan_with_required_vars" {
command = plan

variables {
agent_id = "test-agent-id"
}

# Verify BOUNDARY_CONFIG env var with default config path
assert {
condition = coder_env.boundary_config.name == "BOUNDARY_CONFIG"
error_message = "Environment variable name should be 'BOUNDARY_CONFIG'"
}

assert {
condition = coder_env.boundary_config.value == "$HOME/.coder-modules/coder/boundary/config/config.yaml"
error_message = "BOUNDARY_CONFIG should default to module_directory/config/config.yaml"
}

# Verify the boundary_wrapper_path output
assert {
condition = output.boundary_wrapper_path == "$HOME/.coder-modules/coder/boundary/scripts/boundary-wrapper.sh"
error_message = "boundary_wrapper_path output should be correct"
}

# Verify boundary_config_path output defaults to the managed path
assert {
condition = output.boundary_config_path == "$HOME/.coder-modules/coder/boundary/config/config.yaml"
error_message = "boundary_config_path output should default to managed config path"
}

# Verify the scripts output contains the install script name
assert {
condition = contains(output.scripts, "coder-boundary-install_script")
error_message = "scripts should contain the install script name"
}
}

run "plan_with_compile_from_source" {
command = plan

variables {
agent_id = "test-agent-id"
compile_boundary_from_source = true
boundary_version = "main"
}

assert {
condition = output.boundary_wrapper_path == "$HOME/.coder-modules/coder/boundary/scripts/boundary-wrapper.sh"
error_message = "boundary_wrapper_path output should be correct"
}

assert {
condition = contains(output.scripts, "coder-boundary-install_script")
error_message = "scripts should contain the install script name"
}
}

run "plan_with_use_directly" {
command = plan

variables {
agent_id = "test-agent-id"
use_boundary_directly = true
boundary_version = "latest"
}

assert {
condition = output.boundary_wrapper_path == "$HOME/.coder-modules/coder/boundary/scripts/boundary-wrapper.sh"
error_message = "boundary_wrapper_path output should be correct"
}

assert {
condition = contains(output.scripts, "coder-boundary-install_script")
error_message = "scripts should contain the install script name"
}
}

run "plan_with_custom_hooks" {
command = plan

variables {
agent_id = "test-agent-id"
pre_install_script = "echo 'Before install'"
post_install_script = "echo 'After install'"
}

assert {
condition = contains(output.scripts, "coder-boundary-install_script")
error_message = "scripts should contain the install script name"
}

# Verify pre and post install script names are set
assert {
condition = contains(output.scripts, "coder-boundary-pre_install_script")
error_message = "scripts should contain the pre_install script name"
}

assert {
condition = contains(output.scripts, "coder-boundary-post_install_script")
error_message = "scripts should contain the post_install script name"
}
}

run "plan_with_custom_module_directory" {
command = plan

variables {
agent_id = "test-agent-id"
module_directory = "$HOME/.coder-modules/custom/boundary"
}

assert {
condition = output.boundary_wrapper_path == "$HOME/.coder-modules/custom/boundary/scripts/boundary-wrapper.sh"
error_message = "boundary_wrapper_path output should use custom module directory"
}

# Config path should also follow the module directory
assert {
condition = output.boundary_config_path == "$HOME/.coder-modules/custom/boundary/config/config.yaml"
error_message = "boundary_config_path output should use custom module directory"
}
}

run "plan_with_inline_boundary_config" {
command = plan

variables {
agent_id = "test-agent-id"
boundary_config = "allowlist:\n - domain=example.com\nlog_level: debug\n"
}

# BOUNDARY_CONFIG should still point to the managed path since we write
# the inline content there.
assert {
condition = coder_env.boundary_config.value == "$HOME/.coder-modules/coder/boundary/config/config.yaml"
error_message = "BOUNDARY_CONFIG should point to managed config path when using inline config"
}

assert {
condition = output.boundary_config_path == "$HOME/.coder-modules/coder/boundary/config/config.yaml"
error_message = "boundary_config_path output should point to managed config path"
}
}

run "plan_with_boundary_config_path" {
command = plan

variables {
agent_id = "test-agent-id"
boundary_config_path = "/workspace/my-boundary-config.yaml"
}

# BOUNDARY_CONFIG should point to the user-provided path.
assert {
condition = coder_env.boundary_config.value == "/workspace/my-boundary-config.yaml"
error_message = "BOUNDARY_CONFIG should point to user-provided config path"
}

assert {
condition = output.boundary_config_path == "/workspace/my-boundary-config.yaml"
error_message = "boundary_config_path output should point to user-provided path"
}
}

run "plan_with_both_configs_should_fail" {
command = plan

variables {
agent_id = "test-agent-id"
boundary_config = "allowlist: []"
boundary_config_path = "/workspace/config.yaml"
}

expect_failures = [
var.boundary_config,
]
}
Loading
Loading