Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/dependabot-update-12661.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"miniflare": patch
"wrangler": patch
---

Update dependencies of "miniflare", "wrangler"

The following dependency versions have been updated:

| Dependency | From | To |
| ---------- | ------------ | ------------ |
| workerd | 1.20260302.0 | 1.20260303.0 |
5 changes: 5 additions & 0 deletions .changeset/free-ears-slide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/vite-plugin": patch
---

Move proxy shared secret to a constant that is reused across restarts.
10 changes: 10 additions & 0 deletions .changeset/narrow-account-redaction-to-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"wrangler": patch
---

fix: Only redact account names in CI environments, not all non-interactive contexts

The multi-account selection error in `getAccountId` now only redacts account names
when running in a CI environment (detected via `ci-info`). Non-interactive terminals
such as coding agents and piped commands can now see account names, which they need
to identify which account to configure. CI logs remain protected.
2 changes: 1 addition & 1 deletion packages/miniflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@cspotcode/source-map-support": "0.8.1",
"sharp": "^0.34.5",
"undici": "catalog:default",
"workerd": "1.20260302.0",
"workerd": "1.20260303.0",
"ws": "catalog:default",
"youch": "4.1.0-beta.10"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/vite-plugin-cloudflare/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { randomUUID } from "node:crypto";

// Worker names
export const ROUTER_WORKER_NAME = "__router-worker__";
export const ASSET_WORKER_NAME = "__asset-worker__";
export const VITE_PROXY_WORKER_NAME = "__vite_proxy_worker__";

export const PROXY_SHARED_SECRET = randomUUID();

export const kRequestType = Symbol("kRequestType");

declare module "http" {
Expand Down
7 changes: 0 additions & 7 deletions packages/vite-plugin-cloudflare/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import assert from "node:assert";
import { randomUUID } from "node:crypto";
import { Miniflare } from "miniflare";
import { getInitialWorkerNameToExportTypesMap } from "./export-types";
import { debuglog } from "./utils";
Expand Down Expand Up @@ -37,11 +36,9 @@ export class PluginContext {
#sharedContext: SharedContext;
#resolvedPluginConfig?: ResolvedPluginConfig;
#resolvedViteConfig?: vite.ResolvedConfig;
#proxySharedSecret: string;

constructor(sharedContext: SharedContext) {
this.#sharedContext = sharedContext;
this.#proxySharedSecret = randomUUID();
}

/** Creates a new Miniflare instance or updates the existing instance */
Expand Down Expand Up @@ -211,10 +208,6 @@ export class PluginContext {
getNodeJsCompat(environmentName: string): NodeJsCompat | undefined {
return this.#getWorker(environmentName)?.nodeJsCompat;
}

get proxySharedSecret(): string {
return this.#proxySharedSecret;
}
}

interface NarrowedPluginContext<T extends ResolvedPluginConfig>
Expand Down
5 changes: 3 additions & 2 deletions packages/vite-plugin-cloudflare/src/miniflare-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { getAssetsConfig } from "./asset-config";
import {
ASSET_WORKER_NAME,
kRequestType,
PROXY_SHARED_SECRET,
ROUTER_WORKER_NAME,
VITE_PROXY_WORKER_NAME,
} from "./constants";
Expand Down Expand Up @@ -426,7 +427,7 @@ export async function getDevMiniflareOptions(
return {
miniflareOptions: {
log: logger,
unsafeProxySharedSecret: ctx.proxySharedSecret,
unsafeProxySharedSecret: PROXY_SHARED_SECRET,
logRequests: false,
inspectorPort:
inputInspectorPort === false ? undefined : inputInspectorPort,
Expand Down Expand Up @@ -620,7 +621,7 @@ export async function getPreviewMiniflareOptions(
return {
miniflareOptions: {
log: logger,
unsafeProxySharedSecret: ctx.proxySharedSecret,
unsafeProxySharedSecret: PROXY_SHARED_SECRET,
inspectorPort:
inputInspectorPort === false ? undefined : inputInspectorPort,
unsafeDevRegistryPath: getDefaultDevRegistryPath(),
Expand Down
17 changes: 7 additions & 10 deletions packages/vite-plugin-cloudflare/src/plugins/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,13 @@ export const devPlugin = createPlugin("dev", (ctx) => {
const includeRulesMatcher = generateStaticRoutingRuleMatcher(
staticRouting.user_worker
);
const userWorkerHandler = createRequestHandler(
ctx,
async (request) => {
request.headers.set(CoreHeaders.ROUTE_OVERRIDE, entryWorkerName);
const userWorkerHandler = createRequestHandler(async (request) => {
request.headers.set(CoreHeaders.ROUTE_OVERRIDE, entryWorkerName);

return ctx.miniflare.dispatchFetch(request, {
redirect: "manual",
});
}
);
return ctx.miniflare.dispatchFetch(request, {
redirect: "manual",
});
});

preMiddleware = async (req, res, next) => {
assert(req.url, `req.url not defined`);
Expand Down Expand Up @@ -268,7 +265,7 @@ export const devPlugin = createPlugin("dev", (ctx) => {

// post middleware
viteDevServer.middlewares.use(
createRequestHandler(ctx, async (request, req) => {
createRequestHandler(async (request, req) => {
if (req[kRequestType] === "asset") {
request.headers.set(
CoreHeaders.ROUTE_OVERRIDE,
Expand Down
2 changes: 1 addition & 1 deletion packages/vite-plugin-cloudflare/src/plugins/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const previewPlugin = createPlugin("preview", (ctx) => {

// In preview mode we put our middleware at the front of the chain so that all assets are handled in Miniflare
vitePreviewServer.middlewares.use(
createRequestHandler(ctx, (request) => {
createRequestHandler((request) => {
return ctx.miniflare.dispatchFetch(request, { redirect: "manual" });
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const triggerHandlersPlugin = createPlugin("trigger-handlers", (ctx) => {
}

const entryWorkerName = entryWorkerConfig.name;
const requestHandler = createRequestHandler(ctx, (request) => {
const requestHandler = createRequestHandler((request) => {
request.headers.set(CoreHeaders.ROUTE_OVERRIDE, entryWorkerName);
return ctx.miniflare.dispatchFetch(request, {
redirect: "manual",
Expand Down
12 changes: 4 additions & 8 deletions packages/vite-plugin-cloudflare/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import semverGte from "semver/functions/gte";
import { version as viteVersion } from "vite";
import * as vite from "vite";
import { PROXY_SHARED_SECRET } from "./constants";
import type { PluginContext } from "./context";
import type * as http from "node:http";

Expand Down Expand Up @@ -59,7 +60,6 @@ export function withTrailingSlash(path: string): string {
}

export function createRequestHandler(
ctx: PluginContext,
handler: (
request: MiniflareRequest,
req: vite.Connect.IncomingMessage
Expand All @@ -81,7 +81,7 @@ export function createRequestHandler(
}
request = createRequest(req, res);

let response = await handler(toMiniflareRequest(ctx, request), req);
let response = await handler(toMiniflareRequest(request), req);

// Vite uses HTTP/2 when `server.https` or `preview.https` is enabled
if (req.httpVersionMajor === 2) {
Expand All @@ -106,10 +106,7 @@ export function satisfiesViteVersion(minVersion: string): boolean {
return semverGte(viteVersion, minVersion);
}

function toMiniflareRequest(
ctx: PluginContext,
request: Request
): MiniflareRequest {
function toMiniflareRequest(request: Request): MiniflareRequest {
const host = request.headers.get("Host");
const xForwardedHost = request.headers.get("X-Forwarded-Host");

Expand All @@ -122,8 +119,7 @@ function toMiniflareRequest(

// Add the proxy shared secret to the request headers
// so the proxy worker can trust it and add host headers back
// wrangler dev already does this, we need to match the behavior here
request.headers.set(CoreHeaders.PROXY_SHARED_SECRET, ctx.proxySharedSecret);
request.headers.set(CoreHeaders.PROXY_SHARED_SECRET, PROXY_SHARED_SECRET);

// Undici sets the `Sec-Fetch-Mode` header to `cors` so we capture it in a custom header to be converted back later.
const secFetchMode = request.headers.get("Sec-Fetch-Mode");
Expand Down
2 changes: 1 addition & 1 deletion packages/wrangler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"miniflare": "workspace:*",
"path-to-regexp": "6.3.0",
"unenv": "2.0.0-rc.24",
"workerd": "1.20260302.0"
"workerd": "1.20260303.0"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.721.0",
Expand Down
28 changes: 28 additions & 0 deletions packages/wrangler/src/__tests__/deploy/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as fs from "node:fs";
import { readFile } from "node:fs/promises";
import * as path from "node:path";
import { writeWranglerConfig } from "@cloudflare/workers-utils/test-helpers";
import ci from "ci-info";
import { http, HttpResponse } from "msw";
import * as TOML from "smol-toml";
import dedent from "ts-dedent";
Expand Down Expand Up @@ -877,6 +878,33 @@ describe("deploy", () => {
{ id: "R2-D2", account: { id: "nx01", name: "enterprise-nx" } },
]);

await expect(runWrangler("deploy index.js")).rejects
.toMatchInlineSnapshot(`
[Error: More than one account available but unable to select one in non-interactive mode.
Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable.
Available accounts are (\`<name>\`: \`<account_id>\`):
\`enterprise\`: \`1701\`
\`enterprise-nx\`: \`nx01\`]
`);
});

it("should redact account names in CI even when non-interactive", async () => {
setIsTTY(false);
vi.mocked(ci).isCI = true;
vi.stubEnv("CLOUDFLARE_API_TOKEN", "hunter2");
vi.stubEnv("CLOUDFLARE_ACCOUNT_ID", "");
writeWranglerConfig({
account_id: undefined,
});
writeWorkerSource();
mockSubDomainRequest();
mockUploadWorkerRequest();
mockOAuthServerCallback();
mockGetMemberships([
{ id: "IG-88", account: { id: "1701", name: "enterprise" } },
{ id: "R2-D2", account: { id: "nx01", name: "enterprise-nx" } },
]);

await expect(runWrangler("deploy index.js")).rejects
.toMatchInlineSnapshot(`
[Error: More than one account available but unable to select one in non-interactive mode.
Expand Down
12 changes: 6 additions & 6 deletions packages/wrangler/src/__tests__/kv/key.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1119,8 +1119,8 @@ describe("kv", () => {
[Error: More than one account available but unable to select one in non-interactive mode.
Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable.
Available accounts are (\`<name>\`: \`<account_id>\`):
\`(redacted)\`: \`1\`
\`(redacted)\`: \`2\`]
\`one\`: \`1\`
\`two\`: \`2\`]
`);
});

Expand All @@ -1136,8 +1136,8 @@ describe("kv", () => {
[Error: More than one account available but unable to select one in non-interactive mode.
Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable.
Available accounts are (\`<name>\`: \`<account_id>\`):
\`(redacted)\`: \`1\`
\`(redacted)\`: \`2\`]
\`one\`: \`1\`
\`two\`: \`2\`]
`);
});

Expand Down Expand Up @@ -1179,8 +1179,8 @@ describe("kv", () => {
[Error: More than one account available but unable to select one in non-interactive mode.
Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable.
Available accounts are (\`<name>\`: \`<account_id>\`):
\`(redacted)\`: \`1\`
\`(redacted)\`: \`2\`]
\`one\`: \`1\`
\`two\`: \`2\`]
`);
});
});
Expand Down
6 changes: 3 additions & 3 deletions packages/wrangler/src/__tests__/pages/secret.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,9 @@ describe("wrangler pages secret", () => {
[Error: More than one account available but unable to select one in non-interactive mode.
Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable.
Available accounts are (\`<name>\`: \`<account_id>\`):
\`(redacted)\`: \`account-id-1\`
\`(redacted)\`: \`account-id-2\`
\`(redacted)\`: \`account-id-3\`]
\`account-name-1\`: \`account-id-1\`
\`account-name-2\`: \`account-id-2\`
\`account-name-3\`: \`account-id-3\`]
`);
});
});
Expand Down
6 changes: 3 additions & 3 deletions packages/wrangler/src/__tests__/secret.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,9 @@ describe("wrangler secret", () => {
[Error: More than one account available but unable to select one in non-interactive mode.
Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable.
Available accounts are (\`<name>\`: \`<account_id>\`):
\`(redacted)\`: \`account-id-1\`
\`(redacted)\`: \`account-id-2\`
\`(redacted)\`: \`account-id-3\`]
\`account-name-1\`: \`account-id-1\`
\`account-name-2\`: \`account-id-2\`
\`account-name-3\`: \`account-id-3\`]
`);
});
});
Expand Down
9 changes: 6 additions & 3 deletions packages/wrangler/src/user/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ import {
readFileSync,
UserError,
} from "@cloudflare/workers-utils";
import ci from "ci-info";
import TOML from "smol-toml";
import dedent from "ts-dedent";
import { fetch } from "undici";
Expand Down Expand Up @@ -1287,9 +1288,11 @@ export async function getAccountId(
} catch (e) {
// Did we try to select an account in CI or a non-interactive terminal?
if (e instanceof NoDefaultValueProvided) {
// Redact account names (which may contain email addresses) in non-interactive mode
// to avoid leaking sensitive information in CI logs
const redactAccountName = isNonInteractiveOrCI();
// Redact account names (which may contain email addresses) in CI
// to avoid leaking sensitive information in public CI logs.
// Non-interactive terminals (agents, piped commands) still need
// to see account names to identify which account to configure.
const redactAccountName = ci.isCI;
throw new UserError(
`More than one account available but unable to select one in non-interactive mode.
Please set the appropriate \`account_id\` in your ${configFileName(undefined)} file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable.
Expand Down
Loading
Loading