3746a30126
* 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>
81 lines
1.9 KiB
Go
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",
|
|
})
|
|
})
|
|
}
|