GitHub Security Advisory: GHSA-fwg7-53p4-g33c
Package: github.com/lin-snow/ech0 (GO)
Severity: MODERATE (CVSS 5.5)
CWE: CWE-862: Missing Authorization
Vulnerable versions: < 4.4.3
Patched in: 4.4.3

Summary: Ech0 Comment Panel Endpoints Missing RequireScopes Middleware — Scoped Access Token Bypass

Details:
## Summary

All 9 comment panel admin endpoints (`/api/panel/comments/*`) are missing `RequireScopes()` middleware, while every other admin endpoint in the application enforces scope-based authorization on access tokens. An admin-issued access token scoped to minimal permissions (e.g., `echo:read` only) can perform full comment moderation operations including listing, approving, rejecting, deleting comments, and modifying comment system settings.

## Details

The access token scope enforcement system works as follows: `JWTAuthMiddleware` (`internal/middleware/auth.go`) parses any valid JWT and injects a viewer into the request context. The `RequireScopes()` middleware (`internal/middleware/scope.go:14`) then checks whether the token is an access token and, if so, validates that it carries the required scopes. Session tokens are passed through without scope checks (by design — sessions represent full user authority).

Every admin route group applies `RequireScopes()` per-handler:

- `internal/router/echo.go` — uses `RequireScopes(ScopeEchoWrite)` / `RequireScopes(ScopeEchoRead)`
- `internal/router/file.go` — uses `RequireScopes(ScopeFileRead)` / `RequireScopes(ScopeFileWrite)`
- `internal/router/user.go` — uses `RequireScopes(ScopeAdminUser)` / `RequireScopes(ScopeProfileRead)`
- `internal/router/setting.go` — uses `RequireScopes(ScopeAdminSettings)` / `RequireScopes(ScopeAdminToken)`

However, `internal/router/comment.go:28-36` registers all 9 panel endpoints directly on `AuthRouterGroup` without any `RequireScopes()` call:

```go
// internal/router/comment.go:28-36
appRouterGroup.AuthRouterGroup.GET("/panel/comments", h.CommentHandler.ListPanelComments())
appRouterGroup.AuthRouterGroup.GET("/panel/comments/:id", h.CommentHandler.GetCommentByID())
appRouterGroup.AuthRouterGroup.PATCH("/panel/comments/:id/status", h.CommentHandler.UpdateCommentStatus())
appRouterGroup.AuthRouterGroup.PATCH("/panel/comments/:id/hot", h.CommentHandler.UpdateCommentHot())
appRouterGroup.AuthRouterGroup.DELETE("/panel/comments/:id", h.CommentHandler.DeleteComment())
appRouterGroup.AuthRouterGroup.POST("/panel/comments/batch", h.CommentHandler.BatchAction())
appRouterGroup.AuthRouterGroup.GET("/panel/comments/settings", h.CommentHandler.GetCommentSetting())
appRouterGroup.AuthRouterGroup.PUT("/panel/comments/settings", h.CommentHandler.UpdateCommentSetting())
appRouterGroup.AuthRouterGroup.POST("/panel/comments/settings/test-email", h.CommentHandler.TestCommentEmail())
```

The service layer's `requireAdmin()` (`internal/service/comment/comment.go:719-732`) only validates the user's database role (`IsAdmin`/`IsOwner`), not the token's scopes:

```go
func (s *CommentService) requireAdmin(ctx context.Context) error {
    v := viewer.MustFromContext(ctx)
    if v == nil || strings.TrimSpace(v.UserID()) == "" {
        return commonModel.NewBizError(...)
    }
    user, err := s.commonService.CommonGetUserByUserId(ctx, v.UserID())
    if err != nil { return err }
    if !u