fix(router): accept Windows verbatim workspace paths (#1460)

This commit is contained in:
Pascal André
2026-04-16 19:17:32 +02:00
committed by GitHub
parent 98f9340cff
commit f516efcb1d
3 changed files with 99 additions and 15 deletions

View File

@@ -15,6 +15,7 @@ import { startHealthServer, type HealthSnapshot } from "./health.js";
import { type InboundMessagePart, type MessageDeliveryResult, type OutboundMessagePart, normalizeOutboundParts, summarizeInboundPartsForPrompt, summarizeInboundPartsForReporter, textFromInboundParts } from "./media.js";
import { MediaStore } from "./media-store.js";
import { buildPermissionRules, createClient } from "./opencode.js";
import { isWithinWorkspaceRootPath, normalizeScopedDirectoryPath } from "./path-scope.js";
import { chunkText, formatInputSummary, truncateText } from "./text.js";
import { createSlackAdapter } from "./slack.js";
import { createTelegramAdapter, isTelegramPeerId } from "./telegram.js";
@@ -442,25 +443,17 @@ export async function startBridge(config: Config, logger: Logger, reporter?: Bri
const formatPeer = (_channel: ChannelName, peerId: string) => peerId;
const normalizeDirectory = (input: string) => {
const trimmed = input.trim();
if (!trimmed) return "";
const unified = trimmed.replace(/\\/g, "/");
const withoutTrailing = unified.replace(/\/+$/, "");
const normalized = withoutTrailing || "/";
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
};
const normalizeDirectory = (input: string) =>
normalizeScopedDirectoryPath(input, process.platform);
const workspaceRootNormalized = normalizeDirectory(workspaceRoot);
const isWithinWorkspaceRoot = (candidate: string) => {
const resolved = resolve(candidate || workspaceRoot);
const relativePath = relative(workspaceRoot, resolved);
if (!relativePath) return true;
if (relativePath === ".") return true;
if (relativePath.startsWith("..") || isAbsolute(relativePath)) return false;
const boundary = workspaceRoot.endsWith(sep) ? workspaceRoot : `${workspaceRoot}${sep}`;
return resolved === workspaceRoot || resolved.startsWith(boundary);
return isWithinWorkspaceRootPath({
workspaceRoot,
candidate,
platform: process.platform,
});
};
const resolveScopedDirectory = (input: string): { ok: true; directory: string } | { ok: false; error: string } => {

View File

@@ -0,0 +1,42 @@
import { isAbsolute, relative, resolve } from "node:path";
export function normalizeScopedDirectoryPath(input: string, platform = process.platform) {
const trimmed = input.trim();
if (!trimmed) return "";
const withoutVerbatim = /^\\\\\?\\UNC[\\/]/i.test(trimmed)
? `\\${trimmed.slice(8)}`
: /^\\\\\?\\[a-zA-Z]:[\\/]/.test(trimmed)
? trimmed.slice(4)
: trimmed;
const unified = withoutVerbatim.replace(/\\/g, "/");
const withoutTrailing = unified.replace(/\/+$/, "");
const normalized = withoutTrailing || "/";
return platform === "win32" ? normalized.toLowerCase() : normalized;
}
export function isWithinWorkspaceRootPath(input: {
workspaceRoot: string;
candidate: string;
platform?: NodeJS.Platform;
}) {
const platform = input.platform ?? process.platform;
const rootForComparison =
platform === "win32"
? normalizeScopedDirectoryPath(input.workspaceRoot, platform)
: input.workspaceRoot;
const resolved = resolve(input.candidate || input.workspaceRoot);
const resolvedForComparison =
platform === "win32"
? normalizeScopedDirectoryPath(resolved, platform)
: resolved;
const relativePath = relative(rootForComparison, resolvedForComparison);
if (!relativePath || relativePath === ".") return true;
if (relativePath.startsWith("..") || isAbsolute(relativePath)) return false;
const boundary = rootForComparison.endsWith("/")
? rootForComparison
: `${rootForComparison}/`;
return (
resolvedForComparison === rootForComparison ||
resolvedForComparison.startsWith(boundary)
);
}

View File

@@ -0,0 +1,49 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
isWithinWorkspaceRootPath,
normalizeScopedDirectoryPath,
} from "../dist/path-scope.js";
test("normalizeScopedDirectoryPath strips Windows verbatim prefixes", () => {
const workspaceRoot = String.raw`G:\project\openwork_project`;
const candidate = String.raw`\\?\G:\project\openwork_project`;
assert.equal(
normalizeScopedDirectoryPath(workspaceRoot, "win32"),
"g:/project/openwork_project",
);
assert.equal(
normalizeScopedDirectoryPath(candidate, "win32"),
"g:/project/openwork_project",
);
});
test("isWithinWorkspaceRootPath accepts Windows verbatim aliases for workspace root", () => {
const workspaceRoot = String.raw`G:\project\openwork_project`;
const candidate = String.raw`\\?\G:\project\openwork_project`;
assert.equal(
isWithinWorkspaceRootPath({
workspaceRoot,
candidate,
platform: "win32",
}),
true,
);
});
test("isWithinWorkspaceRootPath still rejects directories outside the workspace root", () => {
const workspaceRoot = String.raw`G:\project\openwork_project`;
const candidate = String.raw`\\?\G:\project\outside`;
assert.equal(
isWithinWorkspaceRootPath({
workspaceRoot,
candidate,
platform: "win32",
}),
false,
);
});