Skip to content

Commit 29e69b9

Browse files
committed
fix(add): refuse to write a template outside the project
1 parent 2a1e5b0 commit 29e69b9

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

‎packages/nuxt-cli/src/commands/add-template.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { styleText } from 'node:util'
66

77
import{intro,outro}from'@clack/prompts'
88
import{defineCommand}from'citty'
9-
import{dirname,resolve}from'pathe'
9+
import{dirname,isAbsolute,relative,resolve}from'pathe'
1010

1111
import{loadKit}from'../utils/kit'
1212
import{logger}from'../utils/logger'
@@ -99,6 +99,13 @@ export default defineCommand({
9999
constkit=awaitloadKit(cwd)
100100
constconfig=awaitkit.loadNuxtConfig({ cwd })
101101
constres=templates[templateName]({ name,args: ctx.args,nuxtOptions: config})
102+
103+
if(escapesRoot(cwd,res.path)){
104+
logger.error(`Refusing to write outside ${styleText('cyan',cwd)}: ${styleText('cyan',res.path)}.`)
105+
logger.info('Pass a name relative to the project, without leading slashes or `..` segments.')
106+
process.exit(1)
107+
}
108+
102109
constparentDir=dirname(res.path)
103110
constcreatedDir=awaitfsp.mkdir(parentDir,{recursive: true})
104111
if(createdDir){
@@ -123,3 +130,8 @@ export default defineCommand({
123130
outro(`Generated a new ${styleText('cyan',templateName)}!`)
124131
},
125132
})
133+
134+
exportfunctionescapesRoot(root: string,path: string): boolean{
135+
constrel=relative(resolve(root),resolve(path))
136+
return!rel||rel.startsWith('../')||rel==='..'||isAbsolute(rel)
137+
}

‎packages/nuxt-cli/test/unit/templates.spec.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
importtype{NuxtOptions}from'@nuxt/schema'
2+
import{resolve}from'pathe'
23
import{describe,expect,it}from'vitest'
34

5+
import{escapesRoot}from'../../src/commands/add-template'
46
import{templates}from'../../src/utils/templates/index'
57

68
describe('templates',()=>{
@@ -10,3 +12,21 @@ describe('templates', () => {
1012
}
1113
})
1214
})
15+
16+
describe('escapesRoot',()=>{
17+
it('should reject a name that traverses out of the project',()=>{
18+
expect(escapesRoot('/project',resolve('/project/components','../../etc/passwd.vue'))).toBe(true)
19+
})
20+
21+
it('should reject an absolute name',()=>{
22+
expect(escapesRoot('/project',resolve('/project/components','/etc/passwd.vue'))).toBe(true)
23+
})
24+
25+
it('should reject the project root itself',()=>{
26+
expect(escapesRoot('/project','/project')).toBe(true)
27+
})
28+
29+
it('should allow a nested name',()=>{
30+
expect(escapesRoot('/project',resolve('/project/components','nested/Thing.vue'))).toBe(false)
31+
})
32+
})

0 commit comments

Comments
 (0)