Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a30ef22
init simple integration
bcpeinhardt Oct 6, 2025
00337b3
default unprivileged
bcpeinhardt Oct 6, 2025
4a1bf33
additional urls and agentapi required urls
bcpeinhardt Oct 6, 2025
2a3e76d
it's behind exp omfg
bcpeinhardt Oct 6, 2025
9796e05
allow localhost for healthz and other reporting
bcpeinhardt Oct 6, 2025
01c436a
specifically allow healthz
bcpeinhardt Oct 6, 2025
7acb90b
fmt
bcpeinhardt Oct 6, 2025
2cf20a4
try just localhost:8080 for the allow rule
bcpeinhardt Oct 6, 2025
919c1bb
try changing claudes mcp port
bcpeinhardt Oct 6, 2025
7ba975a
remove the localhost allowance, going to fix on the boundary side of …
bcpeinhardt Oct 6, 2025
785a6d8
claude-code integration with strict jail
evgeniy-scherbina Oct 16, 2025
19cdadb
remove unprivileged jail
evgeniy-scherbina Oct 20, 2025
9dfdfd9
Make boundary http proxy port configurable
evgeniy-scherbina Oct 20, 2025
c44d07d
Make log-level configurable
evgeniy-scherbina Oct 20, 2025
c7d76bb
mark warn default log level
evgeniy-scherbina Oct 20, 2025
5a74d50
install boundary from specific version
evgeniy-scherbina Oct 20, 2025
b04cea9
Merge remote-tracking branch 'origin/main' into bcpeinhardt/claude-co…
evgeniy-scherbina Oct 21, 2025
2764f82
Remove --dangerously-skip-permissions flag when using boundary
evgeniy-scherbina Oct 21, 2025
b00f535
refactor: add install_boundary function
evgeniy-scherbina Oct 21, 2025
4f35b01
refactor: minor fix
evgeniy-scherbina Oct 21, 2025
3b45c87
fix: linter
evgeniy-scherbina Oct 21, 2025
ff9faa3
fix: use correct allow syntax
evgeniy-scherbina Oct 21, 2025
62a2502
refactor: minor refactor
evgeniy-scherbina Oct 21, 2025
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
1 change: 1 addition & 0 deletions .github/typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Hashi = "Hashi"
HashiCorp = "HashiCorp"
mavrickrishi = "mavrickrishi" # Username
mavrick = "mavrick" # Username
inh = "inh" # Option in setpriv command

[files]
extend-exclude = ["registry/coder/templates/aws-devcontainer/architecture.svg"] #False positive
45 changes: 45 additions & 0 deletions registry/coder/modules/claude-code/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,42 @@ variable "claude_md_path" {
default = "$HOME/.claude/CLAUDE.md"
}

variable "enable_boundary" {
type = bool
description = "Whether to enable coder boundary for network filtering"
default = false
}

variable "boundary_version" {
type = string
description = "Boundary version, valid git reference should be provided (tag, commit, branch)"
default = "main"
}

variable "boundary_log_dir" {
type = string
description = "Directory for boundary logs"
default = "/tmp/boundary_logs"
}

variable "boundary_log_level" {
type = string
description = "Log level for boundary process"
default = "WARN"
}

variable "boundary_additional_allowed_urls" {
type = list(string)
description = "Additional URLs to allow through boundary (in addition to default allowed URLs)"
default = []
}

variable "boundary_proxy_port" {
type = string
description = "Port for HTTP Proxy used by Boundary"
default = "8087"
}

resource "coder_env" "claude_code_md_path" {
count = var.claude_md_path == "" ? 0 : 1

Expand Down Expand Up @@ -229,6 +265,8 @@ locals {
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".claude-module"
remove_last_session_id_script_b64 = base64encode(file("${path.module}/scripts/remove-last-session-id.sh"))
# Extract hostname from access_url for boundary --allow flag
coder_host = replace(replace(data.coder_workspace.me.access_url, "https://", ""), "http://", "")

# Required prompts for the module to properly report task status to Coder
report_tasks_system_prompt = <<-EOT
Expand Down Expand Up @@ -299,6 +337,13 @@ module "agentapi" {
ARG_PERMISSION_MODE='${var.permission_mode}' \
ARG_WORKDIR='${local.workdir}' \
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \
ARG_BOUNDARY_VERSION='${var.boundary_version}' \
ARG_BOUNDARY_LOG_DIR='${var.boundary_log_dir}' \
ARG_BOUNDARY_LOG_LEVEL='${var.boundary_log_level}' \
ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS='${join(" ", var.boundary_additional_allowed_urls)}' \
ARG_BOUNDARY_PROXY_PORT='${var.boundary_proxy_port}' \
ARG_CODER_HOST='${local.coder_host}' \
/tmp/start.sh
EOT

Expand Down
28 changes: 27 additions & 1 deletion registry/coder/modules/claude-code/main.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,32 @@ run "test_claude_code_permission_mode_validation" {
}
}

run "test_claude_code_with_boundary" {
command = plan

variables {
agent_id = "test-agent-boundary"
workdir = "/home/coder/boundary-test"
enable_boundary = true
boundary_log_dir = "/tmp/test-boundary-logs"
}

assert {
condition = var.enable_boundary == true
error_message = "Boundary should be enabled"
}

assert {
condition = var.boundary_log_dir == "/tmp/test-boundary-logs"
error_message = "Boundary log dir should be set correctly"
}

assert {
condition = local.coder_host != ""
error_message = "Coder host should be extracted from access URL"
}
}

run "test_claude_code_system_prompt" {
command = plan

Expand Down Expand Up @@ -267,4 +293,4 @@ run "test_claude_report_tasks_disabled" {
condition = endswith(trimspace(coder_env.claude_code_system_prompt.value), "</system>")
error_message = "System prompt should end with </system>"
}
}
}
62 changes: 61 additions & 1 deletion registry/coder/modules/claude-code/scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ ARG_DANGEROUSLY_SKIP_PERMISSIONS=${ARG_DANGEROUSLY_SKIP_PERMISSIONS:-}
ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-}
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d)
ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"}
ARG_BOUNDARY_LOG_DIR=${ARG_BOUNDARY_LOG_DIR:-"/tmp/boundary_logs"}
ARG_BOUNDARY_LOG_LEVEL=${ARG_BOUNDARY_LOG_LEVEL:-"WARN"}
ARG_BOUNDARY_PROXY_PORT=${ARG_BOUNDARY_PROXY_PORT:-"8087"}
ARG_CODER_HOST=${ARG_CODER_HOST:-}

echo "--------------------------------"

Expand All @@ -27,6 +33,12 @@ printf "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "$ARG_DANGEROUSLY_SKIP_PERMISSIO
printf "ARG_PERMISSION_MODE: %s\n" "$ARG_PERMISSION_MODE"
printf "ARG_AI_PROMPT: %s\n" "$ARG_AI_PROMPT"
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY"
printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION"
printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR"
printf "ARG_BOUNDARY_LOG_LEVEL: %s\n" "$ARG_BOUNDARY_LOG_LEVEL"
printf "ARG_BOUNDARY_PROXY_PORT: %s\n" "$ARG_BOUNDARY_PROXY_PORT"
printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST"

echo "--------------------------------"

Expand All @@ -35,6 +47,14 @@ echo "--------------------------------"
# avoid exiting if the script fails
bash "/tmp/remove-last-session-id.sh" "$(pwd)" 2> /dev/null || true

function install_boundary() {
# Install boundary from public github repo
git clone https://github.com/coder/boundary
cd boundary
git checkout $ARG_BOUNDARY_VERSION
go install ./cmd/...
}

function validate_claude_installation() {
if command_exists claude; then
printf "Claude Code is installed\n"
Expand Down Expand Up @@ -76,7 +96,47 @@ function start_agentapi() {
fi
fi
printf "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")"
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"

if [ "${ARG_ENABLE_BOUNDARY:-false}" = "true" ]; then
install_boundary

mkdir -p "$ARG_BOUNDARY_LOG_DIR"
printf "Starting with coder boundary enabled\n"

# Build boundary args with conditional --unprivileged flag
BOUNDARY_ARGS=(--log-dir "$ARG_BOUNDARY_LOG_DIR")
# Add default allowed URLs
BOUNDARY_ARGS+=(--allow "*anthropic.com" --allow "registry.npmjs.org" --allow "*sentry.io" --allow "claude.ai" --allow "$ARG_CODER_HOST")

# Add any additional allowed URLs from the variable
if [ -n "$ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS" ]; then
IFS=' ' read -ra ADDITIONAL_URLS <<< "$ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS"
for url in "${ADDITIONAL_URLS[@]}"; do
BOUNDARY_ARGS+=(--allow "$url")
done
fi

# Set HTTP Proxy port used by Boundary
BOUNDARY_ARGS+=(--proxy-port $ARG_BOUNDARY_PROXY_PORT)

# Set log level for boundary
BOUNDARY_ARGS+=(--log-level $ARG_BOUNDARY_LOG_LEVEL)

# Remove --dangerously-skip-permissions from ARGS when using boundary (it doesn't work with elevated permissions)
# Create a new array without the dangerous permissions flag
CLAUDE_ARGS=()
for arg in "${ARGS[@]}"; do
if [ "$arg" != "--dangerously-skip-permissions" ]; then
CLAUDE_ARGS+=("$arg")
fi
done

agentapi server --allowed-hosts="*" --type claude --term-width 67 --term-height 1190 -- \
sudo -E env PATH=$PATH setpriv --inh-caps=+net_admin --ambient-caps=+net_admin --bounding-set=+net_admin boundary "${BOUNDARY_ARGS[@]}" -- \
claude "${CLAUDE_ARGS[@]}"
else
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"
fi
}

validate_claude_installation
Expand Down