Files
kandev/apps/backend/internal/gateway/websocket/handler.go
T
João Salavisa 3746a30126 fix: enforce origin validation on websocket upgrades (#1698)
* fix: enforce origin validation on websocket upgrades

The gateway /ws upgrader hardcoded CheckOrigin to true, so any web page
could open a socket to the local backend and drive session/shell actions
(cross-site WebSocket hijacking → RCE on the host).

Both upgraders now share checkWebSocketOrigin: no Origin header is
allowed (non-browser clients), same-hostname and loopback↔loopback
origins are allowed, everything else is rejected. Replaces the terminal
handler's prefix-based check, which accepted origins like
http://localhost.attacker.tld.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: share origin trust policy between cors and websocket checks

Review feedback on #1698: the CORS middleware and the WebSocket upgrader
carried two copies of the same origin policy, which could silently
diverge. Both now delegate to httpmw.AllowedOrigin, which also rejects
URL components that never appear in a browser Origin header (userinfo,
path, query, fragment, opaque data).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 01:13:46 +01:00

81 lines
1.9 KiB
Go

package websocket
import (
"context"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
gorillaws "github.com/gorilla/websocket"
"go.uber.org/zap"
"github.com/kandev/kandev/internal/common/logger"
ws "github.com/kandev/kandev/pkg/websocket"
)
var upgrader = gorillaws.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: checkWebSocketOrigin,
}
// Handler handles WebSocket connections
type Handler struct {
hub *Hub
logger *logger.Logger
}
// NewHandler creates a new WebSocket handler
func NewHandler(hub *Hub, log *logger.Logger) *Handler {
return &Handler{
hub: hub,
logger: log.WithFields(zap.String("component", "ws_handler")),
}
}
// HandleConnection upgrades HTTP to WebSocket and handles messages
func (h *Handler) HandleConnection(c *gin.Context) {
// Validate token (optional for now)
token := c.Query("token")
if token == "" {
token = c.GetHeader("Authorization")
}
// TODO: Implement proper JWT validation
_ = token
// Upgrade connection to WebSocket
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
h.logger.Error("Failed to upgrade connection", zap.Error(err))
return
}
// Create a unique client ID
clientID := uuid.New().String()
h.logger.Debug("WebSocket connection established",
zap.String("client_id", clientID),
zap.String("remote_addr", c.Request.RemoteAddr),
)
// Create client and register with hub
client := NewClient(clientID, conn, h.hub, h.logger)
// Register client with hub
h.hub.Register(client)
// Start read and write pumps
go client.WritePump()
client.ReadPump(c.Request.Context())
}
// RegisterHealthHandler registers the health check handler
func RegisterHealthHandler(d *ws.Dispatcher) {
d.RegisterFunc(ws.ActionHealthCheck, func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
return ws.NewResponse(msg.ID, msg.Action, map[string]interface{}{
"status": "ok",
"service": "kandev",
"mode": "unified",
})
})
}