Skip to content

fix: read X-Forwarded-For for client IP in security audit events#422

Open
cschuerings wants to merge 2 commits intocap-js:mainfrom
cschuerings:fix/proxy-ip-security-events
Open

fix: read X-Forwarded-For for client IP in security audit events#422
cschuerings wants to merge 2 commits intocap-js:mainfrom
cschuerings:fix/proxy-ip-security-events

Conversation

@cschuerings
Copy link
Copy Markdown
Contributor

Description

Fixes #421.

On reverse-proxy deployments (e.g. BTP Cloud Foundry), socket.remoteAddress returns the internal proxy/Go Router IP, not the real client IP. This makes the ipAddress field in security audit events (AttachmentSizeExceeded, AttachmentUploadRejected, AttachmentDownloadRejected) useless for security forensics.

Changes

Read X-Forwarded-For first and fall back to socket.remoteAddress when the header is absent — consistent with how @sap/approuter resolves the client IP (lib/utils/logger.js):

// before
const ipAddress = req.req?.socket?.remoteAddress

// after
const ipAddress = req.req?.headers?.["x-forwarded-for"] || req.req?.socket?.remoteAddress

The full X-Forwarded-For value is logged as-is (may be comma-separated when multiple proxies are in the chain). Falls back to socket.remoteAddress for direct connections without a proxy (e.g. local development).

On reverse-proxy deployments (e.g. BTP Cloud Foundry), socket.remoteAddress
returns the internal proxy IP, not the real client IP. Read X-Forwarded-For
first and fall back to socket.remoteAddress when absent, consistent with
how @sap/approuter resolves the client IP (lib/utils/logger.js:76).

Fixes cap-js#421
@cschuerings cschuerings requested a review from a team as a code owner April 9, 2026 14:02
Copy link
Copy Markdown
Member

@KoblerS KoblerS left a comment

Choose a reason for hiding this comment

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

According to https://expressjs.com/en/5x/api.html#req.ip req.ip should be set. Also verified locally using this example that this parameter is available:

const express = require("express");
const app = express();
const PORT = 3000;

app.get("/", (req, res) => {
  console.log("Client IP:", req.ip);
  res.send(`Your IP is: ${req.ip}`);
});

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Comment on lines +277 to +278
const ipAddress =
req.req?.headers?.["x-forwarded-for"] || req.req?.socket?.remoteAddress
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
const ipAddress =
req.req?.headers?.["x-forwarded-for"] || req.req?.socket?.remoteAddress
const {ipAddress = req.req.ip

Comment on lines +336 to +337
const ipAddress =
req.req?.headers?.["x-forwarded-for"] || req.req?.socket?.remoteAddress
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
const ipAddress =
req.req?.headers?.["x-forwarded-for"] || req.req?.socket?.remoteAddress
const {ipAddress = req.req.ip

@cschuerings
Copy link
Copy Markdown
Contributor Author

Thanks for checking! However, req.ip doesn't help here in a typical BTP/Cloud Foundry deployment with an approuter in front.

req.ip respects trust proxy — but in CF the app server doesn't (and shouldn't) set app.set('trust proxy', true'), because CAP doesn't configure this. Without that setting, Express leaves req.ip as the raw socket address.

I verified this with a deployed test app (approuter → Express server) that logs all three values. When switching between networks, req.ip and socket.remoteAddress always show the same internal/private IP of the last hop — while X-Forwarded-For correctly reflects the real client IP:

Network 1:

"req.ip": "10.x.x.x",
"socket.remoteAddress": "10.x.x.x",
"x-forwarded-for": "203.x.x.x, 10.x.x.x, 52.x.x.x, 10.x.x.x"

Network 2 (different hotspot):

"req.ip": "10.x.x.x",
"socket.remoteAddress": "10.x.x.x",
"x-forwarded-for": "80.x.x.x, 10.x.x.x, 52.x.x.x, 10.x.x.x"

The internal IPs stay constant regardless of which network the client is on, while the first entry in X-Forwarded-For changes correctly with the client's public IP. Reading X-Forwarded-For directly is therefore necessary to get the real client IP in this deployment topology.

Copy link
Copy Markdown
Contributor

@schiwekM schiwekM left a comment

Choose a reason for hiding this comment

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

Thanks for noticing and please excuse that I overlooked the approuter scenario in the initial impl.

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.

[Version: 3.11.0] Security events log proxy IP instead of client IP on Cloud Foundry

3 participants