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
27 changes: 21 additions & 6 deletions packages/nextjs/src/config/getBuildPluginOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,27 @@ export function getBuildPluginOptions({

const finalIgnorePatterns = mergeIgnorePatterns(sourcemapUploadIgnore, sentryBuildOptions.sourcemaps?.ignore);

const filesToDeleteAfterUpload = createFilesToDeleteAfterUploadPattern(
normalizedDistDirAbsPath,
buildTool,
deleteSourcemapsAfterUpload,
useRunAfterProductionCompileHook,
);
const userFilesToDeleteAfterUpload = sentryBuildOptions.sourcemaps?.filesToDeleteAfterUpload;

if (sentryBuildOptions.debug && userFilesToDeleteAfterUpload !== undefined) {
// eslint-disable-next-line no-console
console.debug(
'[@sentry/nextjs] Skipping auto-deletion of source maps as user has provided filesToDeleteAfterUpload:',
userFilesToDeleteAfterUpload,
);
}

const filesToDeleteAfterUpload =
userFilesToDeleteAfterUpload !== undefined
? Array.isArray(userFilesToDeleteAfterUpload)
? userFilesToDeleteAfterUpload
: [userFilesToDeleteAfterUpload]
: createFilesToDeleteAfterUploadPattern(
normalizedDistDirAbsPath,
buildTool,
deleteSourcemapsAfterUpload,
useRunAfterProductionCompileHook,
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User patterns bypass deleteSourcemapsAfterUpload: false guard

Medium Severity

When filesToDeleteAfterUpload is provided, it completely bypasses the deleteSourcemapsAfterUpload check. If a user sets deleteSourcemapsAfterUpload: false alongside filesToDeleteAfterUpload, source maps will still be deleted — contradicting the user's explicit opt-out. The deleteSourcemapsAfterUpload: false setting is expected to act as a master toggle preventing all deletion.

Fix in Cursor Fix in Web


const skipSourcemapsUpload = shouldSkipSourcemapUpload(buildTool, useRunAfterProductionCompileHook);

Expand Down
18 changes: 18 additions & 0 deletions packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,24 @@ export type SentryBuildOptions = {
* Defaults to `true`.
*/
deleteSourcemapsAfterUpload?: boolean;

/**
* A glob or an array of globs that specifies which source map files should be deleted after being uploaded to Sentry.
*
* When set, this overrides the default deletion behavior of `deleteSourcemapsAfterUpload`.
*
* Use this option when you need fine-grained control over which source maps are deleted.
*
* @example
* ```javascript
* withSentryConfig(nextConfig, {
* sourcemaps: {
* filesToDeleteAfterUpload: ['.next/static/**\/*.map'],
* },
* });
* ```
*/
filesToDeleteAfterUpload?: string | string[];
};

/**
Expand Down
77 changes: 77 additions & 0 deletions packages/nextjs/test/config/getBuildPluginOptions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,83 @@ describe('getBuildPluginOptions', () => {
expect(result.sourcemaps?.filesToDeleteAfterUpload).toBeUndefined();
});

it('uses custom filesToDeleteAfterUpload string when provided', () => {
const sentryBuildOptions: SentryBuildOptions = {
org: 'test-org',
project: 'test-project',
sourcemaps: {
filesToDeleteAfterUpload: '.next/static/**/*.map',
},
};

const result = getBuildPluginOptions({
sentryBuildOptions,
releaseName: mockReleaseName,
distDirAbsPath: mockDistDirAbsPath,
buildTool: 'webpack-client',
});

expect(result.sourcemaps?.filesToDeleteAfterUpload).toEqual(['.next/static/**/*.map']);
});

it('uses custom filesToDeleteAfterUpload array when provided', () => {
const sentryBuildOptions: SentryBuildOptions = {
org: 'test-org',
project: 'test-project',
sourcemaps: {
filesToDeleteAfterUpload: ['.next/static/**/*.map', '.next/server/**/*.map'],
},
};

const result = getBuildPluginOptions({
sentryBuildOptions,
releaseName: mockReleaseName,
distDirAbsPath: mockDistDirAbsPath,
buildTool: 'webpack-client',
});

expect(result.sourcemaps?.filesToDeleteAfterUpload).toEqual(['.next/static/**/*.map', '.next/server/**/*.map']);
});

it('filesToDeleteAfterUpload overrides deleteSourcemapsAfterUpload default pattern', () => {
const sentryBuildOptions: SentryBuildOptions = {
org: 'test-org',
project: 'test-project',
sourcemaps: {
deleteSourcemapsAfterUpload: true,
filesToDeleteAfterUpload: ['custom/**/*.map'],
},
};

const result = getBuildPluginOptions({
sentryBuildOptions,
releaseName: mockReleaseName,
distDirAbsPath: mockDistDirAbsPath,
buildTool: 'webpack-client',
});

expect(result.sourcemaps?.filesToDeleteAfterUpload).toEqual(['custom/**/*.map']);
});

it('filesToDeleteAfterUpload bypasses server build guard', () => {
const sentryBuildOptions: SentryBuildOptions = {
org: 'test-org',
project: 'test-project',
sourcemaps: {
filesToDeleteAfterUpload: ['.next/server/**/*.map'],
},
};

const result = getBuildPluginOptions({
sentryBuildOptions,
releaseName: mockReleaseName,
distDirAbsPath: mockDistDirAbsPath,
buildTool: 'webpack-nodejs',
});

expect(result.sourcemaps?.filesToDeleteAfterUpload).toEqual(['.next/server/**/*.map']);
});

it('uses custom sourcemap assets when provided', () => {
const customAssets = ['custom/path/**', 'another/path/**'];
const sentryBuildOptions: SentryBuildOptions = {
Expand Down
Loading