Vulnerability GHSA-wchh-mrv2-5h4q

Medium Risk
MEDIUM RISK
CVSS Score: 6.0
Score Range: 4.0–6.9
Medium severity vulnerabilities (CVSS 4.0–6.9). Important issues that meaningfully reduce security confidence.
6 hours ago
November 09, 2026 at 09:51 PM UTC
ezBookkeeping: Source-IP allowlist (MCP & API token) is bypassable via a spoofed `X-Forwarded-For` header
>=0.10.0 <1.6.0
>=0.10.0 <1.6.0

Summary

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

Details

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.)

Timeline

Published
6 hours ago
November 09, 2026 at 09:51 PM UTC
Last Modified
6 hours ago
November 09, 2026 at 10:00 PM UTC