[Regression on closed #550] No `trust proxy` setting — behind a load balancer every client shares the proxy's IP for throttling, logs and handoff auditing
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 53/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- express, typescript
調査の方向性
Start in src/main.ts at bootstrap(), then read src/app.module.ts for the throttler and src/auth/auth.controller.ts for IP logging. Check configuration.ts, .env.example, the README, and main.spec.ts for the proposed configuration, deployment guidance, and test coverage. Done means proxy trust is configured safely from settings and the documented test passes.
索引モデルが issue の本文から書いたものです。
説明
Location
src/main.ts:29-50—bootstrap(); the Express instance is never configured withtrust proxysrc/app.module.ts:33-38—ThrottlerModule.forRoot([...]), which keys on the client IPsrc/auth/auth.controller.ts:83, 90, 100-113—@Ip() ipused in the handoff-exchange audit log
Problem
grep -rn "trust proxy\|getHttpAdapter" src/ returns nothing. The app is created with NestFactory.create(AppModule, { rawBody: true }) and never calls app.set('trust proxy', ...) (or the equivalent via app.getHttpAdapter().getInstance()).
Express defaults trust proxy to false. With it disabled, req.ip is the TCP peer address and X-Forwarded-For is ignored entirely. In any deployment where the Node process sits behind a reverse proxy, ingress controller or cloud load balancer — which is every realistic production topology, and the one the repo's own Docker/Compose tooling implies — the TCP peer is always the proxy. So req.ip is a single constant value for the entire internet.
This is closed issue #550, unchanged.
Why it matters
Three separate mechanisms silently degrade, all of them security-relevant:
1. Rate limiting collapses into one global bucket. ThrottlerGuard is registered as an APP_GUARD (app.module.ts:70-73) and its default tracker is the client IP. With every request reporting the proxy's address, the configured limits stop being per-client and become per-deployment:
default: 120 requests/minute for all users combined. A moderately busy platform exceeds that on legitimate traffic alone, at which point every user starts receiving HTTP 429 — a self-inflicted outage.@Throttle({ short: { limit: 1, ttl: 1000 } })onPOST /github/sync/:owner/:repo(github.controller.ts:31) becomes one sync request per second globally.@Throttle({ short: { limit: 3, ttl: 1000 } })onGET /auth/github(auth.controller.ts:37) becomes three login initiations per second for the entire platform.
Conversely, the protection the limits were added for disappears: a single attacker gets the whole shared budget and is indistinguishable from every other client, so there is no way to throttle them without throttling everyone.
2. The handoff-exchange audit log records the wrong address. AuthController.exchangeCode logs rejected handoff attempts with the caller's IP specifically so that bursts of guessed or replayed codes are visible server-side:
this.logger.warn(`Handoff exchange rejected on ${route} from ${ip}: invalid or expired code`);
Every one of those lines will name the load balancer. The log line exists to answer "who is doing this?" and cannot.
3. Protocol-dependent logic sees the wrong scheme. Without trust proxy, req.protocol is http for every request even when the client connection was HTTPS, and req.secure is false. Any present or future code that branches on those (cookie secure handling, redirect construction) will make the wrong decision behind TLS termination.
Reproduction / failure path
- Run the backend behind any reverse proxy, e.g.:
# nginx
location / {
proxy_pass http://127.0.0.1:4000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
- From two different client machines, send 121 requests in under a minute through the proxy to any route, e.g.
GET /api/bounties. - Observed: the 121st request receives HTTP 429 regardless of which machine sent it — the two clients share one bucket. Expected: each client has its own 120/minute allowance.
- Attempt a bad handoff exchange from either machine (
POST /api/auth/handoffwith{"code":"bogus"}) and check the logs: the warn line reports127.0.0.1(the proxy), not the real client.
Suggested fix
Configure trust explicitly in bootstrap(), after NestFactory.create and before the guards and middleware run:
const app = await NestFactory.create<NestExpressApplication>(AppModule, { rawBody: true });
app.set('trust proxy', configService.get('trustProxy', { infer: true }));
Make the value configuration-driven rather than hard-coded, because the correct setting depends on topology and a too-permissive value is itself a vulnerability: with trust proxy set to true, a client can forge X-Forwarded-For and spoof an arbitrary IP, defeating the throttler just as thoroughly in the other direction. Add a TRUST_PROXY env var to configuration.ts and .env.example that accepts Express's documented forms — a hop count (1 for a single known proxy, which is what most deployments want), a subnet/address list, or false (the default for a directly-exposed process).
Document in the README which value to use for the repo's own Docker Compose and for a typical ingress deployment, and add a main.spec.ts case asserting app.set is called with 'trust proxy' and the configured value.
- 主要言語
- TypeScript
- スター
- 2
- フォーク
- 90
- 平均マージ
- 31分
- マージ済み PR(30日)
- 20
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
MergeFi/backend のほかの issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
bug security
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
bug testing
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
bug security
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
MergeFi/backend の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
zapcooking/frontend#946 ·
メンテナーはふだん 1 日以内に返信
-
enhancement good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
WiliamRosa/cosmos-semantic-platform#40 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 77/100
liberation-data/drivine#733 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
makecindy/cindy#10062 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信