Every CVESearch/Get API key

CVE-2026-86041

ezBookkeeping: Source-IP allowlist (MCP & API token) is bypassable via a spoofed `X-Forwarded-For` header

5.3 MediumFix available

CVE-2026-86041 is a medium vulnerability (CVSS 5.3) in Mayswind Ezbookkeeping, published November 4, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.22%, the 12th percentile. Fixed in github.com/mayswind/ezbookkeeping 1.6.0.

Severity
5.3 MediumCVSS 4.0, CVE List
Exploit chance
0.22%EPSS, 12th percentile
Exploitation
None knownNot on CISA’s or ENISA’s lists
Fixed in
1.6.0github.com/mayswind/ezbookkeeping

Description

ezBookkeeping is an open source, self-hosted personal finance application. From 0.10.0 until 1.6.0, ezBookkeeping constructs its Gin router with gin.New() without configuring SetTrustedProxies, causing c.ClientIP() to trust a spoofed X-Forwarded-For value. An unauthenticated remote attacker can supply an allowlisted address to bypass the mcp_allowed_remote_ips or api_token_allowed_remote_ips gate. A valid MCP or API token remains required for protected actions. When such actions occur, the same spoofed value is stored in Transaction.CreatedIp and the request log, attributing activity to the trusted host instead of the attacker. This issue is fixed in version 1.6.0.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
Low
Availability
None

Exploit likelihood

0.22%Unchanged since Nov 5, 2026
90%50%10%1%

Affected products

Affected packages

Affected packages
PackageAffectedFixed
github.com/mayswind/ezbookkeeping
Go
0.10.0 up to 1.6.0
1.6.0

What each source reports

CVE List Score used5.3

ezBookkeeping: Source-IP allowlist (MCP & API token) is bypassable via a spoofed `X-Forwarded-For` header

ezBookkeeping is an open source, self-hosted personal finance application. From 0.10.0 until 1.6.0, ezBookkeeping constructs its Gin router with gin.New() without configuring SetTrustedProxies, causing c.ClientIP() to trust a spoofed X-Forwarded-For value. An unauthenticated remote attacker can supply an allowlisted address to bypass the mcp_allowed_remote_ips or api_token_allowed_remote_ips gate. A valid MCP or API token remains required for protected actions. When such actions occur, the same spoofed value is stored in Transaction.CreatedIp and the request log, attributing activity to the trusted host instead of the attacker. This issue is fixed in version 1.6.0.

Record
CVE-2026-86041
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-348
Updated
2026-11-05
EUVDEUVD-2026-1171625.3

ezBookkeeping: Source-IP allowlist (MCP & API token) is bypassable via a spoofed X-Forwarded-For header

Record
EUVD-2026-117162
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-09
GitHub AdvisoriesGHSA-wchh-mrv2-5h4q5.3

ezBookkeeping: Source-IP allowlist (MCP & API token) is bypassable via a spoofed `X-Forwarded-For` header

Summary

A remote attacker from any IP address defeats the operator-configured source-IP allowlist (mcp_allowed_remote_ips / api_token_allowed_remote_ips) by sending a single X-Forwarded-For: <allowlisted-ip> header. The router is created with gin.New() and never calls SetTrustedProxies, so gin trusts every proxy by default and c.ClientIP() returns the attacker-supplied header verbatim. The same forged value is also written to Transaction.CreatedIp and the access log, so the attacker's real IP never appears in any record.

Details

cmd/webserver.go:104 builds the router with gin.New(). A grep of cmd/ and pkg/ confirms SetTrustedProxies is never called, leaving gin's default trusted-proxy range of 0.0.0.0/0 and ::/0. WebContext (pkg/core/context_web.go:39) embeds *gin.Context and does not override ClientIP().

Both allowlist middlewares match the operator allowlist against this attacker-controlled value:

// pkg/middlewares/mcp_server_ip_limit.go:19
if config.MCPAllowedRemoteIPs[i].Match(c.ClientIP()) { c.Next(); return }

The identical c.ClientIP() is consumed by the audit trail at pkg/api/transactions.go:1078 (createNewTransactionModel(..., c.ClientIP()) → CreatedIp) and the access log (pkg/middlewares/request_log.go:28), so one header both bypasses the gate and forges attribution.

PoC

Runtime PoC (no live deployment required) — pkg/middlewares/xff_bypass_runtime_test.go, pkg/middlewares/xff_chain_runtime_test.go:

go test ./pkg/middlewares/ -run 'TestACCESSCONTROL001_XFFBypass|TestCHAIN_AllowlistBypass' -v

Output:

[control] no XFF, RemoteAddr=1.2.3.4 -> status=400 (correctly blocked)
[exploit] XFF=10.0.0.1, RemoteAddr=1.2.3.4 -> status=200, ClientIP=10.0.0.1 (bypassed)
gate status = 200 | created_ip that would be persisted = "10.0.0.1"

Against a live instance with mcp_allowed_remote_ips=10.0.0.1:

curl -s -o /dev/null -w "%{http_code}\n" https://target/mcp            # 403 (blocked)
curl -s -o /dev/null -w "%{http_code}\n" -H "X-Forwarded-For: 10.0.0.1" https://target/mcp  # passes IP gate

Impact

Defeats an explicitly advertised, operator-configured security control on the MCP and API-token surfaces, from any source IP, with a single header and no authentication to the gate itself. It also destroys non-repudiation: every action is logged under the spoofed allowlisted IP, so audit trails point at the trusted host instead of the attacker. (The MCP/API actions themselves still require a valid token — this neutralizes the compensating IP control, not the JWT layer.)

Record
GHSA-wchh-mrv2-5h4q
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-348
Updated
2026-11-09
NVD5.3

ezBookkeeping is an open source, self-hosted personal finance application. From 0.10.0 until 1.6.0, ezBookkeeping constructs its Gin router with gin.New() without configuring SetTrustedProxies, causing c.ClientIP() to trust a spoofed X-Forwarded-For value. An unauthenticated remote attacker can supply an allowlisted address to bypass the mcp_allowed_remote_ips or api_token_allowed_remote_ips gate. A valid MCP or API token remains required for protected actions. When such actions occur, the same spoofed value is stored in Transaction.CreatedIp and the request log, attributing activity to the trusted host instead of the attacker. This issue is fixed in version 1.6.0.

Record
CVE-2026-86041
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/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
Weakness
CWE-348
Updated
2026-11-12
OSV5.3

ezBookkeeping: Source-IP allowlist (MCP & API token) is bypassable via a spoofed `X-Forwarded-For` header

ezBookkeeping is an open source, self-hosted personal finance application. From 0.10.0 until 1.6.0, ezBookkeeping constructs its Gin router with gin.New() without configuring SetTrustedProxies, causing c.ClientIP() to trust a spoofed X-Forwarded-For value. An unauthenticated remote attacker can supply an allowlisted address to bypass the mcp_allowed_remote_ips or api_token_allowed_remote_ips gate. A valid MCP or API token remains required for protected actions. When such actions occur, the same spoofed value is stored in Transaction.CreatedIp and the request log, attributing activity to the trusted host instead of the attacker. This issue is fixed in version 1.6.0.

Record
CVE-2026-86041
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-348
Updated
2026-11-07
OSVGHSA-wchh-mrv2-5h4q5.3

ezBookkeeping: Source-IP allowlist (MCP & API token) is bypassable via a spoofed `X-Forwarded-For` header

Summary

A remote attacker from any IP address defeats the operator-configured source-IP allowlist (mcp_allowed_remote_ips / api_token_allowed_remote_ips) by sending a single X-Forwarded-For: <allowlisted-ip> header. The router is created with gin.New() and never calls SetTrustedProxies, so gin trusts every proxy by default and c.ClientIP() returns the attacker-supplied header verbatim. The same forged value is also written to Transaction.CreatedIp and the access log, so the attacker's real IP never appears in any record.

Details

cmd/webserver.go:104 builds the router with gin.New(). A grep of cmd/ and pkg/ confirms SetTrustedProxies is never called, leaving gin's default trusted-proxy range of 0.0.0.0/0 and ::/0. WebContext (pkg/core/context_web.go:39) embeds *gin.Context and does not override ClientIP().

Both allowlist middlewares match the operator allowlist against this attacker-controlled value:

// pkg/middlewares/mcp_server_ip_limit.go:19
if config.MCPAllowedRemoteIPs[i].Match(c.ClientIP()) { c.Next(); return }

The identical c.ClientIP() is consumed by the audit trail at pkg/api/transactions.go:1078 (createNewTransactionModel(..., c.ClientIP()) → CreatedIp) and the access log (pkg/middlewares/request_log.go:28), so one header both bypasses the gate and forges attribution.

PoC

Runtime PoC (no live deployment required) — pkg/middlewares/xff_bypass_runtime_test.go, pkg/middlewares/xff_chain_runtime_test.go:

go test ./pkg/middlewares/ -run 'TestACCESSCONTROL001_XFFBypass|TestCHAIN_AllowlistBypass' -v

Output:

[control] no XFF, RemoteAddr=1.2.3.4 -> status=400 (correctly blocked)
[exploit] XFF=10.0.0.1, RemoteAddr=1.2.3.4 -> status=200, ClientIP=10.0.0.1 (bypassed)
gate status = 200 | created_ip that would be persisted = "10.0.0.1"

Against a live instance with mcp_allowed_remote_ips=10.0.0.1:

curl -s -o /dev/null -w "%{http_code}\n" https://target/mcp            # 403 (blocked)
curl -s -o /dev/null -w "%{http_code}\n" -H "X-Forwarded-For: 10.0.0.1" https://target/mcp  # passes IP gate

Impact

Defeats an explicitly advertised, operator-configured security control on the MCP and API-token surfaces, from any source IP, with a single header and no authentication to the gate itself. It also destroys non-repudiation: every action is logged under the spoofed allowlisted IP, so audit trails point at the trusted host instead of the attacker. (The MCP/API actions themselves still require a valid token — this neutralizes the compensating IP control, not the JWT layer.)

Record
GHSA-wchh-mrv2-5h4q
CVSS 4.0
5.3 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-348
Updated
2026-11-09

History

History
RecordedChangedSources
publishedEUVD
affected, referencesGitHub Advisories
affected, facts, referencesEUVD, NVD, OSV
referencesCVE List
aliases, referencesNVD, OSV
aliases, publishedEUVD
first indexedCVE List

Dates are when Every CVE recorded each change. Upstream dates may differ.

References

MoveEnter Open