CVE-2026-22874: Gitea Incomplete SSRF Protection Vulnerability

CRITICAL
Advisory Identifier: GHSA-2r5c-gw76-rh3w
CVSS 9.6
CWE-918
Published
Advisory Summary

CVE-2026-22874 is a CRITICAL (CVSS 9.6) incomplete ssrf protection vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Gitea libraries in the Go ecosystem. The issue involves incomplete ssrf protection in webhook and migration allow-list default filter. Remediation is available by upgrading affected packages to version 1.26.3.

CVE-2026-22874 at a Glance

CVE Identifier CVE-2026-22874
GHSA Identifier GHSA-2r5c-gw76-rh3w
Severity CRITICAL
CVSS v3.1 Base Score 9.6
CWE Classification CWE-918: Server-Side Request Forgery (SSRF)
Affected Product Gitea
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 9.6

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity High (I:H)
Availability None (A:N)

How to Fix CVE-2026-22874

To remediate this vulnerability, upgrade all affected Gitea dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

code.gitea.io/gitea fixed: 1.26.3
Upgrade code.gitea.io/gitea to 1.26.3
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for code.gitea.io/gitea

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-22874, inspect your project dependencies for Gitea:

inspect dependencies for code.gitea.io/gitea

Analyze the terminal output to locate any version of Gitea in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.26.3), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Gitea, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary Gitea's default SSRF allow-list ([`MatchBuiltinExternal`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L26-L27), used by both webhook delivery and repository migrations) relies on Go's standard library [`net.IP.IsPrivate()`](https://pkg.go.dev/net#IP.IsPrivate), which only covers RFC 1918 and RFC 4193. As a result, several IP ranges commonly used for cloud metadata services, internal networks, and IPv6 transition mechanisms are not blocked, allowing authenticated users to send HTTP requests to those destinations and read the responses via the webhook history UI. ## Details The vulnerability lives in [`HostMatchList.checkIP`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L96-L114), specifically [line 103](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L103): ```go case MatchBuiltinExternal: if ip.IsGlobalUnicast() && !ip.IsPrivate() { return true } ``` `net.IP.IsPrivate()` recognises only: - `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` (RFC 1918) - `fc00::/7` (RFC 4193 IPv6 ULA) It does **not** recognise: | Range | Description | |---|---| | `100.64.0.0/10` | RFC 6598 Carrier-Grade NAT | | `168.63.129.16/32` | Azure WireServer metadata endpoint | | `172.32.0.0/11` | Non-RFC1918 portion of `172.0.0.0/8` (real-world internal use) | | `64:ff9b::/96` | RFC 6052 IPv6 NAT64 (can embed `169.254.169.254`) | | `2001::/32` | RFC 4380 Teredo tunneling | | `2002::/16` | RFC 3056 6to4 | | `2001:db8::/32` | RFC 3849 documentation | The default is reached by webhook delivery at [`services/webhook/deliver.go#L312-L316`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L312-L316) and by repository migrations at [`services/migrations/migrate.go#L522`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/migrations/migrate.go#L522). The SSRF is **not blind**. Webhook delivery captures the response status, headers, and up to 1 MiB of body ([`services/webhook/deliver.go#L259-L270`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L259-L270)) and renders them in the webhook history UI ([`templates/repo/settings/webhook/history.tmpl#L75-L85`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/templates/repo/settings/webhook/history.tmpl#L75-L85)), so attackers can read everything the targeted internal service returns. ## Impact An authenticated user who can create or modify a webhook can: - Reach cloud metadata endpoints (AWS IMDS via NAT64 `64:ff9b::a9fe:a9fe`, Azure WireServer `168.63.129.16`) - Probe and exfiltrate from internal services on CGNAT (`100.64.0.0/10`) or non-RFC1918 172.x ranges - Read full HTTP response bodies through the webhook history UI The same default is applied to repository migrations, broadening the attack surface to users who can trigger a migration. ## Proof of Concept The attached patch ([`gitea_ssrf_test.patch`](#patch)) adds [`TestSSRFBypassRanges`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher_test.go) to the existing test file. It uses [Go subtests](https://go.dev/blog/subtests) so each vulnerable range is its own named failing test case. Run with: ``` go test -v ./modules/hostmatcher -run TestSSRFBypassRanges ``` Expected result on `4c37f4dacbac022f7beca75272439331f0368830`: - **8 PASS** — RFC 1918, IPv6 private ranges, and legitimate public IPs (control cases) - **10 FAIL** — Each failing subtest is a documented SSRF bypass Sample failure output: ``` --- FAIL: TestSSRFBypassRanges/CGNAT_100.64.0.0/10_(RFC_6598) Error: Not equal: expected: false, actual: true Messages: ip=100.64.0.1 --- FAIL: TestSSRFBypassRanges/Azure_WireServer_168.63.129.16 Error: Not equal: expected: false, actual: true Messages: ip=168.63.129.16 --- FAIL: TestSSRFBypassRanges/IPv6_NAT64_embedded_AWS_IMDS_169.254.169.254 Error: Not equal: expected: false, actual: true Messages: ip=64:ff9b::a9fe:a9fe ``` ## Suggested Remediation I'd suggest treating the design of [`MatchBuiltinExternal`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L96-L114) as the bug — `IsPrivate()` is too narrow a definition of "internal." A comprehensive deny-list approach is what's needed here. For reference, CC-Tweaked's [`AddressPredicate.PrivatePattern`](https://github.com/cc-tweaked/CC-Tweaked/blob/3e7ce15ba6d5ab030092850f7e49829b64ba3555/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java#L116-L169) is a good reference list, blocking each of the ranges named in the table above plus a few others (multicast, broadcast, TEST-NET, etc.). The exact remediation is at your discretion. ## References - Vulnerable function: [`modules/hostmatcher/hostmatcher.go#L96-L114`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L96-L114) - Default for webhooks: [`services/webhook/deliver.go#L312-L316`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L312-L316) - Default for migrations: [`services/migrations/migrate.go#L522`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/migrations/migrate.go#L522) - Response captured: [`services/webhook/deliver.go#L259-L270`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L259-L270) - Response rendered: [`templates/repo/settings/webhook/history.tmpl#L75-L85`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/templates/repo/settings/webhook/history.tmpl#L75-L85) - Go stdlib `net.IP.IsPrivate()`: <https://pkg.go.dev/net#IP.IsPrivate> - CC-Tweaked reference deny-list: [`AddressPredicate.java#L116-L169`](https://github.com/cc-tweaked/CC-Tweaked/blob/3e7ce15ba6d5ab030092850f7e49829b64ba3555/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java#L116-L169) - Go subtests pattern: <https://go.dev/blog/subtests> - RFC 6598 (CGNAT), RFC 6052 (NAT64), RFC 4380 (Teredo), RFC 3056 (6to4), RFC 3849 (documentation) --- ## Patch <a name="patch"></a> Proposed `gitea_ssrf_test.patch`: ```diff diff --git a/modules/hostmatcher/hostmatcher_test.go b/modules/hostmatcher/hostmatcher_test.go index c781847..0b39e60 100644 --- a/modules/hostmatcher/hostmatcher_test.go +++ b/modules/hostmatcher/hostmatcher_test.go @@ -159,3 +159,56 @@ func TestHostOrIPMatchesList(t *testing.T) { } test(cases) } + +// TestSSRFBypassRanges verifies that the "external" filter (the default used by +// webhook delivery and repository migrations) blocks dangerous IP ranges. +// +// Each subtest's `allowed` field is the expected return value of MatchHostOrIP: +// - false: the IP should be blocked (rejected by the filter) +// - true: the IP should be allowed (a legitimate public destination) +// +// Subtests that FAIL are documented SSRF bypasses: IP ranges that should be +// blocked but are incorrectly allowed because the underlying check relies on +// net.IP.IsPrivate(), which only covers RFC 1918 and RFC 4193. +func TestSSRFBypassRanges(t *testing.T) { + type tc struct { + ip net.IP + allowed bool + } + + hl := ParseHostMatchList("", "external") + + cases := map[string]tc{ + // RFC 1918 / IPv6 private ranges - correctly blocked + "RFC1918 10.0.0.0/8": {net.ParseIP("10.0.0.1"), false}, + "RFC1918 172.16.0.0/12": {net.ParseIP("172.16.0.1"), false}, + "RFC1918 192.168.0.0/16": {net.ParseIP("192.168.1.1"), false}, + "IPv6 loopback ::1": {net.ParseIP("::1"), false}, + "IPv6 link-local fe80::/10": {net.ParseIP("fe80::1"), false}, + "IPv6 ULA fd00::/8": {net.ParseIP("fd00::1"), false}, + + // Legitimate public IPs - correctly allowed + "Public IPv4 (Google DNS)": {net.ParseIP("8.8.8.8"), true}, + "Public IPv6 (Google DNS)": {net.ParseIP("2001:4860:4860::8888"), true}, + + // SSRF bypasses - the assertions below intentionally describe the + // expected secure behavior (allowed=false). Each failing subtest is + // a documented bypass. + "CGNAT 100.64.0.0/10 (RFC 6598)": {net.ParseIP("100.64.0.1"), false}, + "CGNAT 100.127.255.254 (RFC 6598)": {net.ParseIP("100.127.255.254"), false}, + "Azure WireServer 168.63.129.16": {net.ParseIP("168.63.129.16"), false}, + "Non-RFC1918 172.32.0.0/11": {net.ParseIP("172.32.0.1"), false}, + "Non-RFC1918 172.45.0.0/16": {net.ParseIP("172.45.0.1"), false}, + "IPv6 NAT64 64:ff9b::/96 (RFC 6052)": {net.ParseIP("64:ff9b::1"), false}, + "IPv6 NAT64 embedded AWS IMDS 169.254.169.254": {net.ParseIP("64:ff9b::a9fe:a9fe"), false}, + "IPv6 Teredo 2001::/32 (RFC 4380)": {net.ParseIP("2001::1"), false}, + "IPv6 6to4 2002::/16 (RFC 3056)": {net.ParseIP("2002::1"), false}, + "IPv6 documentation 2001:db8::/32 (RFC 3849)": {net.ParseIP("2001:db8::1"), false}, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + assert.Equalf(t, c.allowed, hl.MatchHostOrIP("", c.ip), "ip=%v", c.ip) + }) + } +} ``` ## Credit This vulnerability was uncovered by @JLLeitschuh of the @braze-inc security team.

CVE-2026-22874 Affected Gitea Versions

1 component(s)
Fixed Versions:
1.26.3
Product / Package Ecosystem Affected Versions Fixed Version
code.gitea.io/gitea
Go
< 1.26.3
1.26.3

References

Frequently Asked Questions

What is CVE-2026-22874?
CVE-2026-22874 is a security vulnerability in Gitea involving Incomplete SSRF Protection in Webhook and Migration Allow-list Default Filter under CWE-918: Server-Side Request Forgery (SSRF).
Which Gitea versions are affected?
Affected versions include releases prior to 1.26.3 across affected packages: code.gitea.io/gitea.
What version fixes CVE-2026-22874?
Upgrading to 1.26.3 (or the latest patched release for your respective branch) resolves the vulnerability.
Is code.gitea.io/gitea affected?
Yes, code.gitea.io/gitea is affected in releases prior to 1.26.3. Upgrade your dependency to 1.26.3 or later.
What is the CVSS score of CVE-2026-22874?
CVE-2026-22874 has a CVSS v3.1 base score of 9.6 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N.
What CWE is associated with CVE-2026-22874?
CVE-2026-22874 is classified under CWE-918: Server-Side Request Forgery (SSRF) (The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.).
How can I check Go dependencies for CVE-2026-22874?
Run the dependency tree command: 'inspect dependencies for code.gitea.io/gitea'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-2r5c-gw76-rh3w",
  "modified" : "2026-07-21T20:27:17Z",
  "published" : "2026-07-21T20:27:17Z",
  "aliases" : [ "CVE-2026-22874" ],
  "summary" : "Gitea: Incomplete SSRF Protection in Webhook and Migration Allow-list Default Filter",
  "details" : "## Summary\n\nGitea's default SSRF allow-list ([`MatchBuiltinExternal`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L26-L27), used by both webhook delivery and repository migrations) relies on Go's standard library [`net.IP.IsPrivate()`](https://pkg.go.dev/net#IP.IsPrivate), which only covers RFC 1918 and RFC 4193. As a result, several IP ranges commonly used for cloud metadata services, internal networks, and IPv6 transition mechanisms are not blocked, allowing authenticated users to send HTTP requests to those destinations and read the responses via the webhook history UI.\n\n## Details\n\nThe vulnerability lives in [`HostMatchList.checkIP`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L96-L114), specifically [line 103](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L103):\n\n```go\ncase MatchBuiltinExternal:\n    if ip.IsGlobalUnicast() && !ip.IsPrivate() {\n        return true\n    }\n```\n\n`net.IP.IsPrivate()` recognises only:\n- `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` (RFC 1918)\n- `fc00::/7` (RFC 4193 IPv6 ULA)\n\nIt does **not** recognise:\n\n| Range | Description |\n|---|---|\n| `100.64.0.0/10` | RFC 6598 Carrier-Grade NAT |\n| `168.63.129.16/32` | Azure WireServer metadata endpoint |\n| `172.32.0.0/11` | Non-RFC1918 portion of `172.0.0.0/8` (real-world internal use) |\n| `64:ff9b::/96` | RFC 6052 IPv6 NAT64 (can embed `169.254.169.254`) |\n| `2001::/32` | RFC 4380 Teredo tunneling |\n| `2002::/16` | RFC 3056 6to4 |\n| `2001:db8::/32` | RFC 3849 documentation |\n\nThe default is reached by webhook delivery at [`services/webhook/deliver.go#L312-L316`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L312-L316) and by repository migrations at [`services/migrations/migrate.go#L522`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/migrations/migrate.go#L522).\n\nThe SSRF is **not blind**. Webhook delivery captures the response status, headers, and up to 1 MiB of body ([`services/webhook/deliver.go#L259-L270`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L259-L270)) and renders them in the webhook history UI ([`templates/repo/settings/webhook/history.tmpl#L75-L85`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/templates/repo/settings/webhook/history.tmpl#L75-L85)), so attackers can read everything the targeted internal service returns.\n\n## Impact\n\nAn authenticated user who can create or modify a webhook can:\n\n- Reach cloud metadata endpoints (AWS IMDS via NAT64 `64:ff9b::a9fe:a9fe`, Azure WireServer `168.63.129.16`)\n- Probe and exfiltrate from internal services on CGNAT (`100.64.0.0/10`) or non-RFC1918 172.x ranges\n- Read full HTTP response bodies through the webhook history UI\n\nThe same default is applied to repository migrations, broadening the attack surface to users who can trigger a migration.\n\n## Proof of Concept\n\nThe attached patch ([`gitea_ssrf_test.patch`](#patch)) adds [`TestSSRFBypassRanges`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher_test.go) to the existing test file. It uses [Go subtests](https://go.dev/blog/subtests) so each vulnerable range is its own named failing test case.\n\nRun with:\n```\ngo test -v ./modules/hostmatcher -run TestSSRFBypassRanges\n```\n\nExpected result on `4c37f4dacbac022f7beca75272439331f0368830`:\n- **8 PASS** — RFC 1918, IPv6 private ranges, and legitimate public IPs (control cases)\n- **10 FAIL** — Each failing subtest is a documented SSRF bypass\n\nSample failure output:\n```\n--- FAIL: TestSSRFBypassRanges/CGNAT_100.64.0.0/10_(RFC_6598)\n    Error: Not equal: expected: false, actual: true\n    Messages: ip=100.64.0.1\n--- FAIL: TestSSRFBypassRanges/Azure_WireServer_168.63.129.16\n    Error: Not equal: expected: false, actual: true\n    Messages: ip=168.63.129.16\n--- FAIL: TestSSRFBypassRanges/IPv6_NAT64_embedded_AWS_IMDS_169.254.169.254\n    Error: Not equal: expected: false, actual: true\n    Messages: ip=64:ff9b::a9fe:a9fe\n```\n\n## Suggested Remediation\n\nI'd suggest treating the design of [`MatchBuiltinExternal`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L96-L114) as the bug — `IsPrivate()` is too narrow a definition of \"internal.\" A comprehensive deny-list approach is what's needed here. For reference, CC-Tweaked's [`AddressPredicate.PrivatePattern`](https://github.com/cc-tweaked/CC-Tweaked/blob/3e7ce15ba6d5ab030092850f7e49829b64ba3555/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java#L116-L169) is a good reference list, blocking each of the ranges named in the table above plus a few others (multicast, broadcast, TEST-NET, etc.).\n\nThe exact remediation is at your discretion. \n\n## References\n\n- Vulnerable function: [`modules/hostmatcher/hostmatcher.go#L96-L114`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/modules/hostmatcher/hostmatcher.go#L96-L114)\n- Default for webhooks: [`services/webhook/deliver.go#L312-L316`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L312-L316)\n- Default for migrations: [`services/migrations/migrate.go#L522`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/migrations/migrate.go#L522)\n- Response captured: [`services/webhook/deliver.go#L259-L270`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/services/webhook/deliver.go#L259-L270)\n- Response rendered: [`templates/repo/settings/webhook/history.tmpl#L75-L85`](https://github.com/go-gitea/gitea/blob/4c37f4dacbac022f7beca75272439331f0368830/templates/repo/settings/webhook/history.tmpl#L75-L85)\n- Go stdlib `net.IP.IsPrivate()`: <https://pkg.go.dev/net#IP.IsPrivate>\n- CC-Tweaked reference deny-list: [`AddressPredicate.java#L116-L169`](https://github.com/cc-tweaked/CC-Tweaked/blob/3e7ce15ba6d5ab030092850f7e49829b64ba3555/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java#L116-L169)\n- Go subtests pattern: <https://go.dev/blog/subtests>\n- RFC 6598 (CGNAT), RFC 6052 (NAT64), RFC 4380 (Teredo), RFC 3056 (6to4), RFC 3849 (documentation)\n\n---\n\n## Patch <a name=\"patch\"></a>\n\nProposed `gitea_ssrf_test.patch`:\n\n```diff\ndiff --git a/modules/hostmatcher/hostmatcher_test.go b/modules/hostmatcher/hostmatcher_test.go\nindex c781847..0b39e60 100644\n--- a/modules/hostmatcher/hostmatcher_test.go\n+++ b/modules/hostmatcher/hostmatcher_test.go\n@@ -159,3 +159,56 @@ func TestHostOrIPMatchesList(t *testing.T) {\n \t}\n \ttest(cases)\n }\n+\n+// TestSSRFBypassRanges verifies that the \"external\" filter (the default used by\n+// webhook delivery and repository migrations) blocks dangerous IP ranges.\n+//\n+// Each subtest's `allowed` field is the expected return value of MatchHostOrIP:\n+//   - false: the IP should be blocked (rejected by the filter)\n+//   - true:  the IP should be allowed (a legitimate public destination)\n+//\n+// Subtests that FAIL are documented SSRF bypasses: IP ranges that should be\n+// blocked but are incorrectly allowed because the underlying check relies on\n+// net.IP.IsPrivate(), which only covers RFC 1918 and RFC 4193.\n+func TestSSRFBypassRanges(t *testing.T) {\n+\ttype tc struct {\n+\t\tip      net.IP\n+\t\tallowed bool\n+\t}\n+\n+\thl := ParseHostMatchList(\"\", \"external\")\n+\n+\tcases := map[string]tc{\n+\t\t// RFC 1918 / IPv6 private ranges - correctly blocked\n+\t\t\"RFC1918 10.0.0.0/8\":        {net.ParseIP(\"10.0.0.1\"), false},\n+\t\t\"RFC1918 172.16.0.0/12\":     {net.ParseIP(\"172.16.0.1\"), false},\n+\t\t\"RFC1918 192.168.0.0/16\":    {net.ParseIP(\"192.168.1.1\"), false},\n+\t\t\"IPv6 loopback ::1\":         {net.ParseIP(\"::1\"), false},\n+\t\t\"IPv6 link-local fe80::/10\": {net.ParseIP(\"fe80::1\"), false},\n+\t\t\"IPv6 ULA fd00::/8\":         {net.ParseIP(\"fd00::1\"), false},\n+\n+\t\t// Legitimate public IPs - correctly allowed\n+\t\t\"Public IPv4 (Google DNS)\": {net.ParseIP(\"8.8.8.8\"), true},\n+\t\t\"Public IPv6 (Google DNS)\": {net.ParseIP(\"2001:4860:4860::8888\"), true},\n+\n+\t\t// SSRF bypasses - the assertions below intentionally describe the\n+\t\t// expected secure behavior (allowed=false). Each failing subtest is\n+\t\t// a documented bypass.\n+\t\t\"CGNAT 100.64.0.0/10 (RFC 6598)\":              {net.ParseIP(\"100.64.0.1\"), false},\n+\t\t\"CGNAT 100.127.255.254 (RFC 6598)\":            {net.ParseIP(\"100.127.255.254\"), false},\n+\t\t\"Azure WireServer 168.63.129.16\":              {net.ParseIP(\"168.63.129.16\"), false},\n+\t\t\"Non-RFC1918 172.32.0.0/11\":                   {net.ParseIP(\"172.32.0.1\"), false},\n+\t\t\"Non-RFC1918 172.45.0.0/16\":                   {net.ParseIP(\"172.45.0.1\"), false},\n+\t\t\"IPv6 NAT64 64:ff9b::/96 (RFC 6052)\":          {net.ParseIP(\"64:ff9b::1\"), false},\n+\t\t\"IPv6 NAT64 embedded AWS IMDS 169.254.169.254\": {net.ParseIP(\"64:ff9b::a9fe:a9fe\"), false},\n+\t\t\"IPv6 Teredo 2001::/32 (RFC 4380)\":            {net.ParseIP(\"2001::1\"), false},\n+\t\t\"IPv6 6to4 2002::/16 (RFC 3056)\":              {net.ParseIP(\"2002::1\"), false},\n+\t\t\"IPv6 documentation 2001:db8::/32 (RFC 3849)\": {net.ParseIP(\"2001:db8::1\"), false},\n+\t}\n+\n+\tfor name, c := range cases {\n+\t\tt.Run(name, func(t *testing.T) {\n+\t\t\tassert.Equalf(t, c.allowed, hl.MatchHostOrIP(\"\", c.ip), \"ip=%v\", c.ip)\n+\t\t})\n+\t}\n+}\n```\n\n## Credit\n\nThis vulnerability was uncovered by @JLLeitschuh of the @braze-inc security team.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "code.gitea.io/gitea"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.26.3"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/security/advisories/GHSA-2r5c-gw76-rh3w"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-22874"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/pull/38059"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/pull/38173"
  }, {
    "type" : "WEB",
    "url" : "https://blog.gitea.com/release-of-1.26.3-and-1.26.4"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/go-gitea/gitea"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/releases/tag/v1.26.3"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-918" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-21T20:27:17Z",
    "nvd_published_at" : "2026-07-03T21:16:57Z"
  }
}