Skip to content
Draft
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
37 changes: 37 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,30 @@ 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_log_dir" {
type = string
description = "Directory for boundary logs"
default = "/tmp/boundary_logs"
}

variable "boundary_unprivileged" {
type = bool
description = "Whether to use --unprivileged flag with coder boundary (recommended for security)"
default = true
}

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

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

Expand Down Expand Up @@ -222,6 +246,12 @@ resource "coder_env" "claude_api_key" {
value = var.claude_api_key
}

resource "coder_env" "mcp_server_port" {
agent_id = var.agent_id
name = "MCP_SERVER_PORT"
value = "8081"
}

locals {
# we have to trim the slash because otherwise coder exp mcp will
# set up an invalid claude config
Expand All @@ -231,6 +261,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://", "")
}

module "agentapi" {
Expand Down Expand Up @@ -270,6 +302,11 @@ 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_LOG_DIR='${var.boundary_log_dir}' \
ARG_BOUNDARY_UNPRIVILEGED='${var.boundary_unprivileged}' \
ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS='${join(" ", var.boundary_additional_allowed_urls)}' \
ARG_CODER_HOST='${local.coder_host}' \
/tmp/start.sh
EOT

Expand Down
26 changes: 26 additions & 0 deletions registry/coder/modules/claude-code/main.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,29 @@ run "test_claude_code_permission_mode_validation" {
error_message = "Permission mode should be one of the valid options"
}
}

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"
}
}
36 changes: 35 additions & 1 deletion registry/coder/modules/claude-code/scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ 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_LOG_DIR=${ARG_BOUNDARY_LOG_DIR:-"/tmp/boundary_logs"}
ARG_BOUNDARY_UNPRIVILEGED=${ARG_BOUNDARY_UNPRIVILEGED:-true}
ARG_CODER_HOST=${ARG_CODER_HOST:-}

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

Expand All @@ -25,6 +29,10 @@ 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_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR"
printf "ARG_BOUNDARY_UNPRIVILEGED: %s\n" "$ARG_BOUNDARY_UNPRIVILEGED"
printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST"

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

Expand Down Expand Up @@ -74,7 +82,33 @@ 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
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")
if [ "${ARG_BOUNDARY_UNPRIVILEGED:-true}" = "true" ]; then
BOUNDARY_ARGS+=(--unprivileged)
fi
# 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

agentapi server --type claude --term-width 67 --term-height 1190 -- \
coder exp boundary "${BOUNDARY_ARGS[@]}" -- \
claude "${ARGS[@]}"
else
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"
fi
}

validate_claude_installation
Expand Down