Skip to content

Commit 5aef560

Browse files
Merge branch 'main' into danielle/container-push
2 parents fa46517 + 0118e75 commit 5aef560

File tree

47 files changed

+1993
-808
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1993
-808
lines changed

.claude/scripts/format.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,87 @@
66

77
set -euo pipefail
88

9+
# A variable to memoize the command for canonicalizing paths.
10+
_CANONICALIZE_CMD=""
11+
12+
# canonicalize_path resolves a path to its absolute, canonical form.
13+
# It tries 'realpath' and 'readlink -f' in order.
14+
# The chosen command is memoized to avoid repeated checks.
15+
# If none of these are available, it returns an empty string.
16+
canonicalize_path() {
17+
local path_to_resolve="$1"
18+
19+
# If we haven't determined a command yet, find one.
20+
if [[ -z "$_CANONICALIZE_CMD" ]]; then
21+
if command -v realpath >/dev/null 2>&1; then
22+
_CANONICALIZE_CMD="realpath"
23+
elif command -v readlink >/dev/null 2>&1 && readlink -f . >/dev/null 2>&1; then
24+
_CANONICALIZE_CMD="readlink"
25+
else
26+
# No command found, so we can't resolve.
27+
# We set a "none" value to prevent re-checking.
28+
_CANONICALIZE_CMD="none"
29+
fi
30+
fi
31+
32+
# Now, execute the command.
33+
case "$_CANONICALIZE_CMD" in
34+
realpath)
35+
realpath "$path_to_resolve" 2>/dev/null
36+
;;
37+
readlink)
38+
readlink -f "$path_to_resolve" 2>/dev/null
39+
;;
40+
*)
41+
# This handles the "none" case or any unexpected error.
42+
echo ""
43+
;;
44+
esac
45+
}
46+
947
# Read JSON input from stdin
1048
input=$(cat)
1149

1250
# Extract the file path from the JSON input
1351
# Expected format: {"tool_input": {"file_path": "/absolute/path/to/file"}} or {"tool_response": {"filePath": "/absolute/path/to/file"}}
1452
file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_response.filePath // empty')
1553

54+
# Secure path canonicalization to prevent path traversal attacks
55+
# Resolve repo root to an absolute, canonical path.
56+
repo_root_raw="$(cd "$(dirname "$0")/../.." && pwd)"
57+
repo_root="$(canonicalize_path "$repo_root_raw")"
58+
if [[ -z "$repo_root" ]]; then
59+
# Fallback if canonicalization fails
60+
repo_root="$repo_root_raw"
61+
fi
62+
63+
# Resolve the input path to an absolute path
64+
if [[ "$file_path" = /* ]]; then
65+
# Already absolute
66+
abs_file_path="$file_path"
67+
else
68+
# Make relative paths absolute from repo root
69+
abs_file_path="$repo_root/$file_path"
70+
fi
71+
72+
# Canonicalize the path (resolve symlinks and ".." segments)
73+
canonical_file_path="$(canonicalize_path "$abs_file_path")"
74+
75+
# Check if canonicalization failed or if the resolved path is outside the repo
76+
if [[ -z "$canonical_file_path" ]] || { [[ "$canonical_file_path" != "$repo_root" ]] && [[ "$canonical_file_path" != "$repo_root"/* ]]; }; then
77+
echo "Error: File path is outside repository or invalid: $file_path" >&2
78+
exit 1
79+
fi
80+
81+
# Handle the case where the file path is the repository root itself.
82+
if [[ "$canonical_file_path" == "$repo_root" ]]; then
83+
echo "Warning: Formatting the repository root is not a supported operation. Skipping." >&2
84+
exit 0
85+
fi
86+
87+
# Convert back to relative path from repo root for consistency
88+
file_path="${canonical_file_path#"$repo_root"/}"
89+
1690
if [[ -z "$file_path" ]]; then
1791
echo "Error: No file path provided in input" >&2
1892
exit 1

.mcp.json

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
{
2-
"mcpServers": {
3-
"go-language-server": {
4-
"type": "stdio",
5-
"command": "go",
6-
"args": [
7-
"run",
8-
"github.com/isaacphi/mcp-language-server@latest",
9-
"-workspace",
10-
"./",
11-
"-lsp",
12-
"go",
13-
"--",
14-
"run",
15-
"golang.org/x/tools/gopls@latest"
16-
],
17-
"env": {}
18-
},
19-
"typescript-language-server": {
20-
"type": "stdio",
21-
"command": "go",
22-
"args": [
23-
"run",
24-
"github.com/isaacphi/mcp-language-server@latest",
25-
"-workspace",
26-
"./site/",
27-
"-lsp",
28-
"pnpx",
29-
"--",
30-
"typescript-language-server",
31-
"--stdio"
32-
],
33-
"env": {}
34-
}
35-
}
36-
}
2+
"mcpServers": {
3+
"go-language-server": {
4+
"type": "stdio",
5+
"command": "go",
6+
"args": [
7+
"run",
8+
"github.com/isaacphi/mcp-language-server@latest",
9+
"-workspace",
10+
"./",
11+
"-lsp",
12+
"go",
13+
"--",
14+
"run",
15+
"golang.org/x/tools/gopls@latest"
16+
],
17+
"env": {}
18+
},
19+
"typescript-language-server": {
20+
"type": "stdio",
21+
"command": "go",
22+
"args": [
23+
"run",
24+
"github.com/isaacphi/mcp-language-server@latest",
25+
"-workspace",
26+
"./site/",
27+
"-lsp",
28+
"pnpx",
29+
"--",
30+
"typescript-language-server",
31+
"--stdio"
32+
],
33+
"env": {}
34+
}
35+
}
36+
}

agent/agent.go

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -336,18 +336,16 @@ func (a *agent) init() {
336336
// will not report anywhere.
337337
a.scriptRunner.RegisterMetrics(a.prometheusRegistry)
338338

339-
if a.devcontainers {
340-
containerAPIOpts := []agentcontainers.Option{
341-
agentcontainers.WithExecer(a.execer),
342-
agentcontainers.WithCommandEnv(a.sshServer.CommandEnv),
343-
agentcontainers.WithScriptLogger(func(logSourceID uuid.UUID) agentcontainers.ScriptLogger {
344-
return a.logSender.GetScriptLogger(logSourceID)
345-
}),
346-
}
347-
containerAPIOpts = append(containerAPIOpts, a.containerAPIOptions...)
348-
349-
a.containerAPI = agentcontainers.NewAPI(a.logger.Named("containers"), containerAPIOpts...)
339+
containerAPIOpts := []agentcontainers.Option{
340+
agentcontainers.WithExecer(a.execer),
341+
agentcontainers.WithCommandEnv(a.sshServer.CommandEnv),
342+
agentcontainers.WithScriptLogger(func(logSourceID uuid.UUID) agentcontainers.ScriptLogger {
343+
return a.logSender.GetScriptLogger(logSourceID)
344+
}),
350345
}
346+
containerAPIOpts = append(containerAPIOpts, a.containerAPIOptions...)
347+
348+
a.containerAPI = agentcontainers.NewAPI(a.logger.Named("containers"), containerAPIOpts...)
351349

352350
a.reconnectingPTYServer = reconnectingpty.NewServer(
353351
a.logger.Named("reconnecting-pty"),
@@ -1162,7 +1160,7 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
11621160
scripts = manifest.Scripts
11631161
devcontainerScripts map[uuid.UUID]codersdk.WorkspaceAgentScript
11641162
)
1165-
if a.containerAPI != nil {
1163+
if a.devcontainers {
11661164
// Init the container API with the manifest and client so that
11671165
// we can start accepting requests. The final start of the API
11681166
// happens after the startup scripts have been executed to
@@ -1197,7 +1195,7 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
11971195
// autostarted devcontainer will be included in this time.
11981196
err := a.scriptRunner.Execute(a.gracefulCtx, agentscripts.ExecuteStartScripts)
11991197

1200-
if a.containerAPI != nil {
1198+
if a.devcontainers {
12011199
// Start the container API after the startup scripts have
12021200
// been executed to ensure that the required tools can be
12031201
// installed.
@@ -1928,10 +1926,8 @@ func (a *agent) Close() error {
19281926
a.logger.Error(a.hardCtx, "script runner close", slog.Error(err))
19291927
}
19301928

1931-
if a.containerAPI != nil {
1932-
if err := a.containerAPI.Close(); err != nil {
1933-
a.logger.Error(a.hardCtx, "container API close", slog.Error(err))
1934-
}
1929+
if err := a.containerAPI.Close(); err != nil {
1930+
a.logger.Error(a.hardCtx, "container API close", slog.Error(err))
19351931
}
19361932

19371933
// Wait for the graceful shutdown to complete, but don't wait forever so

agent/agent_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,7 +2441,8 @@ func TestAgent_DevcontainersDisabledForSubAgent(t *testing.T) {
24412441

24422442
// Setup the agent with devcontainers enabled initially.
24432443
//nolint:dogsled
2444-
conn, _, _, _, _ := setupAgent(t, manifest, 0, func(*agenttest.Client, *agent.Options) {
2444+
conn, _, _, _, _ := setupAgent(t, manifest, 0, func(_ *agenttest.Client, o *agent.Options) {
2445+
o.Devcontainers = true
24452446
})
24462447

24472448
// Query the containers API endpoint. This should fail because
@@ -2453,8 +2454,8 @@ func TestAgent_DevcontainersDisabledForSubAgent(t *testing.T) {
24532454
require.Error(t, err)
24542455

24552456
// Verify the error message contains the expected text.
2456-
require.Contains(t, err.Error(), "The agent dev containers feature is experimental and not enabled by default.")
2457-
require.Contains(t, err.Error(), "To enable this feature, set CODER_AGENT_DEVCONTAINERS_ENABLE=true in your template.")
2457+
require.Contains(t, err.Error(), "Dev Container feature not supported.")
2458+
require.Contains(t, err.Error(), "Dev Container integration inside other Dev Containers is explicitly not supported.")
24582459
}
24592460

24602461
func TestAgent_Dial(t *testing.T) {

agent/api.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/go-chi/chi/v5"
9+
"github.com/google/uuid"
910

1011
"github.com/coder/coder/v2/coderd/httpapi"
1112
"github.com/coder/coder/v2/codersdk"
@@ -36,12 +37,19 @@ func (a *agent) apiHandler() http.Handler {
3637
cacheDuration: cacheDuration,
3738
}
3839

39-
if a.containerAPI != nil {
40+
if a.devcontainers {
4041
r.Mount("/api/v0/containers", a.containerAPI.Routes())
42+
} else if manifest := a.manifest.Load(); manifest != nil && manifest.ParentID != uuid.Nil {
43+
r.HandleFunc("/api/v0/containers", func(w http.ResponseWriter, r *http.Request) {
44+
httpapi.Write(r.Context(), w, http.StatusForbidden, codersdk.Response{
45+
Message: "Dev Container feature not supported.",
46+
Detail: "Dev Container integration inside other Dev Containers is explicitly not supported.",
47+
})
48+
})
4149
} else {
4250
r.HandleFunc("/api/v0/containers", func(w http.ResponseWriter, r *http.Request) {
4351
httpapi.Write(r.Context(), w, http.StatusForbidden, codersdk.Response{
44-
Message: "The agent dev containers feature is experimental and not enabled by default.",
52+
Message: "Dev Container feature not enabled.",
4553
Detail: "To enable this feature, set CODER_AGENT_DEVCONTAINERS_ENABLE=true in your template.",
4654
})
4755
})

cli/provisionerjobs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func (r *RootCmd) provisionerJobsCancel() *serpent.Command {
166166
err = client.CancelTemplateVersion(ctx, ptr.NilToEmpty(job.Input.TemplateVersionID))
167167
case codersdk.ProvisionerJobTypeWorkspaceBuild:
168168
_, _ = fmt.Fprintf(inv.Stdout, "Canceling workspace build job %s...\n", job.ID)
169-
err = client.CancelWorkspaceBuild(ctx, ptr.NilToEmpty(job.Input.WorkspaceBuildID))
169+
err = client.CancelWorkspaceBuild(ctx, ptr.NilToEmpty(job.Input.WorkspaceBuildID), codersdk.CancelWorkspaceBuildParams{})
170170
}
171171
if err != nil {
172172
return xerrors.Errorf("cancel provisioner job: %w", err)

cli/ssh_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,7 @@ func TestSSH_Container(t *testing.T) {
21042104
clitest.SetupConfig(t, client, root)
21052105

21062106
err := inv.WithContext(ctx).Run()
2107-
require.ErrorContains(t, err, "The agent dev containers feature is experimental and not enabled by default.")
2107+
require.ErrorContains(t, err, "Dev Container feature not enabled.")
21082108
})
21092109
}
21102110

0 commit comments

Comments
 (0)