From 0cca7cad6356e343dcbfc2d79e284faf84662722 Mon Sep 17 00:00:00 2001 From: gonzaloriestra <14979109+gonzaloriestra@users.noreply.github.com> Date: Sat, 16 May 2026 00:25:19 +0000 Subject: [PATCH] [Security] Redact all Shopify tokens in analytics --- .jules/sentinel.md | 4 ++++ .../cli-kit/src/public/node/analytics.test.ts | 19 +++++++++++++++---- packages/cli-kit/src/public/node/analytics.ts | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000000..808ae6169d4 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-05-15 - Redacting all Shopify tokens in analytics +**Vulnerability:** Shopify access tokens (shpat_, shpua_, shpca_) were not redacted from analytics payloads, only Theme Access tokens (shptka_). +**Learning:** Analytics payloads often contain serialized command arguments and metadata which can inadvertently include sensitive tokens if not explicitly sanitized. +**Prevention:** Use a broad regex pattern at the final sanitization boundary to capture all known token formats. diff --git a/packages/cli-kit/src/public/node/analytics.test.ts b/packages/cli-kit/src/public/node/analytics.test.ts index 8d1a68fe80c..63da9c124c0 100644 --- a/packages/cli-kit/src/public/node/analytics.test.ts +++ b/packages/cli-kit/src/public/node/analytics.test.ts @@ -164,12 +164,21 @@ describe('event tracking', () => { }) }) - test('does not send passwords to Monorail', async () => { + test('does not send any shopify tokens to Monorail', async () => { await inProjectWithFile('package.json', async (args) => { // Given const commandContent = {command: 'dev', topic: 'app'} - const argsWithPassword = args.concat(['--password', 'shptka_abc123']) - await startAnalytics({commandContent, args: argsWithPassword, currentTime: currentDate.getTime() - 100}) + const argsWithTokens = args.concat([ + '--password', + 'shptka_abc123', + '--token', + 'shpat_abc123', + '--user-token', + 'shpua_abc123', + '--custom-token', + 'shpca_abc123', + ]) + await startAnalytics({commandContent, args: argsWithTokens, currentTime: currentDate.getTime() - 100}) // When const config = { @@ -180,7 +189,9 @@ describe('event tracking', () => { // Then const expectedPayloadSensitive = { - args: expect.stringMatching(/.*password \*\*\*\*\*/), + args: expect.stringMatching( + /.*password \*\*\*\*\*.*token \*\*\*\*\*.*user-token \*\*\*\*\*.*custom-token \*\*\*\*\*/, + ), metadata: expect.anything(), } expect(publishEventMock).toHaveBeenCalledOnce() diff --git a/packages/cli-kit/src/public/node/analytics.ts b/packages/cli-kit/src/public/node/analytics.ts index 5578e3af328..23765900231 100644 --- a/packages/cli-kit/src/public/node/analytics.ts +++ b/packages/cli-kit/src/public/node/analytics.ts @@ -200,8 +200,8 @@ async function buildPayload({config, errorMessage, exitMode}: ReportAnalyticsEve function sanitizePayload(payload: T): T { const payloadString = JSON.stringify(payload) - // Remove Theme Access passwords from the payload - const sanitizedPayloadString = payloadString.replace(/shptka_\w*/g, '*****') + // Remove Shopify tokens from the payload + const sanitizedPayloadString = payloadString.replace(/shp[a-z0-9]{1,6}_\w*/g, '*****') return JSON.parse(sanitizedPayloadString) }