fix(security): replace err.Error() with generic messages in handler responses (#1193)
Replace all c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
calls across 22 handler files with context-appropriate generic messages
to prevent internal error strings (DB details, validation messages,
file paths) leaking into API responses.
Pattern established:
- ShouldBindJSON failures → "invalid request body" (or "invalid delegation request")
- Validation failures → "invalid workspace ID", "invalid path", etc.
- Server-side errors still logged, only generic message returned to client
References: Security finding from Audit #125 (Stripe key leak via err.Error())
Co-authored-by: Molecule AI Fullstack (floater) <fullstack-floater@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
421d220106
commit
35ccda1091
@ -303,7 +303,7 @@ func (h *ActivityHandler) Report(c *gin.Context) {
|
||||
Metadata interface{} `json:"metadata"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ func (h *AgentHandler) Assign(c *gin.Context) {
|
||||
Model string `json:"model" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ func (h *AgentHandler) Replace(c *gin.Context) {
|
||||
Model string `json:"model" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ func (h *AgentHandler) Move(c *gin.Context) {
|
||||
TargetWorkspaceID string `json:"target_workspace_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ func (h *ApprovalsHandler) Create(c *gin.Context) {
|
||||
Context map[string]interface{} `json:"context"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -170,7 +170,7 @@ func (h *ApprovalsHandler) Decide(c *gin.Context) {
|
||||
DecidedBy string `json:"decided_by"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -141,7 +141,7 @@ func (h *ArtifactsHandler) Create(c *gin.Context) {
|
||||
|
||||
var req createArtifactsRepoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -302,7 +302,7 @@ func (h *ArtifactsHandler) Fork(c *gin.Context) {
|
||||
|
||||
var req forkArtifactsRepoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -367,7 +367,7 @@ func (h *ArtifactsHandler) Token(c *gin.Context) {
|
||||
|
||||
var req artifactsTokenRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ func (h *BudgetHandler) PatchBudget(c *gin.Context) {
|
||||
// so we unmarshal into a raw map first.
|
||||
var raw map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&raw); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ func (h *BundleHandler) Export(c *gin.Context) {
|
||||
|
||||
b, err := bundle.Export(ctx, workspaceID, h.configsDir, h.docker)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "bundle not found"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ func (h *BundleHandler) Export(c *gin.Context) {
|
||||
func (h *BundleHandler) Import(c *gin.Context) {
|
||||
var b bundle.Bundle
|
||||
if err := c.ShouldBindJSON(&b); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid bundle"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -136,7 +136,7 @@ func (h *ChannelHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := adapter.ValidateConfig(body.Config); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid config: " + err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid channel config"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -294,7 +294,8 @@ func (h *ChannelHandler) Send(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.manager.SendOutbound(ctx, channelID, body.Text); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
log.Printf("Channels: send outbound failed for channel %s: %v", channelID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "send failed"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -307,7 +308,8 @@ func (h *ChannelHandler) Test(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
if err := h.manager.SendOutbound(ctx, channelID, "🔔 Molecule AI channel test — connection successful!"); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
log.Printf("Channels: test message failed for channel %s: %v", channelID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "test message failed"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -436,7 +438,7 @@ func (h *ChannelHandler) Webhook(c *gin.Context) {
|
||||
// Parse the webhook first to get the chat_id
|
||||
msg, err := adapter.ParseWebhook(c, nil)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "parse error: " + err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "webhook parse failed"})
|
||||
return
|
||||
}
|
||||
if msg == nil {
|
||||
|
||||
@ -71,7 +71,7 @@ func (h *CheckpointsHandler) Upsert(c *gin.Context) {
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -114,7 +114,7 @@ func (h *DelegationHandler) Delegate(c *gin.Context) {
|
||||
// the 400 response and returns the error so the caller can return.
|
||||
func bindDelegateRequest(c *gin.Context, body *delegateRequest) error {
|
||||
if err := c.ShouldBindJSON(body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid delegation request"})
|
||||
return err
|
||||
}
|
||||
if _, err := uuid.Parse(body.TargetID); err != nil {
|
||||
@ -344,7 +344,7 @@ func (h *DelegationHandler) Record(c *gin.Context) {
|
||||
DelegationID string `json:"delegation_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
if _, err := uuid.Parse(body.TargetID); err != nil {
|
||||
@ -392,7 +392,7 @@ func (h *DelegationHandler) UpdateStatus(c *gin.Context) {
|
||||
ResponsePreview string `json:"response_preview,omitempty"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
if body.Status != "completed" && body.Status != "failed" {
|
||||
|
||||
@ -302,7 +302,7 @@ func (h *DiscoveryHandler) CheckAccess(c *gin.Context) {
|
||||
TargetID string `json:"target_id" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -145,7 +145,7 @@ func (h *MemoriesHandler) Commit(c *gin.Context) {
|
||||
Namespace string `json:"namespace,omitempty"` // optional; defaults to "general"
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -116,7 +116,7 @@ func (h *MemoryHandler) Set(c *gin.Context) {
|
||||
IfMatchVersion *int64 `json:"if_match_version"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -250,7 +250,7 @@ func (h *OrgHandler) Import(c *gin.Context) {
|
||||
Template OrgTemplate `json:"template"` // or inline template
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -264,7 +264,7 @@ func (h *OrgHandler) Import(c *gin.Context) {
|
||||
// letting an unauthenticated caller probe arbitrary filesystem paths.
|
||||
resolved, err := resolveInsideRoot(h.orgDir, body.Dir)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid dir: %v", err)})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid org directory"})
|
||||
return
|
||||
}
|
||||
orgBaseDir = resolved
|
||||
@ -279,11 +279,11 @@ func (h *OrgHandler) Import(c *gin.Context) {
|
||||
// refactor. Fails loudly on missing / cyclic / escaping includes.
|
||||
expanded, err := resolveYAMLIncludes(data, orgBaseDir)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("!include expansion failed: %v", err)})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "org template expansion failed"})
|
||||
return
|
||||
}
|
||||
if err := yaml.Unmarshal(expanded, &tmpl); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid YAML: %v", err)})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid org template"})
|
||||
return
|
||||
}
|
||||
} else if body.Template.Name != "" {
|
||||
|
||||
@ -237,7 +237,7 @@ func (h *OrgPluginAllowlistHandler) PutAllowlist(c *gin.Context) {
|
||||
|
||||
var req putAllowlistRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
if req.EnabledBy == "" {
|
||||
|
||||
@ -45,7 +45,7 @@ func (h *PluginsHandler) Install(c *gin.Context) {
|
||||
|
||||
var req installRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -56,11 +56,9 @@ func (h *PluginsHandler) Install(c *gin.Context) {
|
||||
c.JSON(he.Status, he.Body)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "plugin install failed"})
|
||||
return
|
||||
}
|
||||
// On success, we own stagedDir cleanup. On error, resolveAndStage
|
||||
// has already cleaned it up (and its returned result is nil).
|
||||
defer os.RemoveAll(result.StagedDir)
|
||||
|
||||
// Org plugin allowlist gate (#591).
|
||||
@ -77,7 +75,7 @@ func (h *PluginsHandler) Install(c *gin.Context) {
|
||||
c.JSON(he.Status, he.Body)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "plugin deliver failed"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -96,7 +94,7 @@ func (h *PluginsHandler) Uninstall(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
if err := validatePluginName(pluginName); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid plugin name"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -179,7 +177,7 @@ func (h *PluginsHandler) Download(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
if err := validatePluginName(pluginName); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid plugin name"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -223,7 +221,7 @@ func (h *PluginsHandler) Download(c *gin.Context) {
|
||||
c.JSON(he.Status, he.Body)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "plugin download failed"})
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(result.StagedDir)
|
||||
|
||||
@ -126,13 +126,13 @@ func validateAgentURL(rawURL string) error {
|
||||
func (h *RegistryHandler) Register(c *gin.Context) {
|
||||
var payload models.RegisterPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
// C6: reject SSRF-capable URLs before persisting or caching them.
|
||||
if err := validateAgentURL(payload.URL); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -251,7 +251,7 @@ func (h *RegistryHandler) Register(c *gin.Context) {
|
||||
func (h *RegistryHandler) Heartbeat(c *gin.Context) {
|
||||
var payload models.HeartbeatPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -390,7 +390,7 @@ func (h *RegistryHandler) evaluateStatus(c *gin.Context, payload models.Heartbea
|
||||
func (h *RegistryHandler) UpdateCard(c *gin.Context) {
|
||||
var payload models.UpdateCardPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -114,7 +114,7 @@ func (h *ScheduleHandler) Create(c *gin.Context) {
|
||||
// Validate and compute next run
|
||||
nextRun, err := scheduler.ComputeNextRun(body.CronExpr, body.Timezone, time.Now())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ func (h *ScheduleHandler) Update(c *gin.Context) {
|
||||
}
|
||||
nextRun, err := scheduler.ComputeNextRun(cronExpr, tz, time.Now())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
nextRunAt = &nextRun
|
||||
|
||||
@ -222,7 +222,7 @@ func (h *SecretsHandler) Set(c *gin.Context) {
|
||||
Value string `json:"value" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -335,7 +335,7 @@ func (h *SecretsHandler) SetGlobal(c *gin.Context) {
|
||||
Value string `json:"value" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -122,7 +122,7 @@ func (h *TemplatesHandler) Import(c *gin.Context) {
|
||||
Files map[string]string `json:"files" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ func (h *TemplatesHandler) Import(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := writeFiles(destDir, body.Files); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ func (h *TemplatesHandler) ReplaceFiles(c *gin.Context) {
|
||||
Files map[string]string `json:"files" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@ func (h *TemplatesHandler) ReplaceFiles(c *gin.Context) {
|
||||
// Validate all paths first
|
||||
for relPath := range body.Files {
|
||||
if err := validateRelPath(relPath); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -227,7 +227,7 @@ func (h *TemplatesHandler) ReplaceFiles(c *gin.Context) {
|
||||
}
|
||||
os.MkdirAll(destDir, 0o755)
|
||||
if err := writeFiles(destDir, body.Files); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"status": "replaced", "workspace": workspaceID, "files": len(body.Files), "source": "template"})
|
||||
|
||||
@ -134,7 +134,7 @@ func (h *TemplatesHandler) ListFiles(c *gin.Context) {
|
||||
subPath := c.DefaultQuery("path", "")
|
||||
if subPath != "" {
|
||||
if err := validateRelPath(subPath); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -258,7 +258,7 @@ func (h *TemplatesHandler) ReadFile(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := validateRelPath(filePath); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -318,7 +318,7 @@ func (h *TemplatesHandler) WriteFile(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := validateRelPath(filePath); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -326,7 +326,7 @@ func (h *TemplatesHandler) WriteFile(c *gin.Context) {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -367,7 +367,7 @@ func (h *TemplatesHandler) DeleteFile(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := validateRelPath(filePath); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ func (h *ViewportHandler) Save(c *gin.Context) {
|
||||
Zoom float64 `json:"zoom"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid viewport data"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -76,14 +76,14 @@ func (h *WorkspaceHandler) TokenRegistry() *provisionhook.Registry {
|
||||
func (h *WorkspaceHandler) Create(c *gin.Context) {
|
||||
var payload models.CreateWorkspacePayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace payload"})
|
||||
return
|
||||
}
|
||||
|
||||
// #685/#688: validate field lengths and reject injection characters before
|
||||
// any DB or provisioner interaction.
|
||||
if err := validateWorkspaceFields(payload.Name, payload.Role, payload.Model, payload.Runtime); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace fields"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ func (h *WorkspaceHandler) Create(c *gin.Context) {
|
||||
var workspaceDir interface{}
|
||||
if payload.WorkspaceDir != "" {
|
||||
if err := validateWorkspaceDir(payload.WorkspaceDir); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace directory"})
|
||||
return
|
||||
}
|
||||
workspaceDir = payload.WorkspaceDir
|
||||
@ -145,7 +145,7 @@ func (h *WorkspaceHandler) Create(c *gin.Context) {
|
||||
workspaceAccess = provisioner.WorkspaceAccessNone
|
||||
}
|
||||
if err := provisioner.ValidateWorkspaceAccess(workspaceAccess, payload.WorkspaceDir); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace access"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -411,7 +411,7 @@ func (h *WorkspaceHandler) Get(c *gin.Context) {
|
||||
|
||||
// #687: reject non-UUID IDs before hitting the DB.
|
||||
if err := validateWorkspaceID(id); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace ID"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -569,13 +569,13 @@ func (h *WorkspaceHandler) Update(c *gin.Context) {
|
||||
|
||||
// #687: reject non-UUID IDs before hitting the DB.
|
||||
if err := validateWorkspaceID(id); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var body map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -591,7 +591,7 @@ func (h *WorkspaceHandler) Update(c *gin.Context) {
|
||||
if err := validateWorkspaceFields(
|
||||
strField("name"), strField("role"), "" /*model not patchable*/, strField("runtime"),
|
||||
); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace fields"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -644,7 +644,7 @@ func (h *WorkspaceHandler) Update(c *gin.Context) {
|
||||
if wsDir != nil {
|
||||
if dirStr, isStr := wsDir.(string); isStr && dirStr != "" {
|
||||
if err := validateWorkspaceDir(dirStr); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace directory"})
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -707,7 +707,7 @@ func (h *WorkspaceHandler) Delete(c *gin.Context) {
|
||||
|
||||
// #687: reject non-UUID IDs before hitting the DB.
|
||||
if err := validateWorkspaceID(id); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace ID"})
|
||||
return
|
||||
}
|
||||
|
||||
@ -864,7 +864,7 @@ func (h *WorkspaceHandler) Delete(c *gin.Context) {
|
||||
// Hard delete the workspace row
|
||||
if _, err := db.DB.ExecContext(ctx, "DELETE FROM workspaces WHERE id = ANY($1::uuid[])", purgeIDs); err != nil {
|
||||
log.Printf("Purge workspace row error for %v: %v", allIDs, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "purge failed: " + err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "purge failed"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"status": "purged", "cascade_deleted": len(descendantIDs)})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user