Skip to content

Commit b929f76

Browse files
nit23uecclaude
andauthored
fix(llmo): temporary cloudflareToken body/query fallback for CF token (#2701)
## Temporary workaround — revert before/after header allowlisting The Cloudflare onboarding endpoints expect the Cloudflare token in the `x-cloudflare-token` header, but that custom header is **not yet allowlisted in the CDN/network config**, so it's stripped before reaching the service — blocking end-to-end verification. This change adds a **temporary fallback**: `getCfToken` accepts the token from a `cloudflareToken` field in the request **body** (POST: deploy / routes) or **query string** (GET: accounts / zones) when the header is absent. The header still takes precedence. ### Notes - **Intentionally NOT documented in the OpenAPI spec** — it's a temporary workaround, not part of the contract. - ⚠️ **Revert this once `x-cloudflare-token` is allowlisted** in the network config. A token in a URL query is visible in access logs, so the query path in particular should not outlive the workaround. - Controller stays at **100% coverage** (unit tests cover both the GET/query and POST/body fallback paths); full suite green. Follow-up to the merged Cloudflare onboarding work (#2681, #2697). ## Related Issues _None linked._ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a1ea182 commit b929f76

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

src/controllers/llmo/llmo-cloudflare.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import AccessControlUtil from '../../support/access-control-util.js';
2020
import { deriveWorkerName, hostInSiteDomain, routePatternHost } from './llmo-cloudflare-utils.js';
2121

2222
const CF_TOKEN_HEADER = 'x-cloudflare-token';
23+
// TEMPORARY: body/query fallback field for the CF token, used only until the
24+
// x-cloudflare-token header is allowlisted in the CDN/network config (for e2e verification).
25+
// Intentionally undocumented in the OpenAPI spec. Remove once the header passes through.
26+
const CF_TOKEN_BODY_FIELD = 'cloudflareToken';
2327
const CF_TOKEN_MISSING = 'Missing x-cloudflare-token header';
2428
const EDGE_OPTIMIZE_API_KEY_SECRET = 'EDGE_OPTIMIZE_API_KEY';
2529
const EDGE_OPTIMIZE_TARGET_HOST_BINDING = 'EDGE_OPTIMIZE_TARGET_HOST';
@@ -61,8 +65,14 @@ function LlmoCloudflareController(ctx) {
6165
};
6266

6367
const getCfToken = (context) => {
64-
const token = context.pathInfo?.headers?.[CF_TOKEN_HEADER];
65-
return hasText(token) ? token : null;
68+
const headerToken = context.pathInfo?.headers?.[CF_TOKEN_HEADER];
69+
if (hasText(headerToken)) {
70+
return headerToken;
71+
}
72+
// TEMPORARY fallback (see CF_TOKEN_BODY_FIELD): accept the token from the request
73+
// body/query while the header is not yet allowlisted in the network config.
74+
const fallbackToken = context.data?.[CF_TOKEN_BODY_FIELD];
75+
return hasText(fallbackToken) ? fallbackToken : null;
6676
};
6777

6878
/**

test/controllers/llmo/llmo-cloudflare.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,14 @@ describe('LlmoCloudflareController', () => {
184184
expect(res.status).to.equal(400);
185185
});
186186

187+
it('falls back to the cloudflareToken in query/body when the header is absent', async () => {
188+
mockContext.pathInfo.headers = {};
189+
mockContext.data = { cloudflareToken: CF_TOKEN };
190+
mockCfClient.listAccounts.resolves([{ id: ACCOUNT_ID, name: 'Test Account' }]);
191+
const res = await controller.listAccounts(mockContext);
192+
expect(res.status).to.equal(200);
193+
});
194+
187195
it('returns 404 when site is not found', async () => {
188196
mockContext.dataAccess.Site.findById.resolves(null);
189197
const res = await controller.listAccounts(mockContext);
@@ -313,6 +321,17 @@ describe('LlmoCloudflareController', () => {
313321
});
314322
});
315323

324+
it('accepts the cloudflareToken from the body when the header is absent', async () => {
325+
mockContext.pathInfo.headers = {};
326+
mockContext.data = {
327+
accountId: ACCOUNT_ID, targetHost: TARGET_HOST, cloudflareToken: CF_TOKEN,
328+
};
329+
mockCfClient.deployWorkerScript.resolves();
330+
mockCfClient.setWorkerSecret.resolves();
331+
const res = await controller.deployWorker(mockContext);
332+
expect(res.status).to.equal(200);
333+
});
334+
316335
it('fetches the worker script from a pinned commit SHA with a timeout', async () => {
317336
mockCfClient.deployWorkerScript.resolves();
318337
mockCfClient.setWorkerSecret.resolves();

0 commit comments

Comments
 (0)