git stacks accept a compose/env path with a leading slash

This commit is contained in:
jarek committed 2026-08-18 10:05:36 +02:00
1 parent 9b6b6a9d3e
commit 6fe735ad97
1 file changed
+11 -8
+11 -8
View File
@@ -8,7 +8,7 @@
* git@host:path) passes unchanged, so this is fully backward-compatible.
*/
import { resolve, isAbsolute, sep } from 'node:path';
import { resolve, sep } from 'node:path';
export function assertSafeRepoUrl(url: string): void {
const u = (url || '').trim();
@@ -34,15 +34,18 @@ export function assertSafeGitRef(ref: string | null | undefined): void {
}
/**
* Resolve a user-supplied relative path (composePath / envFilePath) INSIDE the clone
* dir, throwing if it would ESCAPE the clone. Uses resolve-then-containment (like a plain
* `join` + escape check): an in-repo `..` (e.g. `stacks/../compose.yml`) or a leading `./`
* still resolve, only a path that escapes the clone is refused. `label` names the field
* for the error message.
* Resolve a user-supplied path (composePath / envFilePath) INSIDE the clone dir,
* throwing if it would ESCAPE the clone. Uses resolve-then-containment (like a plain
* `join` + escape check): an in-repo `..` (e.g. `stacks/../compose.yml`) or a leading
* `./` still resolve, only a path that escapes the clone is refused. A leading `/` is
* treated as repo-root-relative (`/qbittorrent/compose.yaml` -> `qbittorrent/compose.yaml`)
* since the clone is the root the user reasons about; the containment check below still
* blocks any real escape (`/../x` -> `../x` -> refused). `label` names the field for the
* error message.
*/
export function repoFilePath(repoPath: string, userRel: string, label: string): string {
if (isAbsolute(userRel)) throw new Error(`${label} must be a relative path (got "${userRel}")`);
const abs = resolve(repoPath, userRel);
const rel = userRel.replace(/^\/+/, '');
const abs = resolve(repoPath, rel);
if (abs !== repoPath && !abs.startsWith(repoPath + sep)) {
throw new Error(`${label} must be a path inside the repository (got "${userRel}")`);
}