Skip to content

Commit b2b5ee8

Browse files
V3 Backport [#9246]: fix: strips CF-Connecting-IP header within Wrangler (#9250)
* fix(miniflare): disable stripCfConnectingIp option by default * add a regression test from the wrangler persepective * inject monkey patched fetch to strip header * add changeset --------- Co-authored-by: Edmund Hung <[email protected]>
1 parent d3960ea commit b2b5ee8

File tree

6 files changed

+111
-1
lines changed

6 files changed

+111
-1
lines changed

.changeset/public-cameras-thank.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"miniflare": patch
3+
"wrangler": patch
4+
---
5+
6+
fix: strip `CF-Connecting-IP` header within `fetch`
7+
8+
In v4.15.0, Miniflare began stripping the `CF-Connecting-IP` header via a global outbound service, which led to a TCP connection regression due to a bug in Workerd. This PR patches the `fetch` API to strip the header during local `wrangler dev` sessions as a temporary workaround until the underlying issue is resolved.

packages/miniflare/src/plugins/core/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ const CoreOptionsSchemaInput = z.intersection(
163163
unsafeEnableAssetsRpc: z.boolean().optional(),
164164

165165
// Strip the CF-Connecting-IP header from outbound fetches
166-
stripCfConnectingIp: z.boolean().default(true),
166+
// There is an issue with the connect() API and the globalOutbound workerd setting that impacts TCP ingress
167+
// We should default it to true once https://github.com/cloudflare/workerd/pull/4145 is resolved
168+
stripCfConnectingIp: z.boolean().default(false),
167169
})
168170
);
169171
export const CoreOptionsSchema = CoreOptionsSchemaInput.transform((value) => {

packages/miniflare/test/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,7 @@ test("Miniflare: strips CF-Connecting-IP", async (t) => {
27152715
const client = new Miniflare({
27162716
script: `export default { fetch(request) { return fetch('${url.href}', {headers: {"CF-Connecting-IP":"fake-value"}}) } }`,
27172717
modules: true,
2718+
stripCfConnectingIp: true,
27182719
});
27192720
t.teardown(() => client.dispose());
27202721
t.teardown(() => server.dispose());
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import http from "node:http";
2+
import path from "node:path";
3+
import dedent from "ts-dedent";
4+
import { startWorker } from "../../../api/startDevWorker";
5+
import { runInTempDir } from "../../helpers/run-in-tmp";
6+
import { seed } from "../../helpers/seed";
7+
8+
describe("startWorker", () => {
9+
runInTempDir();
10+
11+
// We do not inject the `CF-Connecting-IP` header on Windows at the moment.
12+
// See https://github.com/cloudflare/workerd/issues/3310
13+
it.skipIf(process.platform === "win32")(
14+
"strips the CF-Connecting-IP header from all outbound requests",
15+
async (t) => {
16+
const server = http.createServer((req, res) => {
17+
res.writeHead(200);
18+
res.end(
19+
req.headers["cf-connecting-ip"] ?? "CF-Connecting-IP header stripped"
20+
);
21+
});
22+
23+
t.onTestFinished(() => {
24+
server.close();
25+
});
26+
27+
const address = server.listen(0).address();
28+
29+
if (address === null || typeof address === "string") {
30+
expect.fail("Failed to get server address");
31+
}
32+
33+
await seed({
34+
"src/index.ts": dedent`
35+
export default {
36+
fetch(request) {
37+
if (request.headers.has('CF-Connecting-IP')) {
38+
return fetch(request);
39+
}
40+
41+
return new Response("No CF-Connecting-IP header");
42+
}
43+
}
44+
`,
45+
});
46+
47+
const worker = await startWorker({
48+
name: "test-worker",
49+
entrypoint: path.resolve("src/index.ts"),
50+
});
51+
52+
t.onTestFinished(() => worker.dispose());
53+
54+
const response = await worker.fetch(`http://127.0.0.1:${address.port}`);
55+
await expect(response.text()).resolves.toEqual(
56+
"CF-Connecting-IP header stripped"
57+
);
58+
}
59+
);
60+
});

packages/wrangler/src/deployment-bundle/bundle.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,32 @@ export async function bundleWorker(
304304
inject.push(checkedFetchFileToInject);
305305
}
306306

307+
// We injected the `CF-Connecting-IP` header in the entry worker on Miniflare.
308+
// It used to be stripped by Miniflare, but that caused TCP ingress failures
309+
// because of the global outbound setup. This is a temporary workaround until
310+
// a proper fix is landed in Workerd.
311+
// See https://github.com/cloudflare/workers-sdk/issues/9238 for more details.
312+
if (targetConsumer === "dev" && local) {
313+
const stripCfConnectingIpHeaderFileToInject = path.join(
314+
tmpDir.path,
315+
"strip-cf-connecting-ip-header.js"
316+
);
317+
318+
if (!fs.existsSync(stripCfConnectingIpHeaderFileToInject)) {
319+
fs.writeFileSync(
320+
stripCfConnectingIpHeaderFileToInject,
321+
fs.readFileSync(
322+
path.resolve(
323+
getBasePath(),
324+
"templates/strip-cf-connecting-ip-header.js"
325+
)
326+
)
327+
);
328+
}
329+
330+
inject.push(stripCfConnectingIpHeaderFileToInject);
331+
}
332+
307333
// When multiple workers are running we need some way to disambiguate logs between them. Inject a patched version of `globalThis.console` that prefixes logs with the worker name
308334
if (getFlag("MULTIWORKER")) {
309335
middlewareToLoad.push({
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function stripCfConnectingIPHeader(input, init) {
2+
const request = new Request(input, init);
3+
request.headers.delete("CF-Connecting-IP");
4+
return request;
5+
}
6+
7+
globalThis.fetch = new Proxy(globalThis.fetch, {
8+
apply(target, thisArg, argArray) {
9+
return Reflect.apply(target, thisArg, [
10+
stripCfConnectingIPHeader.apply(null, argArray),
11+
]);
12+
},
13+
});

0 commit comments

Comments
 (0)