Skip to content

fix(api): restrict forwarded-header trust - #63

Merged
CryptoJones merged 1 commit into
mainfrom
fix/49-trusted-proxies
Jul 29, 2026
Merged

fix(api): restrict forwarded-header trust#63
CryptoJones merged 1 commit into
mainfrom
fix/49-trusted-proxies

Conversation

@CryptoJones

Copy link
Copy Markdown
Owner

Summary

  • retain ASP.NET Core safe loopback trust defaults instead of accepting forwarded headers from every caller
  • add explicit proxy-IP and CIDR configuration with fail-fast validation
  • prove spoofed direct headers share the direct-connection rate-limit bucket while trusted proxies retain per-client buckets
  • document same-host, container, and remote-proxy configuration
  • bump both runtimes to v1.11.3 and update BACKLOG.md

Validation

  • 58 Python tests passed
  • 176 .NET tests passed
  • 12 Playwright tests passed; 2 viewport-specific tests skipped as designed
  • Ruff, strict mypy, Bandit, uv lock, and compose validation passed
  • npm, NuGet, and Python dependency audits found no known vulnerabilities

Closes #49

Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Security

    • Forwarded client and protocol headers are now trusted only from configured proxy addresses or networks.
    • Untrusted forwarded headers are ignored, reducing spoofing risks and ensuring accurate client IP rate limiting.
  • Documentation

    • Added configuration guidance and examples for trusted proxies and networks.
    • Clarified reverse-proxy and HTTPS deployment recommendations.
  • Tests

    • Added coverage for safe defaults, valid proxy configuration, invalid settings, and trusted versus spoofed client IPs.
  • Release

    • Updated the application version to 1.11.3.

Walkthrough

The API now trusts forwarded client and protocol headers only from configured proxies or networks. Configuration parsing validates entries, rate-limit tests cover trusted and spoofed headers, and deployment documentation and defaults expose the new settings.

Changes

Forwarded-header trust

Layer / File(s) Summary
Forwarded-header policy and validation
api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs, api/ApplyTrack.Api.Tests/ConfigurationTests.cs
Builds validated forwarded-header options with a one-hop limit, configured known proxies/networks, safe defaults, and invalid-value checks.
Runtime trust and rate-limit behavior
api/ApplyTrack.Api/Program.cs, api/ApplyTrack.Api.Tests/RateLimitTests.cs
Applies the configuration-driven policy and verifies that spoofed addresses share the direct-connection bucket while trusted proxy addresses use distinct buckets.
Deployment configuration and documentation
api/ApplyTrack.Api/appsettings.json, docker-compose.yml, .env.example, README.md, BACKLOG.md, api/ApplyTrack.Api/ApplyTrack.Api.csproj, pyproject.toml
Adds configuration examples and security guidance, marks the related backlog item complete, and updates project versions to 1.11.3.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Program
  participant ForwardedHeadersMiddleware
  participant RateLimiter
  Client->>Program: Send request with X-Forwarded-For
  Program->>ForwardedHeadersMiddleware: Apply configured trust policy
  ForwardedHeadersMiddleware->>RateLimiter: Use trusted or direct connection address
  RateLimiter-->>Client: Enforce rate-limit bucket
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% 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
Title check ✅ Passed The title clearly summarizes the main change: restricting forwarded-header trust.
Description check ✅ Passed The description matches the change set and its security, testing, documentation, and version-bump goals.
Linked Issues check ✅ Passed The PR meets #49 by adding trusted proxy/network config, blocking spoofed direct headers, documenting setup, and adding trusted/untrusted tests.
Out of Scope Changes check ✅ Passed The apparent extras are in scope here, including the version bumps and BACKLOG.md update called out in the PR objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/49-trusted-proxies

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

@CryptoJones
CryptoJones merged commit bd2e985 into main Jul 29, 2026
3 of 4 checks passed

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs`:
- Around line 32-38: Update the KnownNetworks parsing loop in
ForwardedHeadersConfiguration to reject successfully parsed networks whose
prefix length is zero, including 0.0.0.0/0 and ::/0. Throw the same
configuration validation exception before adding such networks to
options.KnownIPNetworks, while preserving acceptance of non-wildcard valid CIDR
ranges.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 362d1a26-1b0d-4ffa-bb06-a385db6d3e0f

📥 Commits

Reviewing files that changed from the base of the PR and between 835e6c3 and 8858a40.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .env.example
  • BACKLOG.md
  • README.md
  • api/ApplyTrack.Api.Tests/ConfigurationTests.cs
  • api/ApplyTrack.Api.Tests/RateLimitTests.cs
  • api/ApplyTrack.Api/ApplyTrack.Api.csproj
  • api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs
  • api/ApplyTrack.Api/Program.cs
  • api/ApplyTrack.Api/appsettings.json
  • docker-compose.yml
  • pyproject.toml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cs,csproj,sln,py,js,html,css,sql}

📄 CodeRabbit inference engine (CLAUDE.md)

Every source file must carry SPDX-License-Identifier: Apache-2.0 and Copyright 2026 Aaron K. Clark. The project remains Apache-2.0 licensed.

Files:

  • api/ApplyTrack.Api/Program.cs
  • api/ApplyTrack.Api/ApplyTrack.Api.csproj
  • api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs
  • api/ApplyTrack.Api.Tests/ConfigurationTests.cs
  • api/ApplyTrack.Api.Tests/RateLimitTests.cs
api/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

api/**/*.cs: The core API must use .NET 10 with ASP.NET Core Minimal APIs running on Kestrel.
Use Dapper and Npgsql for data access over PostgreSQL; do not use Entity Framework Core or SQLAlchemy.
Implement optimistic locking with a manual version column.
The cover-letter engine must support any OpenAI-compatible endpoint, never hard-code a vendor, require a specific model, or require a paid key.
Cover-letter drafting is opt-out per tenant via cover_letters_enabled; disabled tenants must receive no drafting UI and trigger no LLM calls.
The .NET API owns authentication, sessions, and CRUD operations.

Files:

  • api/ApplyTrack.Api/Program.cs
  • api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs
  • api/ApplyTrack.Api.Tests/ConfigurationTests.cs
  • api/ApplyTrack.Api.Tests/RateLimitTests.cs
{api/**/*.cs,src/applytrack/**/*.py}

📄 CodeRabbit inference engine (CLAUDE.md)

The .NET API and Python poller must share PostgreSQL tables without calling each other. Every query in both runtimes must unconditionally filter with WHERE tenant_id.

Files:

  • api/ApplyTrack.Api/Program.cs
  • api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs
  • api/ApplyTrack.Api.Tests/ConfigurationTests.cs
  • api/ApplyTrack.Api.Tests/RateLimitTests.cs
🔇 Additional comments (12)
api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs (2)

16-30: LGTM!


43-47: LGTM!

api/ApplyTrack.Api.Tests/ConfigurationTests.cs (1)

6-7: LGTM!

Also applies to: 61-106

api/ApplyTrack.Api/Program.cs (1)

147-152: LGTM!

api/ApplyTrack.Api.Tests/RateLimitTests.cs (1)

7-17: LGTM!

Also applies to: 30-54, 107-159

api/ApplyTrack.Api/appsettings.json (1)

9-12: LGTM!

docker-compose.yml (1)

48-51: LGTM!

.env.example (1)

31-37: LGTM!

README.md (1)

195-195: LGTM!

Also applies to: 387-392

BACKLOG.md (1)

11-11: LGTM!

api/ApplyTrack.Api/ApplyTrack.Api.csproj (1)

8-8: LGTM!

pyproject.toml (1)

7-7: LGTM!

Comment on lines +32 to +38
foreach (var value in Values(configuration, "ForwardedHeaders:KnownNetworks"))
{
if (!System.Net.IPNetwork.TryParse(value, out var network))
throw new InvalidOperationException(
$"ForwardedHeaders:KnownNetworks contains invalid CIDR network '{value}'.");
options.KnownIPNetworks.Add(network);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File outline ==\n'
ast-grep outline api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs --view expanded || true

printf '\n== Relevant file contents ==\n'
cat -n api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs | sed -n '1,220p'

printf '\n== Search for KnownNetworks / prefix-length handling ==\n'
rg -n "KnownNetworks|KnownIPNetworks|PrefixLength|0\.0\.0\.0/0|::/0|ForwardedHeaders" api README.md . -g '!**/bin/**' -g '!**/obj/**' || true

Repository: CryptoJones/OSApplyTrack

Length of output: 9213


Reject wildcard CIDR ranges in ForwardedHeaders:KnownNetworks. IPNetwork.TryParse accepts 0.0.0.0/0 and ::/0, which would trust every client as a proxy and reopen the spoofing hole this change is meant to close. Add an explicit zero-prefix check and fail fast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/ApplyTrack.Api/ForwardedHeadersConfiguration.cs` around lines 32 - 38,
Update the KnownNetworks parsing loop in ForwardedHeadersConfiguration to reject
successfully parsed networks whose prefix length is zero, including 0.0.0.0/0
and ::/0. Throw the same configuration validation exception before adding such
networks to options.KnownIPNetworks, while preserving acceptance of non-wildcard
valid CIDR ranges.

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.

Security: restrict forwarded-header trust to configured proxies

1 participant