Skip to content

Commit d033a7d

Browse files
authored
fix: strips CF-Connecting-IP header within Wrangler (#9246)
1 parent 07f4010 commit d033a7d

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
@@ -164,7 +164,9 @@ const CoreOptionsSchemaInput = z.intersection(
164164
tails: z.array(ServiceDesignatorSchema).optional(),
165165

166166
// Strip the CF-Connecting-IP header from outbound fetches
167-
stripCfConnectingIp: z.boolean().default(true),
167+
// There is an issue with the connect() API and the globalOutbound workerd setting that impacts TCP ingress
168+
// We should default it to true once https://github.com/cloudflare/workerd/pull/4145 is resolved
169+
stripCfConnectingIp: z.boolean().default(false),
168170
})
169171
);
170172
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
@@ -3010,6 +3010,7 @@ test("Miniflare: strips CF-Connecting-IP", async (t) => {
30103010
const client = new Miniflare({
30113011
script: `export default { fetch(request) { return fetch('${serverUrl.href}', {headers: {"CF-Connecting-IP":"fake-value"}}) } }`,
30123012
modules: true,
3013+
stripCfConnectingIp: true,
30133014
});
30143015
t.teardown(() => client.dispose());
30153016
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
@@ -262,6 +262,32 @@ export async function bundleWorker(
262262
inject.push(checkedFetchFileToInject);
263263
}
264264

265+
// We injected the `CF-Connecting-IP` header in the entry worker on Miniflare.
266+
// It used to be stripped by Miniflare, but that caused TCP ingress failures
267+
// because of the global outbound setup. This is a temporary workaround until
268+
// a proper fix is landed in Workerd.
269+
// See https://github.com/cloudflare/workers-sdk/issues/9238 for more details.
270+
if (targetConsumer === "dev" && local) {
271+
const stripCfConnectingIpHeaderFileToInject = path.join(
272+
tmpDir.path,
273+
"strip-cf-connecting-ip-header.js"
274+
);
275+
276+
if (!fs.existsSync(stripCfConnectingIpHeaderFileToInject)) {
277+
fs.writeFileSync(
278+
stripCfConnectingIpHeaderFileToInject,
279+
fs.readFileSync(
280+
path.resolve(
281+
getBasePath(),
282+
"templates/strip-cf-connecting-ip-header.js"
283+
)
284+
)
285+
);
286+
}
287+
288+
inject.push(stripCfConnectingIpHeaderFileToInject);
289+
}
290+
265291
// 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
266292
if (getFlag("MULTIWORKER")) {
267293
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)