fix(paths): recognize contained dot-prefix directories (#524)

Paths under a directory whose name starts with dots (for example ..cache) now count as inside their root, in launch cwd resolution, rig up's path-form spec handling and restore-check evidence. A real parent traversal (..) and a sibling that only shares the root's name prefix still count as outside.

As a result, the existing rule for a launch cwd inside the OpenRig installation now also applies to dot-prefixed folders there when no cwd override is given; an explicit --cwd still works as before.

Closes #494.
This commit is contained in:
Rudy Mizrahi Celekli authored and GitHub committed 2026-10-02 11:58:06 -07:00
1 parent bdfc58aeab
commit f4ea676d62
6 files changed
+57 -6

No files matched your search

+1 -1
View File
@@ -30,7 +30,7 @@ async function defaultPromptYesNo(question: string): Promise<boolean> {
// inside the daemon install root without importing the daemon package.
function isPathInsideRoot(candidate: string, root: string): boolean {
const relative = nodePath.relative(nodePath.resolve(root), nodePath.resolve(candidate));
return relative === "" || (!relative.startsWith("..") && !nodePath.isAbsolute(relative));
return relative === "" || (relative !== ".." && !relative.startsWith(`..${nodePath.sep}`) && !nodePath.isAbsolute(relative));
}
// OPR.0.4.4.11 (arch return R11-2) — pre-dispatch detection of a topology
+2 -2
View File
@@ -463,13 +463,13 @@ describe("Up CLI", () => {
// and trip getOpenRigInstallCwdError at preflight. The bare-name form
// is already rescued at the resolveLibrarySpec branch (covered by the
// earlier library-name test); this pins the path-form gap.
it("up path-form spec inside install root defaults cwdOverride to caller cwd and prints notice", async () => {
it.each(["rigs", "..cache"])("up path-form spec inside install root (%s) defaults cwdOverride to caller cwd and prints notice", async (directory) => {
const fs = await import("node:fs");
const path = await import("node:path");
const os = await import("node:os");
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), "up-bug3-install-"));
const specPath = path.join(installRoot, "rigs", "demo", "rig.yaml");
const specPath = path.join(installRoot, directory, "demo", "rig.yaml");
fs.mkdirSync(path.dirname(specPath), { recursive: true });
fs.writeFileSync(specPath, "version: '0.2'\nname: demo\npods: []\nedges: []\n");
+1 -1
View File
@@ -22,7 +22,7 @@ export function getOpenRigInstallRoot(): string {
export function isPathInsideRoot(candidatePath: string, rootPath: string): boolean {
const relative = nodePath.relative(nodePath.resolve(rootPath), nodePath.resolve(candidatePath));
return relative === "" || (!relative.startsWith("..") && !nodePath.isAbsolute(relative));
return relative === "" || (relative !== ".." && !relative.startsWith(`..${nodePath.sep}`) && !nodePath.isAbsolute(relative));
}
export function getOpenRigInstallCwdError(
@@ -1,5 +1,5 @@
import { existsSync, accessSync, constants } from "node:fs";
import { isAbsolute, join, relative } from "node:path";
import { isAbsolute, join, relative, sep } from "node:path";
import { getCompatibleOpenRigPath } from "../openrig-compat.js";
import { shellQuote as quoteShellArgument } from "../adapters/shell-quote.js";
import { reanchorBuiltinStartupFile, reanchorShippedProjectionEntry } from "./builtin-startup-files.js";
@@ -683,7 +683,7 @@ export class RestoreCheckService {
const openRigHome = getCompatibleOpenRigPath("");
const resolved = join(openRigHome, relativePath);
const relativeToHome = relative(openRigHome, resolved);
if (relativeToHome.startsWith("..") || isAbsolute(relativeToHome)) {
if (relativeToHome === ".." || relativeToHome.startsWith(`..${sep}`) || isAbsolute(relativeToHome)) {
return { error: "path traversal outside OPENRIG_HOME rejected" };
}
return { path: resolved };
@@ -0,0 +1,24 @@
import { describe, it, expect } from "vitest";
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { isPathInsideRoot, getOpenRigInstallCwdError } from "../src/domain/cwd-resolution.js";
describe("directory boundaries", () => {
it("recognizes literal dot-prefixed descendants without admitting siblings", () => {
const base = mkdtempSync(join(tmpdir(), "openrig-path-boundary-"));
const root = join(base, "install");
const inside = join(root, "..cache", "workspace");
const sibling = join(base, "install-neighbor");
mkdirSync(inside, { recursive: true });
mkdirSync(sibling);
try {
expect(isPathInsideRoot(inside, root)).toBe(true);
expect(getOpenRigInstallCwdError(inside, undefined, root)).toContain("inside the OpenRig installation");
expect(isPathInsideRoot(root, root)).toBe(true);
expect(isPathInsideRoot(sibling, root)).toBe(false);
expect(isPathInsideRoot(base, root)).toBe(false);
expect(getOpenRigInstallCwdError(inside, sibling, root)).toBeNull();
} finally { rmSync(base, { recursive: true, force: true }); }
});
});
@@ -496,6 +496,33 @@ describe("RestoreCheckService", () => {
}
});
it("accepts real OPENRIG_HOME evidence inside a literal dot-prefixed directory", () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "restore-dot-prefix-"));
const evidence = path.join(root, "..cache", "launchd.plist");
const declaration = path.join(root, "host-infra.json");
const previous = process.env["OPENRIG_HOME"];
process.env["OPENRIG_HOME"] = root;
fs.mkdirSync(path.dirname(evidence));
fs.writeFileSync(evidence, "synthetic local launch evidence");
fs.writeFileSync(declaration, v2HostInfraDeclaration({
daemonEvidencePaths: ["${OPENRIG_HOME}/..cache/launchd.plist"], supportingInfra: [],
}));
try {
const service = new RestoreCheckService(mockDeps({
listRigs: () => [], exists: fs.existsSync,
readFile: (candidate) => fs.readFileSync(candidate, "utf8"),
}));
const result = service.check({ noQueue: true, noHooks: true });
const check = result.checks.find((entry) => entry.check === "host.bootstrap-autostart.declaration");
expect(check?.status).toBe("green");
expect(check?.evidence).toContain(evidence);
} finally {
if (previous === undefined) delete process.env["OPENRIG_HOME"];
else process.env["OPENRIG_HOME"] = previous;
fs.rmSync(root, { recursive: true, force: true });
}
});
it("schemaVersion 2 missing daemon evidence path is yellow with exact path", () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "restore-check-host-infra-v2-missing-daemon-"));
const declarationPath = path.join(tmpDir, "host-infra.json");