From d3c9076898a661f627e0ed83ead9d0607db8d1b6 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:39:09 +0200 Subject: [PATCH 1/2] feat(js): Add shared dataCollection prompt and preset (v11 only) Adds src/utils/data-collection.ts: a v11 gate (sdkSupportsDataCollection), a yes/no prompt with telemetry (askShouldReduceDataCollection), and a renderer for the PII-reducing dataCollection preset with deny lists (getDataCollectionSnippet). Also hoists the duplicated private getMajor helpers from sveltekit and react-router into src/utils/semver.ts as getMajorVersion. --- CHANGELOG.md | 1 + src/react-router/sdk-version.ts | 18 +---- src/sveltekit/utils.ts | 20 ++--- src/utils/data-collection.ts | 116 +++++++++++++++++++++++++++++ src/utils/semver.ts | 28 ++++++- test/utils/data-collection.test.ts | 92 +++++++++++++++++++++++ 6 files changed, 245 insertions(+), 30 deletions(-) create mode 100644 src/utils/data-collection.ts create mode 100644 test/utils/data-collection.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c4dd114fd..587bebee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - feat(sveltekit): Import `sentrySvelteKit` from `@sentry/sveltekit/vite` on SDK v11 and write `sentryUrl` for self-hosted ([#1348](https://github.com/getsentry/sentry-wizard/pull/1348)) - feat(cloudflare): Write `nodejs_compat` instead of `nodejs_als` to the wrangler config and drop a stale `nodejs_als` ([#1353](https://github.com/getsentry/sentry-wizard/pull/1353)) - feat(js): Warn when the wizard runs on a Node.js version that SDK v11 does not support ([#1354](https://github.com/getsentry/sentry-wizard/pull/1354)) +- feat(js): Add shared dataCollection prompt and preset (v11 only) ([#TODO](https://github.com/getsentry/sentry-wizard/pull/TODO)) - fix(sourcemaps): Treat prerelease SDK versions as up to date ([#1345](https://github.com/getsentry/sentry-wizard/pull/1345)) ## 7.0.3 diff --git a/src/react-router/sdk-version.ts b/src/react-router/sdk-version.ts index e064404fa..a5c7ab626 100644 --- a/src/react-router/sdk-version.ts +++ b/src/react-router/sdk-version.ts @@ -1,4 +1,4 @@ -import { major, minVersion } from 'semver'; +import { getMajorVersion } from '../utils/semver'; /** * Version range of `@sentry/react-router` (and `@sentry/profiling-node`) the @@ -27,23 +27,11 @@ export function getSentryReactRouterVitePluginImportPath( installedSdkVersion: string | undefined, ): string { const sdkMajor = - getMajor(installedSdkVersion) ?? - getMajor(SENTRY_REACT_ROUTER_SDK_RANGE) ?? + getMajorVersion(installedSdkVersion) ?? + getMajorVersion(SENTRY_REACT_ROUTER_SDK_RANGE) ?? 0; return sdkMajor >= 11 ? SENTRY_REACT_ROUTER_VITE_IMPORT_PATH : SENTRY_REACT_ROUTER_ROOT_IMPORT_PATH; } - -function getMajor(version: string | undefined): number | undefined { - if (!version) { - return undefined; - } - try { - const minVer = minVersion(version); - return minVer ? major(minVer) : undefined; - } catch { - return undefined; - } -} diff --git a/src/sveltekit/utils.ts b/src/sveltekit/utils.ts index 6d17ea90e..cae048ea0 100644 --- a/src/sveltekit/utils.ts +++ b/src/sveltekit/utils.ts @@ -1,4 +1,6 @@ -import { lt, major, minVersion } from 'semver'; +import { lt, minVersion } from 'semver'; + +import { getMajorVersion } from '../utils/semver'; /** * Version range of `@sentry/sveltekit` the wizard installs. Also decides the @@ -22,25 +24,15 @@ export function getSentrySvelteKitVitePluginImportPath( installedSdkVersion: string | undefined, ): string { const sdkMajor = - getMajor(installedSdkVersion) ?? getMajor(SENTRY_SVELTEKIT_SDK_RANGE) ?? 0; + getMajorVersion(installedSdkVersion) ?? + getMajorVersion(SENTRY_SVELTEKIT_SDK_RANGE) ?? + 0; return sdkMajor >= 11 ? SENTRY_SVELTEKIT_VITE_IMPORT_PATH : SENTRY_SVELTEKIT_ROOT_IMPORT_PATH; } -function getMajor(version: string | undefined): number | undefined { - if (!version) { - return undefined; - } - try { - const minVer = minVersion(version); - return minVer ? major(minVer) : undefined; - } catch { - return undefined; - } -} - export type KitVersionBucket = | 'none' | 'invalid' diff --git a/src/utils/data-collection.ts b/src/utils/data-collection.ts new file mode 100644 index 000000000..5f1858ee0 --- /dev/null +++ b/src/utils/data-collection.ts @@ -0,0 +1,116 @@ +// @ts-expect-error - clack is ESM and TS complains about that. It works though +import * as clack from '@clack/prompts'; +import * as Sentry from '@sentry/node'; + +import { traceStep } from '../telemetry'; +import { abortIfCancelled } from './clack'; +import { getMajorVersion } from './semver'; + +export const DATA_COLLECTION_DOCS_URL = + 'https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection'; + +/** + * Keys that commonly reveal user identity in HTTP headers, cookies, and URL + * query parameters (forwarding chains, IP addresses, user hints). + */ +const PII_KEY_DENYLIST = ['forwarded', '-ip', 'remote-', 'via', '-user']; + +const PII_KEY_DENYLIST_SNIPPET = `[${PII_KEY_DENYLIST.map( + (key) => `"${key}"`, +).join(', ')}]`; + +/** + * `dataCollection` value the wizard writes when the user opts to reduce data + * collection. Kept as an object so magicast-based wizards can insert it as an + * AST node; `getDataCollectionSnippet` renders the same preset for + * template-string-based wizards. + */ +export const DATA_COLLECTION_PII_PRESET = { + userInfo: false, + graphQL: { document: false, variables: false }, + genAI: { inputs: false, outputs: false }, + databaseQueryData: false, + queues: false, + httpBodies: [], + httpHeaders: { deny: [...PII_KEY_DENYLIST] }, + cookies: { deny: [...PII_KEY_DENYLIST] }, + urlQueryParams: { deny: [...PII_KEY_DENYLIST] }, +}; + +export const DATA_COLLECTION_COMMENT_LINES = [ + 'Turns off collection of data that could identify users. Adjust per category:', + DATA_COLLECTION_DOCS_URL, +]; + +/** + * Renders the PII preset (with its explanatory comment) as code to embed in a + * generated `Sentry.init` options object. Every line is prefixed with + * `indent`; the result ends without a trailing newline. + */ +export function getDataCollectionSnippet(indent = ' '): string { + const lines = [ + ...DATA_COLLECTION_COMMENT_LINES.map((line) => `// ${line}`), + 'dataCollection: {', + ' userInfo: false,', + ' graphQL: { document: false, variables: false },', + ' genAI: { inputs: false, outputs: false },', + ' databaseQueryData: false,', + ' queues: false,', + ' httpBodies: [],', + ` httpHeaders: { deny: ${PII_KEY_DENYLIST_SNIPPET} },`, + ` cookies: { deny: ${PII_KEY_DENYLIST_SNIPPET} },`, + ` urlQueryParams: { deny: ${PII_KEY_DENYLIST_SNIPPET} },`, + '},', + ]; + + return lines.map((line) => `${indent}${line}`).join('\n'); +} + +/** + * The `dataCollection` option only makes sense to offer from SDK v11 on, + * where the SDK collects rich context by default. Unknown or unparseable + * installed versions fall back to the major of the range the wizard installs. + */ +export function sdkSupportsDataCollection( + installedSdkVersion: string | undefined, + wizardInstallRange: string, +): boolean { + const sdkMajor = + getMajorVersion(installedSdkVersion) ?? + getMajorVersion(wizardInstallRange) ?? + 0; + + return sdkMajor >= 11; +} + +/** + * Asks whether the wizard should write the PII-reducing `dataCollection` + * preset. Call this only when `sdkSupportsDataCollection` returned `true`. + */ +export async function askShouldReduceDataCollection(): Promise { + return traceStep('data-collection', async () => { + const reduceDataCollection: boolean = await abortIfCancelled( + clack.select({ + message: + 'Sentry collects request data, user info, and other context by default. Do you want to reduce this to avoid sending personally identifiable information (PII)?', + initialValue: false, + options: [ + { + value: true, + label: 'Yes', + hint: 'Add a dataCollection config to Sentry.init() that turns off PII-heavy categories', + }, + { + value: false, + label: 'No', + hint: 'Keep the SDK defaults', + }, + ], + }), + ); + + Sentry.setTag('data-collection-reduced', reduceDataCollection); + + return reduceDataCollection; + }); +} diff --git a/src/utils/semver.ts b/src/utils/semver.ts index e14d4bc1d..3109fb7ce 100644 --- a/src/utils/semver.ts +++ b/src/utils/semver.ts @@ -1,4 +1,30 @@ -import { satisfies, subset, valid, validRange } from 'semver'; +import { + major, + minVersion, + satisfies, + subset, + valid, + validRange, +} from 'semver'; + +/** + * Returns the major version of an exact version or the lowest version that a + * range permits (e.g. `^10` -> 10, `~11.2.0` -> 11). Returns `undefined` for + * missing or unparseable input. + */ +export function getMajorVersion( + version: string | undefined, +): number | undefined { + if (!version) { + return undefined; + } + try { + const minVer = minVersion(version); + return minVer ? major(minVer) : undefined; + } catch { + return undefined; + } +} export function fulfillsVersionRange({ version, diff --git a/test/utils/data-collection.test.ts b/test/utils/data-collection.test.ts new file mode 100644 index 000000000..bbc3b5e41 --- /dev/null +++ b/test/utils/data-collection.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, it } from 'vitest'; + +import { + getDataCollectionSnippet, + sdkSupportsDataCollection, +} from '../../src/utils/data-collection'; +import { getMajorVersion } from '../../src/utils/semver'; + +describe('getMajorVersion', () => { + it.each([ + ['^10', 10], + ['^10.73.0', 10], + ['~11.2.0', 11], + ['11.0.0', 11], + // Prerelease versions keep their major, so v11 rc/beta installs pass the gate + ['11.0.0-rc.1', 11], + ['^11.0.0-rc.0', 11], + ['~11.0.0-beta.2', 11], + ['>=10.57.0 <12', 10], + ])('returns the lowest permitted major for %s', (version, expected) => { + expect(getMajorVersion(version)).toBe(expected); + }); + + it.each([undefined, '', 'not-a-version', 'workspace:*'])( + 'returns undefined for %s', + (version) => { + expect(getMajorVersion(version)).toBeUndefined(); + }, + ); +}); + +describe('sdkSupportsDataCollection', () => { + it.each([ + ['^11.0.0', '^10'], + ['11.2.3', '^10'], + ['^12', '^10'], + ['11.0.0-rc.1', '^10'], + ['^11.0.0-beta.0', '^10'], + ])('returns true for installed version %s', (installed, range) => { + expect(sdkSupportsDataCollection(installed, range)).toBe(true); + }); + + it.each([ + ['^10.73.0', '^10'], + ['10.99.0', '^10'], + ['^9', '^10'], + ['10.99.0-rc.1', '^10'], + ])('returns false for installed version %s', (installed, range) => { + expect(sdkSupportsDataCollection(installed, range)).toBe(false); + }); + + it('falls back to the wizard install range when the installed version is missing', () => { + expect(sdkSupportsDataCollection(undefined, '^11')).toBe(true); + expect(sdkSupportsDataCollection(undefined, '^10')).toBe(false); + }); + + it('falls back to the wizard install range when the installed version is unparseable', () => { + expect(sdkSupportsDataCollection('workspace:*', '^11')).toBe(true); + expect(sdkSupportsDataCollection('workspace:*', '^10')).toBe(false); + }); + + it('returns false when neither version is parseable', () => { + expect(sdkSupportsDataCollection(undefined, 'not-a-version')).toBe(false); + }); +}); + +describe('getDataCollectionSnippet', () => { + it('renders the preset with the default indentation', () => { + expect(getDataCollectionSnippet()).toMatchInlineSnapshot(` + " // Turns off collection of data that could identify users. Adjust per category: + // https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection + dataCollection: { + userInfo: false, + graphQL: { document: false, variables: false }, + genAI: { inputs: false, outputs: false }, + databaseQueryData: false, + queues: false, + httpBodies: [], + httpHeaders: { deny: ["forwarded", "-ip", "remote-", "via", "-user"] }, + cookies: { deny: ["forwarded", "-ip", "remote-", "via", "-user"] }, + urlQueryParams: { deny: ["forwarded", "-ip", "remote-", "via", "-user"] }, + }," + `); + }); + + it('prefixes every line with the given indent', () => { + const snippet = getDataCollectionSnippet(' '); + for (const line of snippet.split('\n')) { + expect(line.startsWith(' ')).toBe(true); + } + }); +}); From 98e48d6579d2be8e926c176001b5d06b04318b92 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:51:12 +0200 Subject: [PATCH 2/2] add pr number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 587bebee2..660298d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - feat(sveltekit): Import `sentrySvelteKit` from `@sentry/sveltekit/vite` on SDK v11 and write `sentryUrl` for self-hosted ([#1348](https://github.com/getsentry/sentry-wizard/pull/1348)) - feat(cloudflare): Write `nodejs_compat` instead of `nodejs_als` to the wrangler config and drop a stale `nodejs_als` ([#1353](https://github.com/getsentry/sentry-wizard/pull/1353)) - feat(js): Warn when the wizard runs on a Node.js version that SDK v11 does not support ([#1354](https://github.com/getsentry/sentry-wizard/pull/1354)) -- feat(js): Add shared dataCollection prompt and preset (v11 only) ([#TODO](https://github.com/getsentry/sentry-wizard/pull/TODO)) +- feat(js): Add shared dataCollection prompt and preset (v11 only) ([#1358](https://github.com/getsentry/sentry-wizard/pull/1358)) - fix(sourcemaps): Treat prerelease SDK versions as up to date ([#1345](https://github.com/getsentry/sentry-wizard/pull/1345)) ## 7.0.3