Skip to content

Conversation

@munishchouhan
Copy link
Member

@munishchouhan munishchouhan commented Jan 16, 2026

Summary

Fixed IP address spoofing vulnerability where attackers could bypass rate limiting by sending arbitrary X-Forwarded-For headers. The rate limiter now uses socket addresses by default (secure) with opt-in support for trusted proxy headers when deployed behind AWS ALB.

Vulnerability

The rate limiter previously trusted client-supplied X-Forwarded-For headers without validation, allowing attackers to bypass rate limits by spoofing different IP addresses:

# Attack: Send 1000 requests with different spoofed IPs
for i in {1..1000}; do
    curl -H "X-Forwarded-For: 192.168.1.$i" http://wave.example.com/v2/
done
# Each request got separate rate limit → attack succeeds

Solution

Implemented custom SecureHttpClientAddressResolver that takes the rightmost IP from comma-separated X-Forwarded-For headers:

  • Default Mode (Secure): Uses socket address directly, ignoring all headers
  • ALB Mode (Opt-in): Takes rightmost IP from X-Forwarded-For (the one ALB added)

Key Implementation

@Override
String resolve(HttpRequest request) {
    // Get IP from Micronaut's default logic
    final String resolvedIp = super.resolve(request)

    if (!resolvedIp || !resolvedIp.contains(',')) {
        return resolvedIp  // Single IP - return as is
    }

    // SECURITY FIX: Take the RIGHTMOST IP (added by ALB)
    // X-Forwarded-For: <spoofed-ip>, <real-client-ip>
    // Returns: <real-client-ip>
    final String[] ips = resolvedIp.split(',')
    return ips[ips.length - 1].trim()
}

Why Rightmost IP?

When behind ALB, the X-Forwarded-For header contains:

  • Leftmost IP: Client-supplied (can be spoofed)
  • Rightmost IP: Added by ALB (trustworthy)

Example:

Client sends: X-Forwarded-For: 192.168.1.1 (spoofed)
ALB prepends: X-Forwarded-For: 192.168.1.1, 203.0.113.5 (real)
Wave uses: 203.0.113.5 ✓ (rightmost = secure)

Changes

Core Files

  • SecureHttpClientAddressResolver.groovy (NEW) (src/main/groovy/io/seqera/wave/util/SecureHttpClientAddressResolver.groovy):

    • Custom resolver that takes rightmost IP from X-Forwarded-For
    • Extends Micronaut's DefaultHttpClientAddressResolver
    • Replaces default resolver when ALB profile is enabled
    • Prevents IP spoofing by ignoring client-supplied IPs
  • ContainerController.groovy & RegistryProxyController.groovy:

    • Already inject and use HttpClientAddressResolver
    • No code changes needed - automatically use secure resolver
  • RateLimiterFilter.groovy (src/main/groovy/io/seqera/wave/filter/RateLimiterFilter.groovy):

    • Already injects and uses HttpClientAddressResolver
    • No code changes needed

Configuration

  • application-alb.yml (NEW) (src/main/resources/application-alb.yml):
    • Profile for AWS ALB deployments
    • Configures client-address-header: X-Forwarded-For
    • Includes security warnings about when to enable

Tests

  • RateLimiterFilterTest.groovy (NEW) (src/test/groovy/io/seqera/wave/filter/RateLimiterFilterTest.groovy):
    • 4 comprehensive unit tests (100% passing)
    • Verifies spoofed headers are ignored by default
    • Verifies X-Forwarded-For is trusted only in ALB mode

Documentation

  • docs/configuration.md: Added security section explaining ALB profile usage

Security Benefits

Default Mode (Secure by Default)

Client sends: X-Forwarded-For: 1.2.3.4
Socket IP: 127.0.0.1
Rate limit key: 127.0.0.1 ✓ (spoofed header ignored)

ALB Mode (When Configured)

MICRONAUT_ENVIRONMENTS=alb

Real Client (1.2.3.4) → ALB → Wave
Socket IP: 10.0.1.50 (ALB's IP)
X-Forwarded-For: 1.2.3.4 (real client, added by ALB)
Rate limit key: 1.2.3.4 ✓ (header trusted from ALB)

munishchouhan and others added 3 commits January 16, 2026 13:32
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
@munishchouhan munishchouhan marked this pull request as draft January 16, 2026 12:37
@munishchouhan munishchouhan self-assigned this Jan 16, 2026
Signed-off-by: munishchouhan <hrma017@gmail.com>

@Inject
@Nullable
private HttpClientAddressResolver addressResolver
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's prevent to use this always ?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am concerned that, it will add performance overhead on each request, I will dig more to see what is better

Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
Signed-off-by: munishchouhan <hrma017@gmail.com>
@munishchouhan
Copy link
Member Author

tested in dev:

wave % for i in {1..110}; do
      curl -H "X-Forwarded-For: 10.99.88.$i" \
           -X POST https://wave.dev-seqera.io/v1alpha2/container \
           -H "Content-Type: application/json" \
           -d '{"containerImage": "ubuntu:22.04"}' \
           -s -o /dev/null -w "Request $i: %{http_code}\n"
  done
.
.
.
.
Request 100: 200
Request 101: 200
Request 102: 200
Request 103: 200
Request 104: 429
Request 105: 429
Request 106: 429
Request 107: 429
Request 108: 429
Request 109: 429
Request 110: 429

@munishchouhan
Copy link
Member Author

run again:

wave % for i in {1..110}; do
      curl -H "X-Forwarded-For: 10.99.88.$i" \
           -X POST https://wave.dev-seqera.io/v1alpha2/container \
           -H "Content-Type: application/json" \
           -d '{"containerImage": "ubuntu:22.04"}' \
           -s -o /dev/null -w "Request $i: %{http_code}\n"
  done
Request 1: 429
Request 2: 429
Request 3: 429
Request 4: 429

@munishchouhan munishchouhan marked this pull request as ready for review January 21, 2026 06:32
@munishchouhan munishchouhan changed the title added HttpClientAddressResolver in RateLimiterFilter COMP-1149 added HttpClientAddressResolver in RateLimiterFilter Jan 21, 2026
@munishchouhan
Copy link
Member Author

if removed alb env:

wave % for i in {1..110}; do
      curl -H "X-Forwarded-For: 10.99.88.$i" \
           -X POST https://wave.dev-seqera.io/v1alpha2/container \
           -H "Content-Type: application/json" \
           -d '{"containerImage": "ubuntu:22.04"}' \
           -s -o /dev/null -w "Request $i: %{http_code}\n"
  done
Request 1: 200
Request 2: 200
Request 3: 200
Request 4: 200

Request 5: 200
Request 6: 200

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.

3 participants