Skip to content

Commit c193fc0

Browse files
Reject paths in theme package filenames
1 parent 098524b commit c193fc0

4 files changed

Lines changed: 39 additions & 2 deletions

File tree

‎packages/theme/src/cli/commands/theme/package.test.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import {joinPath, relativizePath, cwd} from '@shopify/cli-kit/node/path'
88
import {withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output'
99
import {renderSuccess} from '@shopify/cli-kit/node/ui'
1010
import {expect, test, vi} from 'vitest'
11+
// Native JSON paths must preserve Windows separators instead of pathe normalization.
12+
// eslint-disable-next-line no-restricted-imports
13+
import {resolve as nativePath} from 'node:path'
1114

1215
vi.mock('@shopify/cli-kit/node/ui')
1316
vi.mock('@shopify/cli-kit/node/analytics')
@@ -31,7 +34,7 @@ test.each([true, false])('writes the package receipt to stdout with version=%s',
3134

3235
await withCapturedStandardStreams(async ({stdout, stderr}) => {
3336
await runWithCommandEventsForCommand(['--json'], () => run(['--path', directory, '--json']))
34-
expect(stdout()).toBe(`${themePackageJsonOutputSchema.encode({path})}\n`)
37+
expect(stdout()).toBe(`${themePackageJsonOutputSchema.encode({path: nativePath(path)})}\n`)
3538
expect(stderr()).toBe('')
3639
})
3740
await expect(fileExists(path)).resolves.toBe(true)

‎packages/theme/src/cli/services/package.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@ const StreamZip = require('node-stream-zip')
99
vi.mock('@shopify/cli-kit/node/ui')
1010

1111
describe('packageTheme', () => {
12+
test.each([
13+
{name: '../outside', version: ''},
14+
{name: 'absolute', version: ''},
15+
{name: 'C:\\outside', version: ''},
16+
{name: 'C:outside', version: ''},
17+
{name: 'Dawn', version: '../outside'},
18+
{name: 'Dawn', version: '..\\outside'},
19+
])('rejects paths in the package filename: %j', async ({name, version}) => {
20+
await inTemporaryDirectory(async (tmpDir) => {
21+
const inputDirectory = joinPath(tmpDir, 'theme')
22+
await mkdir(joinPath(inputDirectory, 'config'))
23+
await createSettingsSchema(
24+
JSON.stringify([
25+
{
26+
name: 'theme_info',
27+
theme_name: name === 'absolute' ? joinPath(tmpDir, 'outside') : name,
28+
theme_version: version,
29+
},
30+
]),
31+
inputDirectory,
32+
)
33+
34+
await expect(packageTheme(inputDirectory)).rejects.toThrow('Theme name and version must not contain paths.')
35+
await expect(fileExists(joinPath(tmpDir, 'outside.zip'))).resolves.toBe(false)
36+
})
37+
})
38+
1239
test('creates zip file from theme', async () => {
1340
await inTemporaryDirectory(async (tmpDir) => {
1441
// Given

‎packages/theme/src/cli/services/package.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ async function getThemePackageName(inputDirectory: string) {
5353

5454
const themeNameVersion = [themeInfo.theme_name, themeInfo.theme_version].filter(Boolean).join('-')
5555

56+
if (/[/\\]/.test(themeNameVersion) || /^[a-z]:/i.test(themeNameVersion)) {
57+
throw new AbortError('Theme name and version must not contain paths.')
58+
}
59+
5660
return `${themeNameVersion}.zip`
5761
}
5862

‎packages/theme/src/cli/services/package/result.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import {themePackageJsonOutputSchema, type ThemePackageResult} from './types.js'
22
import {outputResult} from '@shopify/cli-kit/node/output'
33
import {relativizePath} from '@shopify/cli-kit/node/path'
44
import {renderSuccess} from '@shopify/cli-kit/node/ui'
5+
// Native JSON paths must preserve Windows separators instead of pathe normalization.
6+
// eslint-disable-next-line no-restricted-imports
7+
import {resolve as resolvePath} from 'node:path'
58

69
export function renderThemePackageResult(result: ThemePackageResult, format: 'text' | 'json'): void {
710
if (format === 'json') {
8-
outputResult(themePackageJsonOutputSchema.encode(result))
11+
outputResult(themePackageJsonOutputSchema.encode({...result, path: resolvePath(result.path)}))
912
return
1013
}
1114

0 commit comments

Comments
 (0)