Broken Authentication

Broken Authentication in Go (Gin) Remediation [Mar 2026] [CVE-2026-32815]

[Updated Mar 2026] Updated CVE-2026-32815

Overview

CVE-2026-32815 exposes a broken authentication flaw where SiYuan versions 3.6.0 and below allow unauthenticated access to the WebSocket endpoint (/ws) if specific URL parameters are supplied (?app=siyuan&id=auth&type=auth). This bypass enables any external client, including scripts on malicious sites, to establish a cross-origin WebSocket connection and receive server push events in real time. Because the Origin header was not validated, a malicious site can silently connect to a victim's local instance and observe sensitive note-taking activity, including document metadata such as titles, notebook names, file paths, and all authenticated users’ CRUD operations. The issue’s root cause aligns with CWE-287 (Improper Authentication) and was fixed in version 3.6.1. In Go (Gin) contexts, this class of vulnerability manifests when a WebSocket upgrade is performed without ensuring the user is authenticated and without validating the request origin, allowing attackers to subscribe to event streams they should not access. This guide references CVE-2026-32815 specifically and explains how to remediate in Go (Gin).

Affected Versions

3.6.0 and below

Code Fix Example

Go (Gin) API Security Remediation
// Vulnerable pattern (demonstration)
package main

import (
  "net/http"
  "github.com/gin-gonic/gin"
  "github.com/gorilla/websocket"
)

var vulnUpgrader = websocket.Upgrader{ // origin not restricted
  CheckOrigin: func(r *http.Request) bool { return true }
}

func vulnerableWS(c *gin.Context) {
  ws, err := vulnUpgrader.Upgrade(c.Writer, c.Request, nil)
  if err != nil {
    return
  }
  defer ws.Close()
  for {
    _, msg, err := ws.ReadMessage()
    if err != nil {
      break
    }
    ws.WriteMessage(websocket.TextMessage, msg)
  }
}

var safeUpgrader = websocket.Upgrader{
  CheckOrigin: func(r *http.Request) bool {
    origin := r.Header.Get("Origin")
    // example allowlist; tailor to your domains
    return origin == "http://localhost:8080" || origin == "https://your.app.example"
  },
}

func authMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    token := c.GetHeader("Authorization")
    if token == "" {
      c.AbortWithStatus(http.StatusUnauthorized)
      return
    }
    c.Set("user", token)
    c.Next()
  }
}

func fixedWS(c *gin.Context) {
  ws, err := safeUpgrader.Upgrade(c.Writer, c.Request, nil)
  if err != nil {
    return
  }
  defer ws.Close()
  user, _ := c.Get("user")
  _ = user
  for {
    _, msg, err := ws.ReadMessage()
    if err != nil {
      break
    }
    ws.WriteMessage(websocket.TextMessage, msg)
  }
}

func main() {
  r := gin.Default()
  r.GET("/ws/vuln", vulnerableWS)
  r.GET("/ws/fix", authMiddleware(), fixedWS)
  r.Run(":8080")
}

CVE References

Choose which optional cookies to allow. You can change this any time.