mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
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:
1 parent
bdfc58aeab
commit
f4ea676d62
6 files changed
+57
-6
No files matched your search
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user