compiler-explorer / compiler-explorer/infra

Client IP is spoofable via X-Forwarded-For (trust proxy true, ALB open to the world)

Open
#2,353 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
434
Forks
429
Avg merge
4d 21h
Merged PRs (30d)
38

Description

## Symptom

Papertrail shows request log lines like:

```
[ip-172-30-1-152] [amazon] warn: 127.0.0.0 "GET /wp-content/backups/data%2esql" 404
```

The "client" is logged as localhost. The CloudFront access logs for the same requests (2026-09-14, 16:07 to 16:39 UTC and again at 20:00 UTC) show the real source was four hosts in `185.177.72.0/24` running `curl/8.7.1`, each sending exactly 3001 vulnerability-scan requests (`.env`, `wp-json`, `terraform/modules.tf`, `.aws/config`, ...) against random subdomains (`noam.`, `taf.`, `newstudent.godbolt.org`), every one carrying a client-supplied header:

```
X-Forwarded-For: 127.0.0.1
```

CloudFront and the ALB both append to that header rather than replace it, so the app receives `127.0.0.1, , `.

Other sources in the same logs spoof too (`45.148.10.60` sending `127.0.0.1` during a wordpress scan), and corporate proxies such as Zscaler (`165.225.x`, `151.186.x`) legitimately add their internal client address as the leftmost entry, which is why those requests also log an IP that is not the peer.

## Cause

`compiler-explorer/lib/app/server-config.ts` sets `trust proxy` to `true`. Express then takes the leftmost `X-Forwarded-For` entry as `req.ip`, which is whatever the client chose to send. `lib/storage/s3.ts` (`storeItem`) goes further and reads the raw `X-Forwarded-For` header directly.

The infra side is what makes the app-side fix awkward: `terraform/security.tf` opens the ALB on 443 to `0.0.0.0/0` (rule `ALB_HttpsFromAnywhere`), so a request may arrive with two trusted hops (CloudFront, ALB) or one (ALB only). A fixed hop count in Express is only correct once the chain is deterministic. There is already a TODO in `security.tf` about restricting the ALB to the CloudFront origin-facing managed prefix list (`pl-3b927c52`).

## Impact

The IP we log, and anything keyed on `req.ip` (abuse tracing, short link creator IP in `storeItem`, any future rate limiting), is attacker-controlled. No availability impact: the scan itself was cheap 404s and did not move latency.

## Proposed fix

1. infra: restrict the ALB 443 ingress to the CloudFront managed prefix list so only CloudFront can reach it. Note the prefix list carries a weight of 55 rules against the security group's 60 rule quota, so check the remaining rules in that group (or ask for a quota bump). Keep an ingress path for the admin node if anyone relies on hitting the ALB directly for debugging.
2. compiler-explorer: set `trust proxy` to the exact hop count (2: CloudFront then ALB) or resolve the client from `CloudFront-Viewer-Address` if the origin request policy forwards it, and make `storeItem` use `req.ip` instead of the raw header.

Evidence: CloudFront logs under `s3://compiler-explorer-logs/cloudfront/` for `EFCZGUFIBB1UY.2026-09-14-16.*` and `-20.*`; the `x-forwarded-for` column carries the spoofed value.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Contributor guide

Open the contributing guide

Research direction

Start with terraform/security.tf and its TODO about the CloudFront origin-facing prefix list, then read compiler-explorer/lib/app/server-config.ts and lib/storage/s3.ts, including storeItem. Confirm the ALB rule, remaining security-group quota, and any required admin path before choosing the trusted-hop or CloudFront-header approach. Done means direct ALB spoofing is prevented, client IP handling is no longer attacker-controlled, and storeItem uses the trusted request IP.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, express, terraform, typescript
Domain
backend, infrastructure, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.