Skip to content

fix(agent): exempt direct loopback callers from the IP whitelist#1744

Open
Scra3 wants to merge 1 commit into
mainfrom
fix/ip-whitelist-internal-callers
Open

fix(agent): exempt direct loopback callers from the IP whitelist#1744
Scra3 wants to merge 1 commit into
mainfrom
fix/ip-whitelist-internal-callers

Conversation

@Scra3

@Scra3 Scra3 commented Jul 8, 2026

Copy link
Copy Markdown
Member

What

The agent's IP whitelist evaluated x-forwarded-for ?? request.ip on all /forest routes. The embedded workflow-executor reaches /forest over a loopback socket with no real client IP, so once a customer enabled the whitelist the executor was rejected as 127.0.0.1403 at its probe → the executor never started.

Fix

IpWhitelist.checkIp now exempts a trusted internal caller before the rule check: one reaching us directly over a loopback socket with no proxy hop (no x-forwarded-for).

  • Keys on req.socket.remoteAddress (the true socket peer) — never request.ip / x-forwarded-for, which are spoofable once the host app sets app.proxy.
  • Feature-agnostic: no coupling to any specific token/scope. A caller behind a same-host reverse proxy carries x-forwarded-for, so it is not exempted and its real IP is still checked normally.
  • Safe: checkIp runs after koa-jwt, so an exempted request is already authenticated; a remote attacker can neither forge the socket peer nor pass koa-jwt without the authSecret.

Scope (deliberately narrow)

Fixes only the executor case. Whether the MCP channel should be subject to the IP whitelist is a deferred product decision (Slack thread, PRD-752). Consequences, documented (PRD-753), not changed here:

  • Mounted MCP in-process + whitelist ON -> tool calls remain 403 (known limitation, not silently exempted).
  • Standalone mcp-server + whitelist ON -> the mcp-server's IP must be whitelisted on the agent (pre-existing).

Tests

packages/agent/test/routes/security/ip-whitelist.test.ts — exemption for the 3 loopback socket forms with no proxy hop; not exempt for a non-loopback socket, nor for a loopback socket carrying x-forwarded-for (proxied user); anti-spoof (external socket + x-forwarded-for: 127.0.0.1 -> 403 on the header IP); non-regression (proxied user from a whitelisted IP passes the normal check).

Note

Exempt direct loopback callers from IP whitelist checks in agent

Requests originating from a loopback socket (127.0.0.1, ::1, ::ffff:127.0.0.1) with no x-forwarded-for header now bypass IP whitelist validation in IpWhitelist.checkIp. A Debug log line is emitted for each exempted request. Loopback sockets that carry an x-forwarded-for header are still subject to normal IP rule checks using the header value.

Macroscope summarized 5306eaa.

@qltysh

qltysh Bot commented Jul 8, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

⬆️ Merging this pull request will increase total coverage on main by 0.01%.

Modified Files with Diff Coverage (1)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent/src/routes/security/ip-whitelist.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@Scra3 Scra3 force-pushed the fix/ip-whitelist-internal-callers branch 2 times, most recently from 27ef873 to df8902f Compare July 8, 2026 11:09
Comment thread packages/mcp-server/src/forest-oauth-provider.ts Outdated
Comment thread packages/mcp-server/src/server.ts
@Scra3 Scra3 force-pushed the fix/ip-whitelist-internal-callers branch from df8902f to 206ab05 Compare July 8, 2026 11:18
Comment thread packages/mcp-server/src/forest-oauth-provider.ts Outdated
@Scra3 Scra3 force-pushed the fix/ip-whitelist-internal-callers branch from 206ab05 to 071d76b Compare July 8, 2026 13:17
@Scra3 Scra3 changed the title fix(agent): IP whitelist — exempt authenticated internal callers, enforce at the MCP perimeter fix(agent): exempt the loopback executor from the IP whitelist Jul 8, 2026
@Scra3 Scra3 force-pushed the fix/ip-whitelist-internal-callers branch from 071d76b to 4f2ddf5 Compare July 8, 2026 13:34
The IP whitelist evaluated `x-forwarded-for ?? request.ip` on all /forest. The
embedded workflow-executor reaches /forest over a loopback socket with no real
client IP, so with the whitelist enabled it was rejected as 127.0.0.1 (403 at
probe → the executor never started).

ip-whitelist `checkIp` now exempts a trusted internal caller before the rule
check: one reaching us directly over a loopback socket with no proxy hop (no
`x-forwarded-for`). It keys on `req.socket.remoteAddress` (the true peer), never
request.ip / x-forwarded-for (spoofable, and request.ip follows the header once
the host app sets app.proxy). The check is feature-agnostic — a caller behind a
same-host reverse proxy carries `x-forwarded-for` and is not exempted, so its
real IP is still checked; and `checkIp` runs after koa-jwt, so an exempted
request is already authenticated.

Scope: the executor case only. Whether the MCP channel should be subject to the
IP whitelist is a deferred product decision (PRD-752) — mounted-MCP in-process
calls remain 403 when the whitelist is enabled, documented as a known limitation
(PRD-753) rather than silently exempted here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the fix/ip-whitelist-internal-callers branch from 4f2ddf5 to 5306eaa Compare July 8, 2026 13:39
@Scra3 Scra3 changed the title fix(agent): exempt the loopback executor from the IP whitelist fix(agent): exempt direct loopback callers from the IP whitelist Jul 8, 2026
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.

1 participant