diff --git a/.changeset/old-teachers-bow.md b/.changeset/old-teachers-bow.md new file mode 100644 index 000000000000..c7eb420a2a18 --- /dev/null +++ b/.changeset/old-teachers-bow.md @@ -0,0 +1,7 @@ +--- +"wrangler": minor +--- + +feat: add sanitised error messages to Wrangler telemetry + +Error messages that have been audited for potential inclusion of personal information, and explicitly opted-in, are now included in Wrangler's telemetry collection. Collected error messages will not include any filepaths, user input or any other potentially private content. diff --git a/packages/wrangler/src/__tests__/errors.test.ts b/packages/wrangler/src/__tests__/errors.test.ts new file mode 100644 index 000000000000..8cec76ebf27a --- /dev/null +++ b/packages/wrangler/src/__tests__/errors.test.ts @@ -0,0 +1,135 @@ +import { + CommandLineArgsError, + DeprecationError, + FatalError, + MissingConfigError, + UserError, +} from "../errors"; +import { APIError, ParseError } from "../parse"; + +describe("errors", () => { + describe("UserError", () => { + it("takes a custom telemetry message", () => { + const error = new UserError("message", { telemetryMessage: "telemetry" }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("telemetry"); + }); + it("can set telemetryMessage to equal the main message", () => { + const error = new UserError("message", { telemetryMessage: true }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("message"); + }); + }); + + describe("DeprecationError", () => { + it("takes a custom telemetry message", () => { + const error = new DeprecationError("message", { + telemetryMessage: "telemetry", + }); + expect(error.message).toBe("Deprecation:\nmessage"); + expect(error.telemetryMessage).toBe("telemetry"); + }); + it("can set telemetryMessage to equal the main message", () => { + const error = new DeprecationError("message", { telemetryMessage: true }); + expect(error.message).toBe("Deprecation:\nmessage"); + expect(error.telemetryMessage).toBe("Deprecation:\nmessage"); + }); + }); + + describe("FatalError", () => { + it("takes a custom telemetry message", () => { + const error = new FatalError("message", undefined, { + telemetryMessage: "telemetry", + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("telemetry"); + expect(error.code).toBeUndefined(); + }); + it("can set telemetryMessage to equal the main message", () => { + const error = new FatalError("message", 1, { telemetryMessage: true }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("message"); + expect(error.code).toBe(1); + }); + }); + + describe("CommandLineArgsError", () => { + it("takes a custom telemetry message", () => { + const error = new CommandLineArgsError("message", { + telemetryMessage: "telemetry", + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("telemetry"); + }); + it("can set telemetryMessage to equal the main message", () => { + const error = new CommandLineArgsError("message", { + telemetryMessage: true, + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("message"); + }); + }); + + describe("JsonFriendlyFatalError", () => { + it("takes a custom telemetry message", () => { + const error = new FatalError("message", undefined, { + telemetryMessage: "telemetry", + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("telemetry"); + expect(error.code).toBeUndefined(); + }); + it("can set telemetryMessage to equal the main message", () => { + const error = new FatalError("message", 1, { telemetryMessage: true }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("message"); + expect(error.code).toBe(1); + }); + }); + + describe("MissingConfigError", () => { + it("just sets the telemetry message as the main message", () => { + const error = new MissingConfigError("message"); + expect(error.message).toBe("Missing config value for message"); + expect(error.telemetryMessage).toBe("Missing config value for message"); + }); + }); + + describe("ParseError", () => { + it("takes a custom telemetry message", () => { + const error = new ParseError({ + text: "message", + telemetryMessage: "telemetry", + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("telemetry"); + }); + it("can set telemetryMessage to equal the main message", () => { + const error = new ParseError({ + text: "message", + telemetryMessage: true, + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("message"); + }); + }); + + describe("APIError", () => { + it("takes a custom telemetry message", () => { + const error = new APIError({ + text: "message", + telemetryMessage: "telemetry", + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("telemetry"); + }); + it("can set telemetryMessage to equal the main message", () => { + const error = new APIError({ + text: "message", + telemetryMessage: true, + }); + expect(error.message).toBe("message"); + expect(error.telemetryMessage).toBe("message"); + }); + }); +}); diff --git a/packages/wrangler/src/__tests__/metrics.test.ts b/packages/wrangler/src/__tests__/metrics.test.ts index d0b5ac3d18b9..2b995223470f 100644 --- a/packages/wrangler/src/__tests__/metrics.test.ts +++ b/packages/wrangler/src/__tests__/metrics.test.ts @@ -321,6 +321,7 @@ describe("metrics", () => { durationSeconds: 6, durationMinutes: 0.1, errorType: "TypeError", + errorMessage: undefined, }, }; @@ -445,6 +446,34 @@ describe("metrics", () => { expect(std.debug).toContain('"isInteractive":false,'); }); + it("should include an error message if the specific error has been allow-listed with {telemetryMessage:true}", async () => { + setIsTTY(false); + const requests = mockMetricRequest(); + + await expect( + runWrangler("docs arg -j=false") + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Wrangler now supports wrangler.json configuration files by default and ignores the value of the \`--experimental-json-config\` flag.]` + ); + expect(requests.count).toBe(2); + expect(std.debug).toContain( + '"errorMessage":"Wrangler now supports wrangler.json configuration files by default and ignores the value of the `--experimental-json-config` flag."' + ); + }); + + it("should include an error message if the specific error has been allow-listed with a custom telemetry message", async () => { + setIsTTY(false); + const requests = mockMetricRequest(); + + await expect( + runWrangler("bloop") + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Unknown argument: bloop]` + ); + expect(requests.count).toBe(2); + expect(std.debug).toContain('"errorMessage":"yargs validation error"'); + }); + describe("banner", () => { beforeEach(() => { vi.mocked(getWranglerVersion).mockReturnValue("1.2.3"); diff --git a/packages/wrangler/src/__tests__/parse.test.ts b/packages/wrangler/src/__tests__/parse.test.ts index b87828136ec3..88d21ad5f99f 100644 --- a/packages/wrangler/src/__tests__/parse.test.ts +++ b/packages/wrangler/src/__tests__/parse.test.ts @@ -142,6 +142,7 @@ describe("parseTOML", () => { lineText: "name = 'fail\"", }, notes: [], + telemetryMessage: "TOML parse error", }); } }); @@ -163,6 +164,7 @@ describe("parseTOML", () => { fileText: "\n[name", }, notes: [], + telemetryMessage: "TOML parse error", }); } }); @@ -232,6 +234,7 @@ describe("parseJSON", () => { fileText: `\n{\n"version" "1\n}\n`, }, notes: [], + telemetryMessage: "JSON(C) parse error", }); expect(text).toEqual("UnexpectedEndOfString"); } @@ -257,6 +260,7 @@ describe("parseJSON", () => { lineText: `\t\t\t"c":[012345]`, }, notes: [], + telemetryMessage: "JSON(C) parse error", }); } }); @@ -339,6 +343,7 @@ describe("parseJSONC", () => { fileText: `\n{\n"version" "1\n}\n`, }, notes: [], + telemetryMessage: "JSON(C) parse error", }); } }); @@ -363,6 +368,7 @@ describe("parseJSONC", () => { lineText: `\t\t\t"c":[012345]`, }, notes: [], + telemetryMessage: "JSON(C) parse error", }); } }); diff --git a/packages/wrangler/src/assets.ts b/packages/wrangler/src/assets.ts index e22c6e95703e..7df9018404e4 100644 --- a/packages/wrangler/src/assets.ts +++ b/packages/wrangler/src/assets.ts @@ -77,7 +77,8 @@ export const syncAssets = async ( if (!initializeAssetsResponse.jwt) { throw new FatalError( "Could not find assets information to attach to deployment. Please try again.", - 1 + 1, + { telemetryMessage: true } ); } logger.info(`No files to upload. Proceeding with deployment...`); @@ -102,7 +103,11 @@ export const syncAssets = async ( if (manifestEntry === undefined) { throw new FatalError( `A file was requested that does not appear to exist.`, - 1 + 1, + { + telemetryMessage: + "A file was requested that does not appear to exist. (asset manifest upload)", + } ); } // just logging file uploads at the moment... @@ -182,7 +187,9 @@ export const syncAssets = async ( throw new FatalError( `Upload took too long.\n` + `Asset upload took too long on bucket ${bucketIndex + 1}/${initializeAssetsResponse.buckets.length}. Please try again.\n` + - `Assets already uploaded have been saved, so the next attempt will automatically resume from this point.` + `Assets already uploaded have been saved, so the next attempt will automatically resume from this point.`, + undefined, + { telemetryMessage: "Asset upload took too long" } ); } else { throw e; @@ -207,7 +214,8 @@ export const syncAssets = async ( if (!completionJwt) { throw new FatalError( "Failed to complete asset upload. Please try again.", - 1 + 1, + { telemetryMessage: true } ); } @@ -249,7 +257,8 @@ const buildAssetManifest = async (dir: string) => { throw new UserError( `Maximum number of assets exceeded.\n` + `Cloudflare Workers supports up to ${MAX_ASSET_COUNT.toLocaleString()} assets in a version. We found ${counter.toLocaleString()} files in the specified assets directory "${dir}".\n` + - `Ensure your assets directory contains a maximum of ${MAX_ASSET_COUNT.toLocaleString()} files, and that you have specified your assets directory correctly.` + `Ensure your assets directory contains a maximum of ${MAX_ASSET_COUNT.toLocaleString()} files, and that you have specified your assets directory correctly.`, + { telemetryMessage: "Maximum number of assets exceeded" } ); } @@ -267,7 +276,8 @@ const buildAssetManifest = async (dir: string) => { binary: true, } )}.\n` + - `Ensure all assets in your assets directory "${dir}" conform with the Workers maximum size requirement.` + `Ensure all assets in your assets directory "${dir}" conform with the Workers maximum size requirement.`, + { telemetryMessage: "Asset too large" } ); } manifest[normalizeFilePath(relativeFilepath)] = { @@ -335,12 +345,15 @@ export function getAssetsOptions( if (directory === undefined) { throw new UserError( - "The `assets` property in your configuration is missing the required `directory` property." + "The `assets` property in your configuration is missing the required `directory` property.", + { telemetryMessage: true } ); } if (directory === "") { - throw new UserError("`The assets directory cannot be an empty string."); + throw new UserError("`The assets directory cannot be an empty string.", { + telemetryMessage: true, + }); } const assetsBasePath = getAssetsBasePath(config, args.assets); @@ -353,7 +366,11 @@ export function getAssetsOptions( throw new UserError( `The directory specified by the ${sourceOfTruthMessage} does not exist:\n` + - `${resolvedAssetsPath}` + `${resolvedAssetsPath}`, + + { + telemetryMessage: `The assets directory specified does not exist`, + } ); } @@ -413,7 +430,11 @@ export function validateAssetsArgsAndConfig( ) { throw new UserError( "Cannot use assets and legacy assets in the same Worker.\n" + - "Please remove either the `legacy_assets` or `assets` field from your configuration file." + "Please remove either the `legacy_assets` or `assets` field from your configuration file.", + { + telemetryMessage: + "Cannot use assets and legacy assets in the same Worker", + } ); } @@ -440,7 +461,8 @@ export function validateAssetsArgsAndConfig( ) { throw new UserError( "Cannot use assets with a binding in an assets-only Worker.\n" + - "Please remove the asset binding from your configuration file, or provide a Worker script in your configuration file (`main`)." + "Please remove the asset binding from your configuration file, or provide a Worker script in your configuration file (`main`).", + { telemetryMessage: true } ); } @@ -481,7 +503,8 @@ export function validateAssetsArgsAndConfig( ) { throw new UserError( "Cannot set experimental_serve_directly=false without a Worker script.\n" + - "Please remove experimental_serve_directly from your configuration file, or provide a Worker script in your configuration file (`main`)." + "Please remove experimental_serve_directly from your configuration file, or provide a Worker script in your configuration file (`main`).", + { telemetryMessage: true } ); } } @@ -526,12 +549,15 @@ function errorOnLegacyPagesWorkerJSAsset( ? "directory" : null; if (workerJsType !== null) { - throw new UserError(dedent` + throw new UserError( + dedent` Uploading a Pages _worker.js ${workerJsType} as an asset. This could expose your private server-side code to the public Internet. Is this intended? If you do not want to upload this ${workerJsType}, either remove it or add an "${CF_ASSETS_IGNORE_FILENAME}" file, to the root of your asset directory, containing "_worker.js" to avoid uploading. If you do want to upload this ${workerJsType}, you can add an empty "${CF_ASSETS_IGNORE_FILENAME}" file, to the root of your asset directory, to hide this error. - `); + `, + { telemetryMessage: true } + ); } } } diff --git a/packages/wrangler/src/d1/execute.ts b/packages/wrangler/src/d1/execute.ts index 24d76c5c8651..f9fcd8071af8 100644 --- a/packages/wrangler/src/d1/execute.ts +++ b/packages/wrangler/src/d1/execute.ts @@ -116,7 +116,9 @@ export const Handler = async (args: HandlerOptions): Promise => { if (file && command) { throw createFatalError( `Error: can't provide both --command and --file.`, - json + json, + undefined, + { telemetryMessage: true } ); } diff --git a/packages/wrangler/src/delete.ts b/packages/wrangler/src/delete.ts index 6a05069d29f8..5f074ba2eaa1 100644 --- a/packages/wrangler/src/delete.ts +++ b/packages/wrangler/src/delete.ts @@ -98,7 +98,8 @@ export async function deleteHandler(args: DeleteArgs) { if (config.pages_build_output_dir) { throw new UserError( "It looks like you've run a Workers-specific command in a Pages project.\n" + - "For Pages, please run `wrangler pages project delete` instead." + "For Pages, please run `wrangler pages project delete` instead.", + { telemetryMessage: true } ); } metrics.sendMetricsEvent( @@ -112,7 +113,11 @@ export async function deleteHandler(args: DeleteArgs) { const scriptName = getScriptName(args, config); if (!scriptName) { throw new UserError( - `A worker name must be defined, either via --name, or in your ${configFileName(config.configPath)} file` + `A worker name must be defined, either via --name, or in your ${configFileName(config.configPath)} file`, + { + telemetryMessage: + "`A worker name must be defined, either via --name, or in your config file", + } ); } diff --git a/packages/wrangler/src/deployments.ts b/packages/wrangler/src/deployments.ts index 999b81e30184..106592d55647 100644 --- a/packages/wrangler/src/deployments.ts +++ b/packages/wrangler/src/deployments.ts @@ -157,13 +157,16 @@ export async function rollbackDeployment( if (deploys.length < 2) { throw new UserError( - "Cannot rollback to previous deployment since there are less than 2 deployments" + "Cannot rollback to previous deployment since there are less than 2 deployments", + { telemetryMessage: true } ); } deploymentId = deploys.at(-2)?.id; if (deploymentId === undefined) { - throw new UserError("Cannot find previous deployment"); + throw new UserError("Cannot find previous deployment", { + telemetryMessage: true, + }); } } @@ -265,7 +268,9 @@ export async function viewDeployment( deploymentId = latest.id; if (deploymentId === undefined) { - throw new UserError("Cannot find previous deployment"); + throw new UserError("Cannot find previous deployment", { + telemetryMessage: true, + }); } } @@ -333,7 +338,10 @@ export async function commonDeploymentCMDSetup( if (!scriptName) { throw new UserError( - `Required Worker name missing. Please specify the Worker name in your ${configFileName(config.configPath)} file, or pass it as an argument with \`--name\`` + `Required Worker name missing. Please specify the Worker name in your ${configFileName(config.configPath)} file, or pass it as an argument with \`--name\``, + { + telemetryMessage: `Required Worker name missing. Please specify the Worker name in your config file, or pass it as an argument with \`--name\``, + } ); } diff --git a/packages/wrangler/src/dialogs.ts b/packages/wrangler/src/dialogs.ts index f8ecba8a4cf2..8c47c0caf8f2 100644 --- a/packages/wrangler/src/dialogs.ts +++ b/packages/wrangler/src/dialogs.ts @@ -9,7 +9,9 @@ export class NoDefaultValueProvided extends UserError { // This is user-facing, so make the message something understandable // It _should_ always be caught and replaced with a more descriptive error // but this is fine as a fallback. - super("This command cannot be run in a non-interactive context"); + super("This command cannot be run in a non-interactive context", { + telemetryMessage: true, + }); Object.setPrototypeOf(this, new.target.prototype); } } diff --git a/packages/wrangler/src/errors.ts b/packages/wrangler/src/errors.ts index e273d9572c98..509ff98b1909 100644 --- a/packages/wrangler/src/errors.ts +++ b/packages/wrangler/src/errors.ts @@ -1,3 +1,11 @@ +/** + * This is used to provide telemetry with a sanitised error + * message that could not have any user-identifying information. + * Set to `true` to duplicate `message`. + * */ +export type TelemetryMessage = { + telemetryMessage?: string | true; +}; /** * Base class for errors where the user has done something wrong. These are not * reported to Sentry. API errors are intentionally *not* `UserError`s, and are @@ -5,26 +13,33 @@ * messaging. */ export class UserError extends Error { - constructor(...args: ConstructorParameters) { - super(...args); + telemetryMessage: string | undefined; + constructor( + message?: string | undefined, + options?: (ErrorOptions & TelemetryMessage) | undefined + ) { + super(message, options); // Restore prototype chain: // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget Object.setPrototypeOf(this, new.target.prototype); + this.telemetryMessage = + options?.telemetryMessage === true ? message : options?.telemetryMessage; } } export class DeprecationError extends UserError { - constructor(message: string) { - super(`Deprecation:\n${message}`); + constructor(message: string, options?: TelemetryMessage) { + super(`Deprecation:\n${message}`, options); } } export class FatalError extends UserError { constructor( message?: string, - readonly code?: number + readonly code?: number | undefined, + options?: TelemetryMessage ) { - super(message); + super(message, options); } } @@ -41,15 +56,18 @@ export class CommandLineArgsError extends UserError {} export class JsonFriendlyFatalError extends FatalError { constructor( message?: string, - readonly code?: number + readonly code?: number, + options?: TelemetryMessage ) { - super(message); + super(message, code, options); } } export class MissingConfigError extends Error { + telemetryMessage: string | undefined; constructor(key: string) { super(`Missing config value for ${key}`); + this.telemetryMessage = `Missing config value for ${key}`; } } @@ -61,11 +79,16 @@ export class MissingConfigError extends Error { export function createFatalError( message: unknown, isJson: boolean, - code?: number + code?: number, + telemetryMessage?: TelemetryMessage ): Error { if (isJson) { - return new JsonFriendlyFatalError(JSON.stringify(message), code); + return new JsonFriendlyFatalError( + JSON.stringify(message), + code, + telemetryMessage + ); } else { - return new FatalError(`${message}`, code); + return new FatalError(`${message}`, code, telemetryMessage); } } diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 33ce6dec0fa0..db04b6db7490 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -206,7 +206,9 @@ export function createCLIParser(argv: string[]) { if (!error || error.name === "YError") { // If there is no error or the error is a "YError", then this came from yargs own validation // Wrap it in a `CommandLineArgsError` so that we can handle it appropriately further up. - error = new CommandLineArgsError(msg); + error = new CommandLineArgsError(msg, { + telemetryMessage: "yargs validation error", + }); } throw error; }) @@ -254,7 +256,8 @@ export function createCLIParser(argv: string[]) { .check((args) => { if (args["experimental-json-config"] === false) { throw new CommandLineArgsError( - `Wrangler now supports wrangler.json configuration files by default and ignores the value of the \`--experimental-json-config\` flag.` + `Wrangler now supports wrangler.json configuration files by default and ignores the value of the \`--experimental-json-config\` flag.`, + { telemetryMessage: true } ); } return true; @@ -1143,6 +1146,7 @@ export async function main(argv: string[]): Promise { durationMinutes: durationMs / 1000 / 60, errorType: errorType ?? (e instanceof Error ? e.constructor.name : undefined), + errorMessage: e instanceof UserError ? e.telemetryMessage : undefined, }, argv ); diff --git a/packages/wrangler/src/metrics/types.ts b/packages/wrangler/src/metrics/types.ts index 28ebdf2c2396..caa6d694817a 100644 --- a/packages/wrangler/src/metrics/types.ts +++ b/packages/wrangler/src/metrics/types.ts @@ -125,5 +125,9 @@ export type Events = * Type of error, e.g. UserError, APIError. Does not include stack trace or error message. */ errorType: string | undefined; + /** + * Sanitised error messages that will not include user information like filepaths or stack traces (e.g. `Asset too large`). + */ + errorMessage: string | undefined; }; }; diff --git a/packages/wrangler/src/parse.ts b/packages/wrangler/src/parse.ts index 6e662a0d476d..1e50cb2a4109 100644 --- a/packages/wrangler/src/parse.ts +++ b/packages/wrangler/src/parse.ts @@ -5,6 +5,7 @@ import { formatMessagesSync } from "esbuild"; import * as jsoncParser from "jsonc-parser"; import { UserError } from "./errors"; import { logger } from "./logger"; +import type { TelemetryMessage } from "./errors"; import type { ParseError as JsoncParseError } from "jsonc-parser"; export type Message = { @@ -12,7 +13,7 @@ export type Message = { location?: Location; notes?: Message[]; kind?: "warning" | "error"; -}; +} & TelemetryMessage; export type Location = File & { line: number; @@ -56,8 +57,8 @@ export class ParseError extends UserError implements Message { readonly location?: Location; readonly kind: "warning" | "error"; - constructor({ text, notes, location, kind }: Message) { - super(text); + constructor({ text, notes, location, kind, telemetryMessage }: Message) { + super(text, { telemetryMessage }); this.name = this.constructor.name; this.text = text; this.notes = notes ?? []; @@ -132,7 +133,11 @@ export function parseTOML(input: string, file?: string): TOML.JsonMap | never { file, fileText: input, }; - throw new ParseError({ text, location }); + throw new ParseError({ + text, + location, + telemetryMessage: "TOML parse error", + }); } } @@ -180,6 +185,7 @@ export function parseJSONC( ...indexLocation({ file, fileText: input }, errors[0].offset + 1), length: errors[0].length, }, + telemetryMessage: "JSON(C) parse error", }); } return data; @@ -219,6 +225,7 @@ export function readFileSync(file: string): string { text: message.replace(file, resolve(file)), }, ], + telemetryMessage: "Could not read file", }); } } diff --git a/packages/wrangler/src/tail/index.ts b/packages/wrangler/src/tail/index.ts index a2fdf30b00ae..ee0a5aff703d 100644 --- a/packages/wrangler/src/tail/index.ts +++ b/packages/wrangler/src/tail/index.ts @@ -229,7 +229,8 @@ export const tailCommand = createCommand({ throw createFatalError( "Tail disconnected, exiting.", args.format === "json", - 1 + 1, + { telemetryMessage: true } ); } waitingForPong = true; diff --git a/packages/wrangler/telemetry.md b/packages/wrangler/telemetry.md index 6bf9fd86ff6c..0c867319748a 100644 --- a/packages/wrangler/telemetry.md +++ b/packages/wrangler/telemetry.md @@ -21,7 +21,7 @@ Telemetry in Wrangler allows us to better identify bugs and gain visibility on u - The format of the Wrangler configuration file (e.g. `toml`, `jsonc`) - Total session duration of the command run (e.g. 3 seconds, etc.) - Whether the Wrangler client is running in CI or in an interactive instance -- Error _type_, if one occurs (e.g. `APIError` or `UserError`) +- Error _type_ (e.g. `APIError` or `UserError`), and sanitised error messages that will not include user information like filepaths or stack traces (e.g. `Asset too large`). - General machine information such as OS and OS Version Cloudflare will receive the IP address associated with your machine and such information is handled in accordance with Cloudflare’s [Privacy Policy](https://www.cloudflare.com/privacypolicy/).