Skip to content

Commit b0339c1

Browse files
gcmsgclaude
andcommitted
fix: resolve lint errors and harden admin API for 1.0 release
Fix 4 golangci-lint errcheck violations (unchecked fmt.Fprintf in SSE handler), 1 ineffassign (unused logger in OptionalUserAuthMiddleware), auth.ts header merge bug, self-referencing type imports, and add input validation to admin handlers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 780f4c4 commit b0339c1

File tree

5 files changed

+22
-8
lines changed

5 files changed

+22
-8
lines changed

internal/server/admin_handler.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ func (s *HTTPServer) handleAdminGetUser(w http.ResponseWriter, r *http.Request)
100100
}
101101

102102
id := r.PathValue("id")
103+
if id == "" {
104+
s.jsonError(w, "user id is required", http.StatusBadRequest)
105+
return
106+
}
103107
user, err := s.userAuth.GetUser(r.Context(), id)
104108
if err != nil {
105109
s.jsonError(w, "user not found", http.StatusNotFound)
@@ -117,6 +121,10 @@ func (s *HTTPServer) handleAdminUpdateUserRole(w http.ResponseWriter, r *http.Re
117121
}
118122

119123
id := r.PathValue("id")
124+
if id == "" {
125+
s.jsonError(w, "user id is required", http.StatusBadRequest)
126+
return
127+
}
120128
var req struct {
121129
Role string `json:"role"`
122130
}
@@ -527,9 +535,13 @@ func (s *HTTPServer) registerAdminRoutes() {
527535
}
528536

529537
// queryInt extracts an integer query parameter with a default value.
538+
// Values are clamped to [1, 200] to prevent abuse.
530539
func queryInt(r *http.Request, key string, defaultVal int) int {
531540
if v := r.URL.Query().Get(key); v != "" {
532541
if n, err := strconv.Atoi(v); err == nil && n > 0 {
542+
if n > 200 {
543+
n = 200
544+
}
533545
return n
534546
}
535547
}

internal/server/auth.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ func OptionalUserAuthMiddleware(jwtMgr *userauth.JWTManager, logger *slog.Logger
237237

238238
claims, err := jwtMgr.ValidateAccessToken(parts[1])
239239
if err != nil {
240+
logger.Debug("optional JWT validation failed", "error", err)
240241
next.ServeHTTP(w, r)
241242
return
242243
}

internal/server/invoke_handler.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func (s *HTTPServer) handleInvokeSSE(w http.ResponseWriter, r *http.Request, env
175175
invID := uuid.New().String()
176176

177177
// Send initial event.
178-
fmt.Fprintf(w, "event: start\ndata: {\"id\":\"%s\",\"agent_id\":\"%s\"}\n\n", invID, agentID)
178+
_, _ = fmt.Fprintf(w, "event: start\ndata: {\"id\":\"%s\",\"agent_id\":\"%s\"}\n\n", invID, agentID)
179179
flusher.Flush()
180180

181181
var respBody string
@@ -199,17 +199,17 @@ func (s *HTTPServer) handleInvokeSSE(w http.ResponseWriter, r *http.Request, env
199199
duration := time.Since(start).Milliseconds()
200200

201201
if invokeErr != "" {
202-
fmt.Fprintf(w, "event: error\ndata: {\"error\":\"%s\"}\n\n", invokeErr)
202+
_, _ = fmt.Fprintf(w, "event: error\ndata: {\"error\":\"%s\"}\n\n", invokeErr)
203203
} else {
204204
msgData, _ := json.Marshal(map[string]any{
205205
"content": respBody,
206206
"protocol": proto,
207207
})
208-
fmt.Fprintf(w, "event: message\ndata: %s\n\n", string(msgData))
208+
_, _ = fmt.Fprintf(w, "event: message\ndata: %s\n\n", string(msgData))
209209
}
210210
flusher.Flush()
211211

212-
fmt.Fprintf(w, "event: done\ndata: {\"duration_ms\":%d}\n\n", duration)
212+
_, _ = fmt.Fprintf(w, "event: done\ndata: {\"duration_ms\":%d}\n\n", duration)
213213
flusher.Flush()
214214

215215
// Record invocation.

web/app/src/api/auth.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ async function authFetch<T>(
4949
path: string,
5050
options: RequestInit = {}
5151
): Promise<T> {
52+
const { headers, ...rest } = options
5253
const res = await fetch(`${BASE}${path}`, {
53-
headers: { "Content-Type": "application/json", ...options.headers },
54-
...options,
54+
...rest,
55+
headers: { "Content-Type": "application/json", ...headers },
5556
})
5657
if (!res.ok) {
5758
const body = await res.json().catch(() => ({ error: res.statusText }))

web/app/src/api/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,10 @@ export interface AdminUserListResponse {
248248
}
249249

250250
export interface AdminAgentDetail {
251-
agent: import("./types").Agent
251+
agent: Agent
252252
owner?: AdminUser
253253
reputation_score?: number
254-
reputation_events?: import("./types").ReputationEvent[]
254+
reputation_events?: ReputationEvent[]
255255
review_summary?: ReviewSummary
256256
invocation_stats?: AgentInvocationStats
257257
}

0 commit comments

Comments
 (0)