diff --git a/src/config.spec.ts b/src/config.spec.ts index b4ff64f751c..97d88820206 100644 --- a/src/config.spec.ts +++ b/src/config.spec.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import * as os from "os"; import { expect } from "chai"; +import * as sinon from "sinon"; import { Config } from "./config"; import { FIREBASE_JSON_PATH as VALID_CONFIG_PATH } from "./test/fixtures/valid-config"; @@ -33,6 +34,10 @@ describe("Config", () => { }); describe("#path", () => { + afterEach(() => { + sinon.restore(); + }); + it("should skip an absolute path", () => { const config = new Config({}, { cwd: SIMPLE_CONFIG_DIR }); const absPath = "/Users/something"; @@ -43,6 +48,23 @@ describe("Config", () => { const relativePath = "something"; expect(config.path(relativePath)).to.eq(path.join(SIMPLE_CONFIG_DIR, relativePath)); }); + it("should allow path segments containing two dots", () => { + const publicDir = "foo..bar"; + const config = new Config({ hosting: { public: publicDir } }, { cwd: SIMPLE_CONFIG_DIR }); + const configuredPublicDir = config.get("hosting.public") as string; + expect(config.path(configuredPublicDir)).to.eq(path.join(SIMPLE_CONFIG_DIR, publicDir)); + }); + it("should reject parent directory traversal", () => { + const config = new Config({}, { cwd: SIMPLE_CONFIG_DIR }); + for (const relativePath of ["..", path.join("..", "outside")]) { + expect(() => config.path(relativePath)).to.throw("is outside of project directory"); + } + }); + it("should reject an absolute result from path.relative", () => { + const config = new Config({}, { cwd: SIMPLE_CONFIG_DIR }); + sinon.stub(path, "relative").returns(path.resolve("outside")); + expect(() => config.path("inside")).to.throw("is outside of project directory"); + }); }); describe("#parseFile", () => { diff --git a/src/config.ts b/src/config.ts index 31febdf8189..2f747dcbf41 100644 --- a/src/config.ts +++ b/src/config.ts @@ -204,7 +204,12 @@ export class Config { return pathName; } const outPath = path.normalize(path.join(this.projectDir, pathName)); - if (path.relative(this.projectDir, outPath).includes("..")) { + const relativePath = path.relative(this.projectDir, outPath); + if ( + path.isAbsolute(relativePath) || + relativePath === ".." || + relativePath.startsWith(`..${path.sep}`) + ) { throw new FirebaseError(clc.bold(pathName) + " is outside of project directory", { exit: 1 }); } return outPath;