fix: enforce active organization boundary

This commit is contained in:
2026-07-10 18:42:12 +08:00
parent f07f280b8f
commit d730e51c3d
24 changed files with 1686 additions and 460 deletions
+125 -3
View File
@@ -1,5 +1,5 @@
import { constants } from "node:fs";
import { lstat, mkdir, open, realpath, unlink, type FileHandle } from "node:fs/promises";
import { link, lstat, mkdir, open, realpath, unlink, type FileHandle } from "node:fs/promises";
import { isAbsolute, join, relative, resolve, sep } from "node:path";
import type { Readable } from "node:stream";
@@ -21,6 +21,12 @@ export interface WorkspaceFileSnapshot {
readonly data: Buffer;
}
export interface WorkspaceFileWriteResult {
readonly path: string;
readonly device: number;
readonly inode: number;
}
interface OpenDirectory {
readonly handle: FileHandle;
readonly displayPath: string;
@@ -72,6 +78,21 @@ export async function writeNewWorkspaceFileNoFollow(
requestedPath: string,
source: Readable,
): Promise<string> {
return (await writeNewWorkspaceFileNoFollowTracked(
workspaceRoot,
workspaceDir,
requestedPath,
source,
)).path;
}
/** Create one inbound file and return its identity for race-safe compensation. */
export async function writeNewWorkspaceFileNoFollowTracked(
workspaceRoot: string,
workspaceDir: string,
requestedPath: string,
source: Readable,
): Promise<WorkspaceFileWriteResult> {
const workspace = await canonicalWorkspace(workspaceRoot, workspaceDir);
const components = fileComponents(workspace, requestedPath);
const name = components.at(-1)!;
@@ -81,7 +102,7 @@ export async function writeNewWorkspaceFileNoFollow(
let file: FileHandle | undefined;
let device: number | undefined;
let inode: number | undefined;
let result: string | undefined;
let result: WorkspaceFileWriteResult | undefined;
let failure: unknown;
const cleanupFailures: unknown[] = [];
try {
@@ -104,7 +125,11 @@ export async function writeNewWorkspaceFileNoFollow(
}
await file.sync();
await assertNameStillReferences(parent, name, metadata.dev, metadata.ino, requestedPath);
result = join(workspace, ...components);
result = {
path: join(workspace, ...components),
device: metadata.dev,
inode: metadata.ino,
};
} catch (error) {
failure = boundaryError(error, requestedPath, "cannot create inbound workspace file safely");
if (device !== undefined && inode !== undefined) {
@@ -124,6 +149,103 @@ export async function writeNewWorkspaceFileNoFollow(
return result!;
}
/** Remove only the exact file created by a tracked no-follow write. */
export async function removeWorkspaceFileIfUnchangedNoFollow(
workspaceRoot: string,
workspaceDir: string,
requestedPath: string,
expectedDevice: number,
expectedInode: number,
): Promise<void> {
const workspace = await canonicalWorkspace(workspaceRoot, workspaceDir);
const components = fileComponents(workspace, requestedPath);
const name = components.at(-1)!;
const parent = await openDirectoryTree(workspace, components.slice(0, -1), false, requestedPath);
let failure: unknown;
try {
await unlinkIfSameFile(parent, name, expectedDevice, expectedInode);
} catch (error) {
failure = boundaryError(error, requestedPath, "cannot compensate inbound workspace file safely");
}
await finishWithCleanup(
failure,
[parent.handle],
`inbound workspace compensation cleanup failed: ${requestedPath}`,
);
}
/**
* Publish a protected same-filesystem staging file with one exclusive hard-link
* operation. The target path is traversed without following workspace symlinks.
*/
export async function linkWorkspaceFileNoFollow(
workspaceRoot: string,
workspaceDir: string,
requestedPath: string,
stagedPath: string,
): Promise<WorkspaceFileWriteResult> {
const workspace = await canonicalWorkspace(workspaceRoot, workspaceDir);
const components = fileComponents(workspace, requestedPath);
const name = components.at(-1)!;
const parent = await openDirectoryTree(workspace, components.slice(0, -1), true, requestedPath);
let source: FileHandle | undefined;
let result: WorkspaceFileWriteResult | undefined;
let failure: unknown;
const cleanupFailures: unknown[] = [];
try {
source = await open(stagedPath, constants.O_RDONLY | constants.O_NOFOLLOW);
const metadata = await source.stat();
if (!metadata.isFile()) {
throw new WorkspaceFileBoundaryError(`staged resource is not a regular file: ${stagedPath}`, stagedPath);
}
const targetPath = childPath(parent, name);
await link(stagedPath, targetPath);
try {
await assertNameStillReferences(parent, name, metadata.dev, metadata.ino, requestedPath);
} catch (error) {
await unlinkIfSameFile(parent, name, metadata.dev, metadata.ino).catch((cleanupError) => {
cleanupFailures.push(cleanupError);
});
throw error;
}
result = {
path: join(workspace, ...components),
device: metadata.dev,
inode: metadata.ino,
};
} catch (error) {
failure = boundaryError(error, requestedPath, "cannot publish staged workspace file safely");
}
try {
await finishWithCleanup(
failure,
[source, parent.handle],
`staged workspace publication cleanup failed: ${requestedPath}`,
cleanupFailures,
);
} catch (error) {
if (result !== undefined) {
try {
await removeWorkspaceFileIfUnchangedNoFollow(
workspaceRoot,
workspaceDir,
requestedPath,
result.device,
result.inode,
);
} catch (compensationError) {
throw new AggregateError(
[error, compensationError],
`staged workspace publication and compensation both failed: ${requestedPath}`,
{ cause: error },
);
}
}
throw error;
}
return result!;
}
async function canonicalWorkspace(workspaceRoot: string, workspaceDir: string): Promise<string> {
if (process.platform !== "linux") {
throw new WorkspaceFileBoundaryError(