diff --git a/src/fs/loader.spec.ts b/src/fs/loader.spec.ts index 81116b2d9c..d45fce8305 100644 --- a/src/fs/loader.spec.ts +++ b/src/fs/loader.spec.ts @@ -30,5 +30,11 @@ describe('fs/loader', function () { const result = toValueSync(loader.lookup('./foo/bar', LookupType.Partials, true, '/root/current')) expect(result).toBe(resolve('/root/foo/bar')) }) + it('should enforce containment for LookupType.Root', function () { + const mockFs = { ...fs, existsSync: () => true, exists: async () => true } + const loader = new Loader({ relativeReference: false, fs: mockFs, extname: '', root: ['/safe'] } as any) + expect(() => toValueSync(loader.lookup('/etc/hosts', LookupType.Root, true))) + .toThrow(/ENOENT/) + }) }) }) diff --git a/src/fs/loader.ts b/src/fs/loader.ts index b0e471e114..bf2fc8261f 100644 --- a/src/fs/loader.ts +++ b/src/fs/loader.ts @@ -43,15 +43,12 @@ export class Loader { public * lookup (file: string, type: LookupType, sync?: boolean, currentFile?: string): Generator { const dirs = this.options[type] - const enforceRoot = type !== LookupType.Root for (const filepath of this.candidates(file, dirs, currentFile)) { - if (enforceRoot) { - let allowed = false - for (const dir of dirs) { - if (yield this.contains(!!sync, dir, filepath)) { allowed = true; break } - } - if (!allowed) continue + let allowed = false + for (const dir of dirs) { + if (yield this.contains(!!sync, dir, filepath)) { allowed = true; break } } + if (!allowed) continue if (yield this.exists(!!sync, filepath)) return filepath } throw this.lookupError(file, dirs) diff --git a/test/e2e/render-file.spec.ts b/test/e2e/render-file.spec.ts index 3bc1f5a556..6c6227e5a9 100644 --- a/test/e2e/render-file.spec.ts +++ b/test/e2e/render-file.spec.ts @@ -38,6 +38,7 @@ describe('#renderFile()', function () { return expect(html).toContain('"name": "liquidjs"') }) it('should render file with context', async function () { + engine = new Liquid({ root: views, extname: '.html' }) const html = await engine.renderFile(resolve(views, 'name.html'), { name: 'harttle' }) return expect(html).toBe('My name is harttle.') }) diff --git a/test/e2e/render-to-node-stream.spec.ts b/test/e2e/render-to-node-stream.spec.ts index edfc25fc5e..1488d3bee7 100644 --- a/test/e2e/render-to-node-stream.spec.ts +++ b/test/e2e/render-to-node-stream.spec.ts @@ -4,8 +4,8 @@ import { drainStream } from '../stub/stream' describe('.renderToNodeStream()', function () { it('should render to stream in Node.js', done => { const cjs = require('../../dist/liquid.node') - const engine = new cjs.Liquid() - const tpl = engine.parseFileSync(resolve(__dirname, '../stub/root/foo.html')) + const engine = new cjs.Liquid({ root: resolve(__dirname, '../stub/root/') }) + const tpl = engine.parseFileSync('foo.html') const stream = engine.renderToNodeStream(tpl) let html = '' stream.on('data', (data: string) => { html += data }) diff --git a/test/integration/liquid/liquid.spec.ts b/test/integration/liquid/liquid.spec.ts index c778acb4bd..918b1aabe7 100644 --- a/test/integration/liquid/liquid.spec.ts +++ b/test/integration/liquid/liquid.spec.ts @@ -109,6 +109,7 @@ describe('Liquid', function () { }) }) describe('#renderFile', function () { + afterEach(restore) it('should throw with lookup list when file not exist', function () { const engine = new Liquid({ root: ['/boo', '/root/'], @@ -116,6 +117,22 @@ describe('Liquid', function () { }) return expect(engine.renderFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/) }) + it('should reject absolute paths outside root', async function () { + mock({ + '/safe/foo.html': 'safe', + '/etc/secret': 'SECRET' + }) + const engine = new Liquid({ root: ['/safe'] }) + await expect(engine.renderFile('/etc/secret')).rejects.toThrow(/Failed to lookup/) + }) + it('should reject absolute paths outside root (sync)', function () { + mock({ + '/safe/foo.html': 'safe', + '/etc/secret': 'SECRET' + }) + const engine = new Liquid({ root: ['/safe'] }) + expect(() => engine.renderFileSync('/etc/secret')).toThrow(/Failed to lookup/) + }) }) describe('#parseFile', function () { it('should throw with lookup list when file not exist', function () { @@ -127,7 +144,7 @@ describe('Liquid', function () { }) it('should fallback to require.resolve in Node.js', async function () { const engine = new Liquid({ - root: ['/root/'], + root: [process.cwd()], extname: '.html' }) const tpls = await engine.parseFileSync('jest')