import { realpathSync } from "node:fs";
import { isAbsolute, normalize, resolve, sep } from "node:path";
import { homedir } from "node:os";
//#region src/agent/sandbox/path-policy.ts
/**
* Path safety validation for sandbox isolation.
*
* Prevents path traversal to sensitive directories and detects symlink escape attempts.
* Aligned with OpenClaw's validate-sandbox-security.ts blocked-path approach.
*/
const BLOCKED_ABSOLUTE_PATHS = [
	"/etc",
	"/proc",
	"/sys",
	"/dev",
	"/boot",
	"/root",
	"/run",
	"/var/run",
	"/private/etc",
	"/private/var/run",
	"/var/run/docker.sock",
	"/private/var/run/docker.sock",
	"/run/docker.sock"
];
const BLOCKED_HOME_SUBPATHS = [
	".ssh",
	".aws",
	".gnupg",
	".docker",
	".config",
	".netrc",
	".npmrc",
	".cargo/credentials",
	".kube"
];
function normalizePosixPath(raw) {
	return normalize(raw).replace(/\\/g, "/");
}
function getBlockedPaths() {
	const blocked = new Set(BLOCKED_ABSOLUTE_PATHS.map(normalizePosixPath));
	const home = homedir();
	if (home && home !== "/") for (const sub of BLOCKED_HOME_SUBPATHS) blocked.add(normalizePosixPath(resolve(home, sub)));
	return [...blocked];
}
/**
* Check if `target` equals or is a descendant of `root`.
*/
function isPathInsideOrEqual(root, target) {
	const normalizedRoot = normalizePosixPath(root);
	const normalizedTarget = normalizePosixPath(target);
	if (normalizedTarget === normalizedRoot) return true;
	const prefix = normalizedRoot.endsWith("/") ? normalizedRoot : `${normalizedRoot}/`;
	return normalizedTarget.startsWith(prefix);
}
/**
* Resolve symlinks by walking from the existing ancestor downward.
* If the full path does not exist, resolve the deepest existing ancestor
* and append the remaining segments — mirrors OpenClaw's approach.
*/
function resolveCanonicalPath(targetPath) {
	try {
		return realpathSync(targetPath);
	} catch {
		const segments = targetPath.split(sep);
		let resolved = segments[0] === "" ? "/" : segments[0];
		let unresolved = "";
		for (let i = 1; i < segments.length; i++) {
			const candidate = resolve(resolved, segments[i]);
			try {
				resolved = realpathSync(candidate);
			} catch {
				unresolved = segments.slice(i).join("/");
				break;
			}
		}
		return unresolved ? `${resolved}/${unresolved}` : resolved;
	}
}
/**
* Validate that a path does not escape into blocked system/credential directories.
*
* The check runs in two passes:
*  1. Lexical: normalize the path and check against blocked prefixes.
*  2. Canonical: resolve symlinks (or deepest existing ancestor) and re-check.
*
* When `allowedRoots` are provided, the path must additionally be inside at
* least one of them (after canonical resolution).
*/
function validatePath(rawPath, options) {
	if (!rawPath || !rawPath.trim()) return {
		allowed: false,
		reason: "Empty path"
	};
	const target = normalizePosixPath(resolve(rawPath));
	if (target === "/" || target === "\\") return {
		allowed: false,
		reason: "Cannot operate on filesystem root"
	};
	const blockedPaths = [...getBlockedPaths(), ...options?.extraBlockedPaths?.map(normalizePosixPath) ?? []];
	for (const blocked of blockedPaths) {
		if (isPathInsideOrEqual(blocked, target)) return {
			allowed: false,
			reason: `Path targets blocked directory: ${blocked}`
		};
		if (isPathInsideOrEqual(target, blocked) && target !== blocked) {
			if (target === "/" || target === "/private") return {
				allowed: false,
				reason: `Path covers blocked directory: ${blocked}`
			};
		}
	}
	const canonicalNormalized = normalizePosixPath(resolveCanonicalPath(target));
	if (canonicalNormalized !== target) {
		for (const blocked of blockedPaths) if (isPathInsideOrEqual(blocked, canonicalNormalized)) return {
			allowed: false,
			reason: `Path resolves (via symlink) to blocked directory: ${blocked}`
		};
	}
	if (options?.allowedRoots && options.allowedRoots.length > 0) {
		const normalizedRoots = options.allowedRoots.map((r) => normalizePosixPath(resolveCanonicalPath(resolve(r))));
		if (!normalizedRoots.some((root) => isPathInsideOrEqual(root, canonicalNormalized))) return {
			allowed: false,
			reason: `Path is outside allowed roots: ${normalizedRoots.join(", ")}`
		};
	}
	return {
		allowed: true,
		canonicalPath: canonicalNormalized
	};
}
/**
* Validate a path specifically for file write/edit operations.
* Adds protection for config files and profile system files.
*/
function validateWritePath(rawPath, workspaceRoot, options) {
	const resolvedPath = isAbsolute(rawPath) ? rawPath : resolve(workspaceRoot, rawPath);
	const configProtectedPatterns = [
		/\/\.xopc\/xopc\.json$/,
		/\/\.env$/,
		/\/\.env\.local$/,
		/\/\.env\.production$/
	];
	const normalized = normalizePosixPath(resolvedPath);
	for (const pattern of configProtectedPatterns) if (pattern.test(normalized)) return {
		allowed: false,
		reason: `Cannot write to protected config file: ${normalized}`
	};
	return validatePath(resolvedPath, options);
}
//#endregion
export { validatePath, validateWritePath };

//# sourceMappingURL=path-policy.js.map