Skip to content

Fix forwarded header parsing#586

Merged
Flaminel merged 4 commits into
mainfrom
fix_forwarded_header
Apr 27, 2026
Merged

Fix forwarded header parsing#586
Flaminel merged 4 commits into
mainfrom
fix_forwarded_header

Conversation

@Flaminel

Copy link
Copy Markdown
Contributor

Relates to GHSA-8q44-v65j-jc3q

@Flaminel

Copy link
Copy Markdown
Contributor Author

@greptileai review

@Flaminel

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.45614% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...pi/Middleware/TrustedForwardedHeadersMiddleware.cs 86.00% 3 Missing and 4 partials ⚠️
...rr.Api/Auth/TrustedNetworkAuthenticationHandler.cs 50.00% 2 Missing ⚠️
...rr.Api/Features/Auth/Controllers/AuthController.cs 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes GHSA-8q44-v65j-jc3q by replacing the old leftmost-XFF approach (attacker-controlled) with a new TrustedForwardedHeadersMiddleware that walks X-Forwarded-For right-to-left, stopping at the first untrusted hop. The fix is architecturally sound: IP resolution is centralised in the middleware and all downstream consumers (TrustedNetworkAuthenticationHandler, HttpRequestExtensions) simply read the already-rewritten RemoteIpAddress / Scheme / Host. Unit tests and a targeted E2E regression test are included.

Confidence Score: 4/5

Safe to merge; the core security fix is correct and well-tested. Two P2 hardening items remain around proto/host header sanitisation.

No P0 or P1 findings. The right-to-left XFF traversal correctly addresses the GHSA advisory. The two P2 comments are defence-in-depth improvements that require a misconfigured or compromised trusted proxy to matter in practice.

code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs — proto and host header sanitisation

Security Review

  • Scheme injection via X-Forwarded-Proto (TrustedForwardedHeadersMiddleware.cs line 94): the header value is written to context.Request.Scheme without an allowlist check. A chained-proxy scenario where the header contains \"https, http\" would embed a malformed scheme in the OIDC redirect URI.
  • Host injection via X-Forwarded-Host (TrustedForwardedHeadersMiddleware.cs line 100): similarly applied without taking only the first component, which could produce a multi-value host string in OIDC redirect URI construction.
  • Both issues require a trusted proxy in the chain to be misconfigured or compromised; they are residual hardening concerns rather than a bypass of the primary GHSA fix.

Important Files Changed

Filename Overview
code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs New middleware implementing right-to-left XFF chain traversal to fix GHSA-8q44-v65j-jc3q; core logic is correct but X-Forwarded-Proto and X-Forwarded-Host values are not sanitised before being written to the request context
code/backend/Cleanuparr.Api/Auth/TrustedNetworkAuthenticationHandler.cs Simplified to use injected DataContext and delegates IP resolution to the new middleware; ResolveClientIp now just reads the already-rewritten RemoteIpAddress
code/backend/Cleanuparr.Api/Extensions/HttpRequestExtensions.cs GetExternalBaseUrl simplified to read Scheme/Host directly now that middleware normalises them upstream
code/backend/Cleanuparr.Api.Tests/Middleware/TrustedForwardedHeadersMiddlewareTests.cs New test suite covering spoofing, malformed entries, empty entries, IPv4-mapped loopback, custom CIDR trusted networks, and proto/host propagation
e2e/tests/13-trusted-network-xff-bug.spec.ts E2E regression test for GHSA-8q44-v65j-jc3q using nginx attacker-simulation path; validates spoofed XFF and X-Real-IP are both rejected

Reviews (1): Last reviewed commit: "added e2e tests" | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A new middleware centralizes secure handling of X-Forwarded-* headers for requests from trusted networks, updating request context accordingly. The authentication handler was refactored to use dependency injection and simplified IP resolution. The system now processes forwarded headers early in the request pipeline rather than distributed across multiple components.

Changes

Cohort / File(s) Summary
Middleware Core
code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs, code/backend/Cleanuparr.Api/DependencyInjection/ApiDI.cs
New middleware that dynamically reads TrustForwardedHeaders config and validates X-Forwarded-* headers against configured trusted networks, updating RemoteIpAddress, scheme, and host. Integrated into the request pipeline immediately after exception handling.
Authentication Handler & Resolution
code/backend/Cleanuparr.Api/Auth/TrustedNetworkAuthenticationHandler.cs, code/backend/Cleanuparr.Api/Features/Auth/Controllers/AuthController.cs
Handler refactored to accept injected DataContext and removed forwarded header parsing (now delegated to middleware). IP resolution simplified to return direct RemoteIpAddress. Auth controller updated to call new handler signature.
Request Extensions
code/backend/Cleanuparr.Api/Extensions/HttpRequestExtensions.cs
GetExternalBaseUrl now reads only current Scheme and Host from request (forwarded header trust delegated to middleware); removed local IP check and forwarded header logic.
Middleware Tests
code/backend/Cleanuparr.Api.Tests/Middleware/TrustedForwardedHeadersMiddlewareTests.cs
Comprehensive test suite covering untrusted peers, local peer scenarios, trusted CIDR validation, spoofed chains, malformed entries, scheme/host forwarding, and IPv4-mapped IPv6 loopback handling.
E2E Infrastructure
e2e/docker-compose.e2e.yml, e2e/nginx/nginx.conf
New Nginx reverse proxy service for e2e testing that forwards requests to app at localhost:5000 with X-Forwarded-* headers; includes attacker endpoint that injects spoofed IPs.
E2E Tests & Helpers
e2e/tests/13-trusted-network-xff-bug.spec.ts, e2e/tests/helpers/app-api.ts, e2e/tests/helpers/test-config.ts
Regression test suite for GHSA-8q44-v65j-jc3q verifying auth-bypass cannot be spoofed via X-Forwarded-For. Added helpers for retrieving/updating general config and managing auth-bypass settings; extended test config with proxy URL.
Documentation
docs/docs/configuration/general/index.mdx
Updated "Trust Forwarded Headers" warning to clarify feature should only be enabled with controlled reverse proxies and calls out spoofing risk when auth-bypass for local addresses is enabled.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Proxy as Nginx Proxy
    participant Middleware as TrustedForwarded<br/>HeadersMiddleware
    participant AuthHandler as TrustedNetwork<br/>AuthHandler
    participant DataCtx as DataContext
    participant App as App Logic

    Client->>Proxy: GET /resource<br/>(from public IP)
    Proxy->>Middleware: Forward request<br/>RemoteIp=proxy_ip<br/>X-Forwarded-For: client_ip
    Middleware->>DataCtx: Load TrustForwardedHeaders<br/>& TrustedNetworks
    DataCtx-->>Middleware: Config + networks
    Middleware->>Middleware: Validate proxy_ip<br/>against TrustedNetworks
    alt Proxy is Trusted
        Middleware->>Middleware: Parse X-Forwarded-For,<br/>update RemoteIpAddress
        Middleware->>Middleware: Apply X-Forwarded-Proto,<br/>X-Forwarded-Host
    else Proxy is Untrusted
        Middleware->>Middleware: Skip header processing<br/>keep original metadata
    end
    Middleware->>AuthHandler: Pass updated HttpContext
    AuthHandler->>AuthHandler: Call ResolveClientIp()<br/>returns RemoteIpAddress
    AuthHandler->>AuthHandler: Check if IP is<br/>in TrustedNetworks
    AuthHandler-->>App: Auth result
    App-->>Client: HTTP 200 / 401
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A middleware hops into place,
Forwarded headers find their grace,
Trust chains are checked with careful sight,
No spoofed IPs can fool the knight!
Proxies now speak truth so clear,

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description references the security advisory GHSA-8q44-v65j-jc3q, which is directly related to the changeset addressing forwarded header handling and authentication bypass vulnerabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title 'Fix forwarded header parsing' accurately describes the main change: a middleware and handler refactor that fixes how trusted forwarded headers (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host) are parsed and validated, addressing security advisory GHSA-8q44-v65j-jc3q.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix_forwarded_header

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
e2e/docker-compose.e2e.yml (1)

39-46: Potential race condition: no healthcheck for nginx service.

The depends_on: - app only ensures the app container starts, not that it's ready to accept connections. Since neither app nor nginx have healthchecks, tests may start before both services are actually ready, leading to flaky test runs.

Consider adding a healthcheck or startup delay mechanism, or have the test suite poll for readiness before running tests.

💡 Example healthcheck for nginx
   nginx:
     image: nginx:1.27-alpine
     network_mode: host
     volumes:
       - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
     depends_on:
       - app
+    healthcheck:
+      test: ["CMD-SHELL", "curl -sf http://localhost:8000/ || exit 1"]
+      interval: 2s
+      timeout: 2s
+      retries: 10
+      start_period: 5s

Note: This requires curl in the container or using a netcat alternative. Alternatively, ensure the test harness waits for port 8000 availability before running tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/docker-compose.e2e.yml` around lines 39 - 46, Add a healthcheck for the
nginx service (the nginx block) so Docker Compose waits for nginx to be
considered healthy rather than just started by depends_on: - app; implement a
healthcheck that probes the nginx endpoint (e.g., curl or a TCP check against
the nginx port) and set sensible interval/retries/start-period values, and also
add a complementary healthcheck for the app service (or ensure the test harness
polls the app/nginx readiness) so tests only run after both nginx and app are
truly ready.
code/backend/Cleanuparr.Api/Auth/TrustedNetworkAuthenticationHandler.cs (1)

79-80: Add XML docs for ResolveClientIp.

This helper’s behavior changed materially in this PR, and callers now have to know that TrustedForwardedHeadersMiddleware must have normalized Connection.RemoteIpAddress first.

As per coding guidelines, "Add XML documentation comments for public APIs in C#".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/backend/Cleanuparr.Api/Auth/TrustedNetworkAuthenticationHandler.cs`
around lines 79 - 80, Add an XML documentation comment for the public helper
ResolveClientIp (in TrustedNetworkAuthenticationHandler) that explains its
behavior and contract: it returns httpContext.Connection.RemoteIpAddress and
therefore requires that TrustedForwardedHeadersMiddleware has already normalized
the Connection.RemoteIpAddress for forwarded client IPs; document the nullable
return (IPAddress?) and any threading/context expectations so callers know they
must run the middleware before invoking ResolveClientIp.
code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs (1)

19-25: Document the new public middleware surface.

The class summary helps, but the public constructor, InvokeAsync, and ApplyForwardedHeaders still lack XML docs.

As per coding guidelines, "Add XML documentation comments for public APIs in C#".

Also applies to: 47-47

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs`
around lines 19 - 25, Add XML documentation comments for the public middleware
API: add a summary and param/returns tags to the constructor
TrustedForwardedHeadersMiddleware(RequestDelegate next), to the
InvokeAsync(HttpContext context) method, and to the ApplyForwardedHeaders(...)
method (the public method referenced in the class) describing their purpose,
parameters, and behavior; ensure the constructor doc explains that it
initializes the middleware with the next RequestDelegate, InvokeAsync documents
that it processes the HttpContext and calls the next middleware asynchronously,
and ApplyForwardedHeaders documents what headers it applies/filters and any
exceptions or side effects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs`:
- Around line 94-103: The middleware is assigning raw comma-separated header
values to Request.Scheme and Request.Host; update
TrustedForwardedHeadersMiddleware to split the X-Forwarded-Proto and
X-Forwarded-Host header values on ',' and use the first non-empty trimmed token
(per-hop consumption) when setting context.Request.Scheme and
context.Request.Host (use new HostString(parsedHost)). Locate the assignments
where forwardedProto and forwardedHost are read and replace the raw value usage
with the parsed/trimmed first element.
- Around line 71-92: The loop in TrustedForwardedHeadersMiddleware mutates
context.Connection.RemoteIpAddress as it parses entries, allowing a malformed
trailing entry to leave RemoteIpAddress set to a trusted hop; instead, fully
validate the X-Forwarded-For chain first: iterate entries (from right-to-left)
and parse each using IPAddress.TryParse, detect any malformed entry and abort
without changing context, and determine the final client IP only after verifying
the chain with IsTrustedHop(trustedNetworks) logic; finally, if validation
succeeds and consumedAtLeastOne, assign the chosen IP to
context.Connection.RemoteIpAddress so ResolveClientIp() will see a
safely-validated value.

---

Nitpick comments:
In `@code/backend/Cleanuparr.Api/Auth/TrustedNetworkAuthenticationHandler.cs`:
- Around line 79-80: Add an XML documentation comment for the public helper
ResolveClientIp (in TrustedNetworkAuthenticationHandler) that explains its
behavior and contract: it returns httpContext.Connection.RemoteIpAddress and
therefore requires that TrustedForwardedHeadersMiddleware has already normalized
the Connection.RemoteIpAddress for forwarded client IPs; document the nullable
return (IPAddress?) and any threading/context expectations so callers know they
must run the middleware before invoking ResolveClientIp.

In `@code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs`:
- Around line 19-25: Add XML documentation comments for the public middleware
API: add a summary and param/returns tags to the constructor
TrustedForwardedHeadersMiddleware(RequestDelegate next), to the
InvokeAsync(HttpContext context) method, and to the ApplyForwardedHeaders(...)
method (the public method referenced in the class) describing their purpose,
parameters, and behavior; ensure the constructor doc explains that it
initializes the middleware with the next RequestDelegate, InvokeAsync documents
that it processes the HttpContext and calls the next middleware asynchronously,
and ApplyForwardedHeaders documents what headers it applies/filters and any
exceptions or side effects.

In `@e2e/docker-compose.e2e.yml`:
- Around line 39-46: Add a healthcheck for the nginx service (the nginx block)
so Docker Compose waits for nginx to be considered healthy rather than just
started by depends_on: - app; implement a healthcheck that probes the nginx
endpoint (e.g., curl or a TCP check against the nginx port) and set sensible
interval/retries/start-period values, and also add a complementary healthcheck
for the app service (or ensure the test harness polls the app/nginx readiness)
so tests only run after both nginx and app are truly ready.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ac52fe0f-ffde-4610-94bf-cfa992545c62

📥 Commits

Reviewing files that changed from the base of the PR and between 9f48d35 and 76bd46a.

📒 Files selected for processing (12)
  • code/backend/Cleanuparr.Api.Tests/Middleware/TrustedForwardedHeadersMiddlewareTests.cs
  • code/backend/Cleanuparr.Api/Auth/TrustedNetworkAuthenticationHandler.cs
  • code/backend/Cleanuparr.Api/DependencyInjection/ApiDI.cs
  • code/backend/Cleanuparr.Api/Extensions/HttpRequestExtensions.cs
  • code/backend/Cleanuparr.Api/Features/Auth/Controllers/AuthController.cs
  • code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs
  • docs/docs/configuration/general/index.mdx
  • e2e/docker-compose.e2e.yml
  • e2e/nginx/nginx.conf
  • e2e/tests/13-trusted-network-xff-bug.spec.ts
  • e2e/tests/helpers/app-api.ts
  • e2e/tests/helpers/test-config.ts

Comment thread code/backend/Cleanuparr.Api/Middleware/TrustedForwardedHeadersMiddleware.cs Outdated
@Flaminel Flaminel changed the title Fix forwarded header Fix forwarded header parsing Apr 27, 2026
@Flaminel Flaminel merged commit 85de80a into main Apr 27, 2026
7 of 11 checks passed
@Flaminel Flaminel deleted the fix_forwarded_header branch April 27, 2026 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant