blob: 24d1babd3f019fb2f4b17cf521cd473fe2484212 [file]
import { chmod, lstat, mkdtemp, readdir, rm, rmdir, unlink } from 'node:fs/promises';
import { join } from 'node:path';
const POSIX_ENDPOINT_ROOT = '/tmp';
const PORTABLE_UNIX_SOCKET_PATH_LIMIT = 100;
export interface RuntimeHostEndpointInput {
rootId: string;
hostEpoch: string;
}
export interface RuntimeHostEndpoint {
path: string;
prepareAfterListen(): Promise<void>;
cleanup(): Promise<void>;
}
export class RuntimeHostEndpointError extends Error {
constructor(
readonly code: 'insecure_endpoint_directory' | 'endpoint_path_too_long',
message: string,
) {
super(message);
this.name = 'RuntimeHostEndpointError';
}
}
export async function prepareRuntimeHostEndpoint(
input: RuntimeHostEndpointInput,
): Promise<RuntimeHostEndpoint> {
if (process.platform === 'win32') {
const path = `\\\\.\\pipe\\maka-runtime-host-${input.rootId.slice(0, 16)}-${input.hostEpoch}`;
return {
path,
async prepareAfterListen() {},
async cleanup() {},
};
}
const prefix = endpointDirectoryPrefix(input.rootId);
await removeStaleEndpointDirectories(prefix);
const directory = await mkdtemp(join(POSIX_ENDPOINT_ROOT, prefix));
try {
await ensurePrivateEndpointDirectory(directory);
const path = join(directory, 'h.sock');
if (Buffer.byteLength(path, 'utf8') > PORTABLE_UNIX_SOCKET_PATH_LIMIT) {
throw new RuntimeHostEndpointError(
'endpoint_path_too_long',
`Runtime Host endpoint path exceeds the portable Unix socket limit: ${path}`,
);
}
return {
path,
async prepareAfterListen() {
await chmod(path, 0o600);
const endpointStat = await lstat(path);
if (
!endpointStat.isSocket() ||
endpointStat.uid !== currentUid() ||
(endpointStat.mode & 0o077) !== 0
) {
throw new RuntimeHostEndpointError(
'insecure_endpoint_directory',
`Runtime Host endpoint is not a private current-user socket: ${path}`,
);
}
},
async cleanup() {
await unlink(path).catch((error: unknown) => {
if (!isNodeError(error, 'ENOENT')) throw error;
});
await rmdir(directory).catch((error: unknown) => {
if (!isNodeError(error, 'ENOENT')) throw error;
});
},
};
} catch (error) {
await rm(directory, { recursive: true, force: true }).catch(() => undefined);
throw error;
}
}
function endpointDirectoryPrefix(rootId: string): string {
if (!/^[a-f0-9]{64}$/.test(rootId)) {
throw new RuntimeHostEndpointError(
'insecure_endpoint_directory',
'Runtime Host endpoint requires a valid storage root identity',
);
}
const rootTag = Buffer.from(rootId, 'hex').toString('base64url');
return `m-${currentUid()}-${rootTag}-`;
}
async function removeStaleEndpointDirectories(prefix: string): Promise<void> {
const entries = await readdir(POSIX_ENDPOINT_ROOT, { withFileTypes: true });
await Promise.all(
entries.map(async (entry) => {
if (
!entry.isDirectory() ||
!entry.name.startsWith(prefix) ||
entry.name.length !== prefix.length + 6
)
return;
const path = join(POSIX_ENDPOINT_ROOT, entry.name);
const directoryStat = await lstat(path).catch(() => undefined);
if (!directoryStat?.isDirectory() || directoryStat.uid !== currentUid()) return;
await rm(path, { recursive: true, force: true });
}),
);
}
async function ensurePrivateEndpointDirectory(path: string): Promise<void> {
await chmod(path, 0o700);
const directoryStat = await lstat(path);
if (
!directoryStat.isDirectory() ||
directoryStat.uid !== currentUid() ||
(directoryStat.mode & 0o077) !== 0
) {
throw new RuntimeHostEndpointError(
'insecure_endpoint_directory',
`Runtime Host endpoint parent is not a private current-user directory: ${path}`,
);
}
}
function currentUid(): number {
if (typeof process.getuid !== 'function') {
throw new RuntimeHostEndpointError(
'insecure_endpoint_directory',
'Runtime Host POSIX endpoints require a current-user identity',
);
}
return process.getuid();
}
function isNodeError(error: unknown, code: string): boolean {
return (
error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === code
);
}