Skip to content

Commit 1325449

Browse files
committed
fix: security and reliability — path traversal protection, dummy vertex token, bash errors, marshal errors
1 parent 180bd36 commit 1325449

4 files changed

Lines changed: 51 additions & 11 deletions

File tree

bedrock.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ func (p *BedrockProvider) Complete(ctx context.Context, messages []Message, opts
7777
body["temperature"] = p.config.Temperature
7878
}
7979

80-
jsonBody, _ := json.Marshal(body)
80+
jsonBody, err := json.Marshal(body)
81+
if err != nil {
82+
return "", fmt.Errorf("marshal request: %w", err)
83+
}
8184

8285
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonBody))
8386
if err != nil {
@@ -95,7 +98,10 @@ func (p *BedrockProvider) Complete(ctx context.Context, messages []Message, opts
9598
}
9699
defer resp.Body.Close()
97100

98-
respBody, _ := io.ReadAll(resp.Body)
101+
respBody, err := io.ReadAll(resp.Body)
102+
if err != nil {
103+
return "", fmt.Errorf("read response: %w", err)
104+
}
99105

100106
if resp.StatusCode != http.StatusOK {
101107
return "", fmt.Errorf("Bedrock error (%d): %s", resp.StatusCode, string(respBody))

openairesponses.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,10 @@ func (p *OpenAIResponsesProvider) Stream(ctx context.Context, config StreamConfi
205205
Store: false,
206206
}
207207

208-
jsonBody, _ := json.Marshal(body)
208+
jsonBody, err := json.Marshal(body)
209+
if err != nil {
210+
return Message{}, fmt.Errorf("marshal request: %w", err)
211+
}
209212

210213
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonBody))
211214
if err != nil {
@@ -223,7 +226,7 @@ func (p *OpenAIResponsesProvider) Stream(ctx context.Context, config StreamConfi
223226
defer resp.Body.Close()
224227

225228
if resp.StatusCode != http.StatusOK {
226-
respBody, _ := io.ReadAll(resp.Body)
229+
respBody, _ := io.ReadAll(resp.Body) // best-effort for error message
227230
return Message{}, fmt.Errorf("OpenAI Responses API error (%d): %s", resp.StatusCode, string(respBody))
228231
}
229232

tools.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ func isCommandDangerous(cmd string) bool {
5050
return false
5151
}
5252

53+
// safeJoin joins repoPath with the user-supplied relative path and ensures the
54+
// result stays within repoPath, preventing path traversal attacks.
55+
func safeJoin(repoPath, rel string) (string, error) {
56+
joined := filepath.Join(repoPath, rel)
57+
absRepo, err := filepath.Abs(repoPath)
58+
if err != nil {
59+
return "", fmt.Errorf("invalid repo path: %w", err)
60+
}
61+
absJoined, err := filepath.Abs(joined)
62+
if err != nil {
63+
return "", fmt.Errorf("invalid path: %w", err)
64+
}
65+
if !strings.HasPrefix(absJoined, absRepo+string(filepath.Separator)) && absJoined != absRepo {
66+
return "", fmt.Errorf("path %q is outside the repository", rel)
67+
}
68+
return absJoined, nil
69+
}
70+
5371
// DefaultTools returns all built-in tools available to the agent.
5472
func DefaultTools(repoPath string) []Tool {
5573
return []Tool{
@@ -86,8 +104,8 @@ func BashTool(repoPath string) Tool {
86104
var out bytes.Buffer
87105
c.Stdout = &out
88106
c.Stderr = &out
89-
_ = c.Run()
90-
return out.String(), nil
107+
err := c.Run()
108+
return out.String(), err
91109
},
92110
}
93111
}
@@ -97,7 +115,10 @@ func ReadFileTool(repoPath string) Tool {
97115
Name: "read_file",
98116
Description: "Read a file from the repo.\nArgs: {\"path\": \"internal/agent/agent.go\"}",
99117
Execute: func(ctx context.Context, args map[string]string) (string, error) {
100-
path := filepath.Join(repoPath, args["path"])
118+
path, err := safeJoin(repoPath, args["path"])
119+
if err != nil {
120+
return "", err
121+
}
101122
data, err := os.ReadFile(path)
102123
if err != nil {
103124
return "", fmt.Errorf("read %s: %w", args["path"], err)
@@ -112,7 +133,10 @@ func WriteFileTool(repoPath string) Tool {
112133
Name: "write_file",
113134
Description: "Write or overwrite a file in the repo.\nArgs: {\"path\": \"internal/agent/agent.go\", \"content\": \"...\"}",
114135
Execute: func(ctx context.Context, args map[string]string) (string, error) {
115-
path := filepath.Join(repoPath, args["path"])
136+
path, err := safeJoin(repoPath, args["path"])
137+
if err != nil {
138+
return "", err
139+
}
116140
if isPathProtected(path) {
117141
return "", fmt.Errorf("write to %s is protected", args["path"])
118142
}
@@ -132,7 +156,10 @@ func EditFileTool(repoPath string) Tool {
132156
Name: "edit_file",
133157
Description: "Edit a file by replacing oldString with newString.\nArgs: {\"path\": \"file.go\", \"oldString\": \"old\", \"newString\": \"new\"}",
134158
Execute: func(ctx context.Context, args map[string]string) (string, error) {
135-
path := filepath.Join(repoPath, args["path"])
159+
path, err := safeJoin(repoPath, args["path"])
160+
if err != nil {
161+
return "", err
162+
}
136163
if isPathProtected(path) {
137164
return "", fmt.Errorf("edit %s is protected", args["path"])
138165
}
@@ -194,7 +221,11 @@ func SearchTool(repoPath string) Tool {
194221
}
195222
path := repoPath
196223
if args["path"] != "" {
197-
path = filepath.Join(repoPath, args["path"])
224+
var err error
225+
path, err = safeJoin(repoPath, args["path"])
226+
if err != nil {
227+
return "", err
228+
}
198229
}
199230

200231
c := exec.CommandContext(ctx, "grep", "-r", "-n", pattern, path)

vertex.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (p *VertexProvider) getAccessToken(ctx context.Context) (string, error) {
5454
if err := json.Unmarshal(data, &creds); err != nil {
5555
return "", err
5656
}
57-
return "dummy_token_from_service_account", nil
57+
return "", fmt.Errorf("service account credentials file found but JWT signing is not implemented; set GOOGLE_ACCESS_TOKEN instead")
5858
}
5959

6060
tokenSrc := os.Getenv("GOOGLE_ACCESS_TOKEN")

0 commit comments

Comments
 (0)