Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[breaking] rename xForwardedForIndex to XFF_DEPTH #4332

Merged
merged 10 commits into from
Mar 16, 2022
Merged
5 changes: 5 additions & 0 deletions .changeset/sweet-parents-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-node': patch
---

[breaking] rename `xForwardedForIndex` to `XFF_DEPTH` and make it an environment variable
14 changes: 4 additions & 10 deletions packages/adapter-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,23 +94,17 @@ MY_ORIGINURL=https://my.site \
node build
```

### xForwardedForIndex
### `XFF_DEPTH`

If the `ADDRESS_HEADER` is `X-Forwarded-For`, the header value will contain a comma-separated list of IP addresses. For example, if there are three proxies between your server and the client, proxy 3 will forward the addresses of the client and the first two proxies:
If the `ADDRESS_HEADER` is `X-Forwarded-For`, the header value will contain a comma-separated list of IP addresses. The `XFF_DEPTH` environment variable should specify how many trusted proxies sit between your server and the client. E.g. if there are three trusted proxies, proxy 3 will forward the addresses of the client and the first two proxies:
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved

```
<client address>, <proxy 1 address>, <proxy 2 address>
```

To get the client address we could use `xForwardedFor: 0` or `xForwardedFor: -3`, which counts back from the number of addresses.
To get the client address we could use `XFF_DEPTH=3`.
Copy link
Member

@Rich-Harris Rich-Harris Mar 16, 2022

Choose a reason for hiding this comment

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

I'm not sure how to turn this into a suggestion given that it contains a fenced code block:


Some guides will tell you to read the left-most address, but this leaves you vulnerable to spoofing:

<spoofed address>, <client address>, <proxy 1 address>, <proxy 2 address>

Instead, we read from the right, accounting for the number of trusted proxies. In this case, we would use XFF_DEPTH=3.


**X-Forwarded-For is [trivial to spoof](https://adam-p.ca/blog/2022/03/x-forwarded-for/), howevever**:

```
<spoofed address>, <client address>, <proxy 1 address>, <proxy 2 address>
```

For that reason you should always use a negative number (depending on the number of proxies) if you need to trust `event.clientAddress`. In the above example, `0` would yield the spoofed address while `-3` would continue to work.
This is [the most secure way](https://adam-p.ca/blog/2022/03/x-forwarded-for/) to read from the `X-Forwarded-For` header. It assumes a constant number of proxies between the client and server. If you are not using this value for a security-sensitive application, you may find it more reliable in some instances to avoid this assumption and read the first entry from the `X-Forwarded-For` header yourself (e.g. some users may be connecting through a corporate proxy while others are not breaking the assumption of a constant number of proxies).
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved

## Custom server

Expand Down
1 change: 0 additions & 1 deletion packages/adapter-node/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ interface AdapterOptions {
host?: string;
};
};
xForwardedForIndex?: number;
}

declare function plugin(options?: AdapterOptions): Adapter;
Expand Down
6 changes: 2 additions & 4 deletions packages/adapter-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export default function ({
protocol: protocol_header_env = 'PROTOCOL_HEADER',
host: host_header_env = 'HOST_HEADER'
} = {}
} = {},
xForwardedForIndex = -1
} = {}
} = {}) {
return {
name: '@sveltejs/adapter-node',
Expand Down Expand Up @@ -55,8 +54,7 @@ export default function ({
ORIGIN: origin_env ? `process.env[${JSON.stringify(origin_env)}]` : 'undefined',
PROTOCOL_HEADER: JSON.stringify(protocol_header_env),
HOST_HEADER: JSON.stringify(host_header_env),
ADDRESS_HEADER: JSON.stringify(address_header_env),
X_FORWARDED_FOR_INDEX: JSON.stringify(xForwardedForIndex)
ADDRESS_HEADER: JSON.stringify(address_header_env)
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-node/src/handler.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ declare global {
const ADDRESS_HEADER: string;
const HOST_HEADER: string;
const PROTOCOL_HEADER: string;
const X_FORWARDED_FOR_INDEX: number;
const X_FORWARDED_FOR_PROXIES: number;
}

export const handler: Handle;
24 changes: 22 additions & 2 deletions packages/adapter-node/src/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,32 @@ import { getRequest, setResponse } from '@sveltejs/kit/node';
import { Server } from 'SERVER';
import { manifest } from 'MANIFEST';

/* global ORIGIN, ADDRESS_HEADER, PROTOCOL_HEADER, HOST_HEADER, X_FORWARDED_FOR_INDEX */
/* global ORIGIN, ADDRESS_HEADER, PROTOCOL_HEADER, HOST_HEADER */

const server = new Server(manifest);
const origin = ORIGIN;

const xff_depth = get_xff_depth();
const address_header = ADDRESS_HEADER && (process.env[ADDRESS_HEADER] || '').toLowerCase();
const protocol_header = PROTOCOL_HEADER && process.env[PROTOCOL_HEADER];
const host_header = (HOST_HEADER && process.env[HOST_HEADER]) || 'host';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

function get_xff_depth() {
const value = process.env['XFF_DEPTH'];
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved
let xff_depth;
try {
xff_depth = value ? parseInt(value) : 1;
Rich-Harris marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
throw new Error('Expected XFF_DEPTH to be an integer. Received ${value}');
}
if (xff_depth < 1) {
throw new Error('XFF_DEPTH cannot be less than 1');
}
return xff_depth;
}

/**
* @param {string} path
* @param {number} max_age
Expand Down Expand Up @@ -62,7 +77,12 @@ const ssr = async (req, res) => {

if (address_header === 'x-forwarded-for') {
const addresses = value.split(',');
return addresses[(addresses.length + X_FORWARDED_FOR_INDEX) % addresses.length].trim();
if (xff_depth > addresses.length) {
throw new Error(
`XFF_DEPTH specified as ${xff_depth}, but only found ${addresses.length} addresses`
);
}
return addresses[addresses.length - xff_depth].trim();
}

return value;
Expand Down