diff --git a/.changeset/redact-device-authorization-log.md b/.changeset/redact-device-authorization-log.md new file mode 100644 index 00000000000..cb7de8c8b31 --- /dev/null +++ b/.changeset/redact-device-authorization-log.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli-kit': patch +--- + +Redact sensitive device authorization values from debug output. diff --git a/packages/cli-kit/src/private/node/session/device-authorization.test.ts b/packages/cli-kit/src/private/node/session/device-authorization.test.ts index e87ab4fc703..f857302f081 100644 --- a/packages/cli-kit/src/private/node/session/device-authorization.test.ts +++ b/packages/cli-kit/src/private/node/session/device-authorization.test.ts @@ -68,6 +68,48 @@ describe('requestDeviceAuthorization', () => { expect(got).toEqual(dataExpected) }) + test('does not include authorization credentials in debug output', async () => { + // Given + const outputDebug = vi.spyOn(output, 'outputDebug') + const outputInfo = vi.spyOn(output, 'outputInfo') + const response = new Response(JSON.stringify(data), {status: 200}) + vi.mocked(shopifyFetch).mockResolvedValue(response) + vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') + vi.mocked(clientId).mockReturnValue('clientId') + + // When + await requestDeviceAuthorization(['scope1', 'scope2']) + + // Then + const debugOutput = JSON.stringify(outputDebug.mock.calls) + const infoOutput = JSON.stringify(outputInfo.mock.calls) + expect(debugOutput).toContain('HTTP 200') + expect(debugOutput).toContain('interval=5') + expect(debugOutput).toContain('expires_in=3600') + expect(debugOutput).not.toContain(data.device_code) + expect(debugOutput).not.toContain(data.verification_uri_complete) + expect(infoOutput).toContain(data.user_code) + expect(infoOutput).toContain(data.verification_uri_complete) + }) + + test('uses an explicit marker when the response omits the polling interval', async () => { + // Given + const outputDebug = vi.spyOn(output, 'outputDebug') + outputDebug.mockClear() + const response = new Response(JSON.stringify({...data, interval: undefined}), {status: 200}) + vi.mocked(shopifyFetch).mockResolvedValue(response) + vi.mocked(identityFqdn).mockResolvedValue('fqdn.com') + vi.mocked(clientId).mockReturnValue('clientId') + + // When + await requestDeviceAuthorization(['scope1', 'scope2']) + + // Then + const debugOutput = JSON.stringify(outputDebug.mock.calls) + expect(debugOutput).toContain('interval=not provided') + expect(debugOutput).not.toContain('interval=undefined') + }) + test('opens the browser directly in an interactive terminal', async () => { // Given const outputInfo = vi.spyOn(output, 'outputInfo') diff --git a/packages/cli-kit/src/private/node/session/device-authorization.ts b/packages/cli-kit/src/private/node/session/device-authorization.ts index 8ac629e7cc0..2152a45f6b4 100644 --- a/packages/cli-kit/src/private/node/session/device-authorization.ts +++ b/packages/cli-kit/src/private/node/session/device-authorization.ts @@ -62,7 +62,9 @@ export async function requestDeviceAuthorization(scopes: string[]): Promise