Skip to content

Commit 90c8bd7

Browse files
feat: support skew protection token (#3269)
Co-authored-by: Mateusz Bocian <mrstork@users.noreply.github.com>
1 parent a592004 commit 90c8bd7

File tree

2 files changed

+76
-6
lines changed

2 files changed

+76
-6
lines changed

src/build/skew-protection.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe('shouldEnableSkewProtection', () => {
3333

3434
// Reset env vars
3535
delete process.env.NETLIFY_NEXT_SKEW_PROTECTION
36+
delete process.env.NETLIFY_SKEW_PROTECTION_TOKEN
3637
// Set valid DEPLOY_ID by default
3738
process.env.DEPLOY_ID = 'test-deploy-id'
3839

@@ -72,6 +73,7 @@ describe('shouldEnableSkewProtection', () => {
7273
expect(result).toEqual({
7374
enabled: true,
7475
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
76+
token: 'test-deploy-id',
7577
})
7678
})
7779

@@ -83,6 +85,7 @@ describe('shouldEnableSkewProtection', () => {
8385
expect(result).toEqual({
8486
enabled: true,
8587
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
88+
token: 'test-deploy-id',
8689
})
8790
})
8891
})
@@ -121,6 +124,7 @@ describe('shouldEnableSkewProtection', () => {
121124
expect(result).toEqual({
122125
enabled: true,
123126
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_FF,
127+
token: 'test-deploy-id',
124128
})
125129
})
126130

@@ -146,6 +150,7 @@ describe('shouldEnableSkewProtection', () => {
146150
expect(result).toEqual({
147151
enabled: false,
148152
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID,
153+
token: undefined,
149154
})
150155
})
151156

@@ -158,6 +163,7 @@ describe('shouldEnableSkewProtection', () => {
158163
expect(result).toEqual({
159164
enabled: false,
160165
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID,
166+
token: '0',
161167
})
162168
})
163169

@@ -171,6 +177,7 @@ describe('shouldEnableSkewProtection', () => {
171177
expect(result).toEqual({
172178
enabled: false,
173179
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID_ENV_VAR,
180+
token: '0',
174181
})
175182
})
176183
})
@@ -197,6 +204,50 @@ describe('shouldEnableSkewProtection', () => {
197204
expect(result).toEqual({
198205
enabled: true,
199206
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
207+
token: 'test-deploy-id',
208+
})
209+
})
210+
})
211+
212+
describe('NETLIFY_SKEW_PROTECTION_TOKEN handling', () => {
213+
it('should prefer NETLIFY_SKEW_PROTECTION_TOKEN over DEPLOY_ID', () => {
214+
process.env.NETLIFY_NEXT_SKEW_PROTECTION = 'true'
215+
process.env.NETLIFY_SKEW_PROTECTION_TOKEN = 'custom-token'
216+
process.env.DEPLOY_ID = 'deploy-id'
217+
218+
const result = shouldEnableSkewProtection(mockCtx)
219+
220+
expect(result).toEqual({
221+
enabled: true,
222+
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
223+
token: 'custom-token',
224+
})
225+
})
226+
227+
it('should fall back to DEPLOY_ID when NETLIFY_SKEW_PROTECTION_TOKEN is not set', () => {
228+
process.env.NETLIFY_NEXT_SKEW_PROTECTION = 'true'
229+
delete process.env.NETLIFY_SKEW_PROTECTION_TOKEN
230+
process.env.DEPLOY_ID = 'deploy-id'
231+
232+
const result = shouldEnableSkewProtection(mockCtx)
233+
234+
expect(result).toEqual({
235+
enabled: true,
236+
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_ENV_VAR,
237+
token: 'deploy-id',
238+
})
239+
})
240+
241+
it('should use NETLIFY_SKEW_PROTECTION_TOKEN with feature flag', () => {
242+
mockCtx.featureFlags = { 'next-runtime-skew-protection': true }
243+
process.env.NETLIFY_SKEW_PROTECTION_TOKEN = 'ff-custom-token'
244+
245+
const result = shouldEnableSkewProtection(mockCtx)
246+
247+
expect(result).toEqual({
248+
enabled: true,
249+
enabledOrDisabledReason: EnabledOrDisabledReason.OPT_IN_FF,
250+
token: 'ff-custom-token',
200251
})
201252
})
202253
})
@@ -217,6 +268,7 @@ describe('setSkewProtection', () => {
217268

218269
// Reset env vars
219270
delete process.env.NETLIFY_NEXT_SKEW_PROTECTION
271+
delete process.env.NETLIFY_SKEW_PROTECTION_TOKEN
220272
delete process.env.NEXT_DEPLOYMENT_ID
221273
// Set valid DEPLOY_ID by default
222274
process.env.DEPLOY_ID = 'test-deploy-id'
@@ -331,4 +383,20 @@ describe('setSkewProtection', () => {
331383
'Setting up Next.js Skew Protection due to NETLIFY_NEXT_SKEW_PROTECTION=1 environment variable.',
332384
)
333385
})
386+
387+
it('should use NETLIFY_SKEW_PROTECTION_TOKEN when available', async () => {
388+
process.env.NETLIFY_NEXT_SKEW_PROTECTION = 'true'
389+
process.env.NETLIFY_SKEW_PROTECTION_TOKEN = 'custom-skew-token'
390+
process.env.DEPLOY_ID = 'deploy-id'
391+
392+
vi.mocked(dirname).mockReturnValue('/test/path')
393+
394+
await setSkewProtection(mockCtx, mockSpan)
395+
396+
expect(process.env.NEXT_DEPLOYMENT_ID).toBe('custom-skew-token')
397+
expect(mockSpan.setAttribute).toHaveBeenCalledWith(
398+
'skewProtection',
399+
EnabledOrDisabledReason.OPT_IN_ENV_VAR,
400+
)
401+
})
334402
})

src/build/skew-protection.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ export function shouldEnableSkewProtection(ctx: PluginContext) {
6363
}
6464
}
6565

66-
if (
67-
(!process.env.DEPLOY_ID || process.env.DEPLOY_ID === '0') &&
68-
optInOptions.has(enabledOrDisabledReason)
69-
) {
66+
// For compatibility with old environments, fall back to the raw deploy ID.
67+
const token = process.env.NETLIFY_SKEW_PROTECTION_TOKEN || process.env.DEPLOY_ID
68+
69+
if ((!token || token === '0') && optInOptions.has(enabledOrDisabledReason)) {
7070
// We can't proceed without a valid DEPLOY_ID, because Next.js does inline deploy ID at build time
7171
// This should only be the case for CLI deploys
7272
return {
@@ -78,17 +78,19 @@ export function shouldEnableSkewProtection(ctx: PluginContext) {
7878
: // this is silent disablement to avoid spam logs for users opted in via feature flag
7979
// that don't explicitly opt in via env var
8080
EnabledOrDisabledReason.OPT_OUT_NO_VALID_DEPLOY_ID,
81+
token,
8182
}
8283
}
8384

8485
return {
8586
enabled: optInOptions.has(enabledOrDisabledReason),
8687
enabledOrDisabledReason,
88+
token: token as string,
8789
}
8890
}
8991

9092
export const setSkewProtection = async (ctx: PluginContext, span: Span) => {
91-
const { enabled, enabledOrDisabledReason } = shouldEnableSkewProtection(ctx)
93+
const { enabled, enabledOrDisabledReason, token } = shouldEnableSkewProtection(ctx)
9294

9395
span.setAttribute('skewProtection', enabledOrDisabledReason)
9496

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

112-
process.env.NEXT_DEPLOYMENT_ID = process.env.DEPLOY_ID
114+
process.env.NEXT_DEPLOYMENT_ID = token
113115

114116
await mkdir(dirname(ctx.skewProtectionConfigPath), {
115117
recursive: true,

0 commit comments

Comments
 (0)