Skip to content
Open
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
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@
**Vulnerability:** The application used `c.Bind(&req)` to bind incoming JSON requests to structs but failed to subsequently call `c.Validate(&req)`. Because `c.Bind()` alone does not enforce `validate:"required"` tags, attackers could submit incomplete or malformed payloads without being rejected by validation rules, potentially leading to logic errors or unauthorized behaviors.
**Learning:** In the Echo framework, binding and validation are separate steps. `c.Bind()` does not automatically invoke validation logic based on struct tags.
**Prevention:** Always pair `c.Bind(&req)` with an explicit `c.Validate(&req)` (or `c.Validate(req)`) call to ensure that payload constraints and required fields are correctly enforced.

## 2025-06-07 - Ensure Authorization and Role Based Access Controls for Sensitive Operations
**Vulnerability:** The application was missing Role Based Access Control (RBAC) validations on several sensitive routes in `settings.go` and `tickets.go`. This essentially allowed standard "viewer" level users (or any authenticated user) to modify system environment configurations (`.env`), delete global risk-rules, or perform admin-level assignments of security tickets.
**Learning:** Even if routes are hidden in the frontend UI based on role, the backend must actively enforce those same role constraints via middleware to prevent API-level manipulation (authorization bypass).
**Prevention:** In the LabStack Echo framework, use sub-groups like `g.Group("", middleware.RequireRole(...))` to explicitly require the necessary roles on routes that perform state-changing operations or expose sensitive system details.
17 changes: 10 additions & 7 deletions internal/handler/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/cyberoptic/openvas-tracker/internal/config"
"github.com/cyberoptic/openvas-tracker/internal/database/queries"
"github.com/cyberoptic/openvas-tracker/internal/middleware"
"github.com/cyberoptic/openvas-tracker/internal/service"
)

Expand Down Expand Up @@ -251,11 +252,13 @@ func strPtr(s string) *string { return &s }
func (h *SettingsHandler) RegisterRoutes(g *echo.Group) {
g.GET("/setup", h.GetSetup)
g.GET("/users", h.ListUsers)
g.GET("/env", h.GetEnvConfig)
g.PUT("/env", h.UpdateEnvConfig)
g.PUT("/env/batch", h.UpdateEnvBatch)
g.POST("/ldap/test", h.TestLDAP)
g.GET("/risk-rules", h.ListRiskRules)
g.DELETE("/risk-rules/:id", h.DeleteRiskRule)
g.POST("/risk-rules/apply", h.ApplyRiskRules)

admin := g.Group("", middleware.RequireRole("admin"))
admin.GET("/env", h.GetEnvConfig)
admin.PUT("/env", h.UpdateEnvConfig)
admin.PUT("/env/batch", h.UpdateEnvBatch)
admin.POST("/ldap/test", h.TestLDAP)
admin.GET("/risk-rules", h.ListRiskRules)
admin.DELETE("/risk-rules/:id", h.DeleteRiskRule)
admin.POST("/risk-rules/apply", h.ApplyRiskRules)
}
11 changes: 7 additions & 4 deletions internal/handler/tickets.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,15 @@ func (h *TicketHandler) RegisterRoutes(g *echo.Group) {
g.POST("", h.Create)
g.GET("", h.List)
g.GET("/:id", h.Get)
g.PATCH("/:id/status", h.UpdateStatus)
g.PATCH("/:id/assign", h.Assign)
g.POST("/:id/comments", h.AddComment)
g.GET("/:id/comments", h.ListComments)
g.GET("/:id/activity", h.ListActivity)
g.GET("/:id/also-affected", h.AlsoAffected)
g.POST("/:id/risk-rule", h.CreateRiskRule)
g.POST("/bulk", h.BulkUpdate)

// Status/assign require admin or analyst role
protected := g.Group("", middleware.RequireRole("admin", "analyst"))
protected.PATCH("/:id/status", h.UpdateStatus)
protected.PATCH("/:id/assign", h.Assign)
protected.POST("/:id/risk-rule", h.CreateRiskRule)
protected.POST("/bulk", h.BulkUpdate)
}