Skip to content
Draft
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
42 changes: 42 additions & 0 deletions packages/cli-kit/src/public/node/notifications-system.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
Notification,
fetchNotifications,
fetchNotificationsInBackground,
filterNotifications,
showNotificationsIfNeeded,
} from './notifications-system.js'
import {renderError, renderInfo, renderWarning} from './ui.js'
import {fetch} from './http.js'
import {sniffForJson} from './path.js'
import {exec} from './system.js'
import {cacheRetrieve} from '../../private/node/conf-store.js'
Expand All @@ -14,6 +16,7 @@ vi.mock('./ui.js')
vi.mock('../../private/node/conf-store.js')
vi.mock('./path.js')
vi.mock('./system.js')
vi.mock('./http.js')

const betweenVersins1and2: Notification = {
id: 'betweenVersins1and2',
Expand Down Expand Up @@ -456,3 +459,42 @@ describe('fetchNotificationsInBackground', () => {
)
})
})

describe('fetchNotifications', () => {
test('uses the default URL when SHOPIFY_CLI_NOTIFICATIONS_URL is not secure', async () => {
// Given
vi.stubEnv('SHOPIFY_CLI_NOTIFICATIONS_URL', 'http://malicious.com/notifications.json')
vi.mocked(fetch).mockResolvedValue({
status: 200,
text: () => Promise.resolve(JSON.stringify({notifications: []})),
} as any)

// When
await fetchNotifications()

// Then
expect(fetch).toHaveBeenCalledWith(
'https://cdn.shopify.com/static/cli/notifications.json',
undefined,
expect.anything(),
)
vi.unstubAllEnvs()
})

test('uses the provided URL when SHOPIFY_CLI_NOTIFICATIONS_URL is secure', async () => {
// Given
const secureUrl = 'https://my-secure-repo.com/notifications.json'
vi.stubEnv('SHOPIFY_CLI_NOTIFICATIONS_URL', secureUrl)
vi.mocked(fetch).mockResolvedValue({
status: 200,
text: () => Promise.resolve(JSON.stringify({notifications: []})),
} as any)

// When
await fetchNotifications()

// Then
expect(fetch).toHaveBeenCalledWith(secureUrl, undefined, expect.anything())
vi.unstubAllEnvs()
})
})
20 changes: 19 additions & 1 deletion packages/cli-kit/src/public/node/notifications-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,25 @@ const COMMANDS_TO_SKIP = [
]

function url(): string {
return process.env.SHOPIFY_CLI_NOTIFICATIONS_URL ?? URL
const envUrl = process.env.SHOPIFY_CLI_NOTIFICATIONS_URL
if (envUrl) {
try {
const parsedUrl = new globalThis.URL(envUrl)
// We only allow https for notifications to prevent loading malicious content from insecure sources.
if (parsedUrl.protocol === 'https:') {
return envUrl
}
outputDebug(
`The notifications URL provided via SHOPIFY_CLI_NOTIFICATIONS_URL (${envUrl}) is not secure (https). Falling back to default.`,
)
// eslint-disable-next-line no-catch-all/no-catch-all
} catch {
outputDebug(
`The notifications URL provided via SHOPIFY_CLI_NOTIFICATIONS_URL (${envUrl}) is not a valid URL. Falling back to default.`,
)
}
}
return URL
}

const NotificationSchema = zod.object({
Expand Down
Loading