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
68 changes: 68 additions & 0 deletions src/build/skew-protection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('shouldEnableSkewProtection', () => {

// Reset env vars
delete process.env.NETLIFY_NEXT_SKEW_PROTECTION
delete process.env.NETLIFY_SKEW_PROTECTION_TOKEN
// Set valid DEPLOY_ID by default
process.env.DEPLOY_ID = 'test-deploy-id'

Expand Down Expand Up @@ -72,6 +73,7 @@ describe('shouldEnableSkewProtection', () => {
expect(result).toEqual({
enabled: true,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
token: 'test-deploy-id',
})
})

Expand All @@ -83,6 +85,7 @@ describe('shouldEnableSkewProtection', () => {
expect(result).toEqual({
enabled: true,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
token: 'test-deploy-id',
})
})
})
Expand Down Expand Up @@ -121,6 +124,7 @@ describe('shouldEnableSkewProtection', () => {
expect(result).toEqual({
enabled: true,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_FF,
token: 'test-deploy-id',
})
})

Expand All @@ -146,6 +150,7 @@ describe('shouldEnableSkewProtection', () => {
expect(result).toEqual({
enabled: false,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID,
token: undefined,
})
})

Expand All @@ -158,6 +163,7 @@ describe('shouldEnableSkewProtection', () => {
expect(result).toEqual({
enabled: false,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID,
token: '0',
})
})

Expand All @@ -171,6 +177,7 @@ describe('shouldEnableSkewProtection', () => {
expect(result).toEqual({
enabled: false,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID_ENV_VAR,
token: '0',
})
})
})
Expand All @@ -197,6 +204,50 @@ describe('shouldEnableSkewProtection', () => {
expect(result).toEqual({
enabled: true,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
token: 'test-deploy-id',
})
})
})

describe('NETLIFY_SKEW_PROTECTION_TOKEN handling', () => {
it('should prefer NETLIFY_SKEW_PROTECTION_TOKEN over DEPLOY_ID', () => {
process.env.NETLIFY_NEXT_SKEW_PROTECTION = 'true'
process.env.NETLIFY_SKEW_PROTECTION_TOKEN = 'custom-token'
process.env.DEPLOY_ID = 'deploy-id'

const result = shouldEnableSkewProtection(mockCtx)

expect(result).toEqual({
enabled: true,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
token: 'custom-token',
})
})

it('should fall back to DEPLOY_ID when NETLIFY_SKEW_PROTECTION_TOKEN is not set', () => {
process.env.NETLIFY_NEXT_SKEW_PROTECTION = 'true'
delete process.env.NETLIFY_SKEW_PROTECTION_TOKEN
process.env.DEPLOY_ID = 'deploy-id'

const result = shouldEnableSkewProtection(mockCtx)

expect(result).toEqual({
enabled: true,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
token: 'deploy-id',
})
})

it('should use NETLIFY_SKEW_PROTECTION_TOKEN with feature flag', () => {
mockCtx.featureFlags = { 'next-runtime-skew-protection': true }
process.env.NETLIFY_SKEW_PROTECTION_TOKEN = 'ff-custom-token'

const result = shouldEnableSkewProtection(mockCtx)

expect(result).toEqual({
enabled: true,
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_FF,
token: 'ff-custom-token',
})
})
})
Expand All @@ -217,6 +268,7 @@ describe('setSkewProtection', () => {

// Reset env vars
delete process.env.NETLIFY_NEXT_SKEW_PROTECTION
delete process.env.NETLIFY_SKEW_PROTECTION_TOKEN
delete process.env.NEXT_DEPLOYMENT_ID
// Set valid DEPLOY_ID by default
process.env.DEPLOY_ID = 'test-deploy-id'
Expand Down Expand Up @@ -331,4 +383,20 @@ describe('setSkewProtection', () => {
'Setting up Next.js Skew Protection due to NETLIFY_NEXT_SKEW_PROTECTION=1 environment variable.',
)
})

it('should use NETLIFY_SKEW_PROTECTION_TOKEN when available', async () => {
process.env.NETLIFY_NEXT_SKEW_PROTECTION = 'true'
process.env.NETLIFY_SKEW_PROTECTION_TOKEN = 'custom-skew-token'
process.env.DEPLOY_ID = 'deploy-id'

vi.mocked(dirname).mockReturnValue('/test/path')

await setSkewProtection(mockCtx, mockSpan)

expect(process.env.NEXT_DEPLOYMENT_ID).toBe('custom-skew-token')
expect(mockSpan.setAttribute).toHaveBeenCalledWith(
'skewProtection',
EnabledOrDisabledReason.OPT_IN_ENV_VAR,
)
})
})
14 changes: 8 additions & 6 deletions src/build/skew-protection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ export function shouldEnableSkewProtection(ctx: PluginContext) {
}
}

if (
(!process.env.DEPLOY_ID || process.env.DEPLOY_ID === '0') &&
optInOptions.has(enabledOrDisabledReason)
) {
// For compatibility with old environments, fall back to the raw deploy ID.
const token = process.env.NETLIFY_SKEW_PROTECTION_TOKEN || process.env.DEPLOY_ID

if ((!token || token === '0') && optInOptions.has(enabledOrDisabledReason)) {
// We can't proceed without a valid DEPLOY_ID, because Next.js does inline deploy ID at build time
// This should only be the case for CLI deploys
return {
Expand All @@ -78,17 +78,19 @@ export function shouldEnableSkewProtection(ctx: PluginContext) {
: // this is silent disablement to avoid spam logs for users opted in via feature flag
// that don't explicitly opt in via env var
EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID,
token,
}
}

return {
enabled: optInOptions.has(enabledOrDisabledReason),
enabledOrDisabledReason,
token: token as string,
}
}

export const setSkewProtection = async (ctx: PluginContext, span: Span) => {
const { enabled, enabledOrDisabledReason } = shouldEnableSkewProtection(ctx)
const { enabled, enabledOrDisabledReason, token } = shouldEnableSkewProtection(ctx)

span.setAttribute('skewProtection', enabledOrDisabledReason)

Expand All @@ -109,7 +111,7 @@ export const setSkewProtection = async (ctx: PluginContext, span: Span) => {
console.log('Setting up Next.js Skew Protection.')
}

process.env.NEXT_DEPLOYMENT_ID = process.env.DEPLOY_ID
process.env.NEXT_DEPLOYMENT_ID = token

await mkdir(dirname(ctx.skewProtectionConfigPath), {
recursive: true,
Expand Down
Loading