fix(auth): make diagnostics bounded and portable

This commit is contained in:
2026-08-17 12:19:19 +02:00
parent 7cfbee36fa
commit be1724890a
23 changed files with 1088 additions and 116 deletions
+92 -23
View File
@@ -1,6 +1,6 @@
import { createHash, hkdfSync, randomBytes } from "node:crypto";
import {
chmodSync,
accessSync,
closeSync,
constants,
fchmodSync,
@@ -297,6 +297,10 @@ function isNotFound(error: unknown): boolean {
return (error as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
}
function isAlreadyExists(error: unknown): boolean {
return (error as NodeJS.ErrnoException | undefined)?.code === "EEXIST";
}
function canonicalRawValue(value: string): boolean {
if (typeof value !== "string" || !TOKEN_PATTERN.test(value)) return false;
try {
@@ -377,26 +381,12 @@ function directoryScanIdentity(path: string): DirectoryScanIdentity {
return { ...identity, mtimeMs: info.mtimeMs, ctimeMs: info.ctimeMs };
}
function privateDirectory(path: string): void {
let created = false;
try {
lstatSync(path);
} catch (error) {
if (!isNotFound(error)) throw invalid();
try {
mkdirSync(path, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
chmodSync(path, PRIVATE_DIRECTORY_MODE);
created = true;
} catch {
throw invalid();
}
}
try {
directoryIdentity(path);
} catch {
if (!created) throw invalid();
throw invalid();
}
interface SessionRootAncestor {
path: string;
descriptor: number;
dev: number;
ino: number;
uid: number;
}
function validateSessionRootSyntax(root: string): void {
@@ -404,11 +394,90 @@ function validateSessionRootSyntax(root: string): void {
|| root.includes("\0") || /\p{Cc}/u.test(root) || !isAbsolute(root) || normalize(root) !== root) throw invalid();
}
function sameAncestor(ancestor: SessionRootAncestor, info: Stats): boolean {
return info.isDirectory() && !info.isSymbolicLink() && ancestor.dev === info.dev
&& ancestor.ino === info.ino && ancestor.uid === info.uid;
}
function withSessionRootPreflight<T>(root: string, use: (exists: boolean) => T): T {
validateSessionRootSyntax(root);
const ancestors: SessionRootAncestor[] = [];
try {
const components = root.split("/").filter((component) => component.length > 0);
let current = "/";
for (let index = -1; index < components.length; index += 1) {
if (index >= 0) current = join(current, components[index]!);
let info: Stats;
try {
info = lstatSync(current) as Stats;
} catch (error) {
if (!isNotFound(error) || index !== components.length - 1) throw invalid();
accessSync(dirname(current), constants.W_OK | constants.X_OK);
for (const ancestor of ancestors) {
const observed = lstatSync(ancestor.path) as Stats;
if (!sameAncestor(ancestor, observed) || realpathSync(ancestor.path) !== ancestor.path) throw invalid();
}
const result = use(false);
for (const ancestor of ancestors) {
const observed = lstatSync(ancestor.path) as Stats;
if (!sameAncestor(ancestor, observed) || realpathSync(ancestor.path) !== ancestor.path) throw invalid();
}
return result;
}
if (!info.isDirectory() || info.isSymbolicLink() || realpathSync(current) !== current) throw invalid();
const descriptor = openSync(current, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0)
| (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
const opened = fstatSync(descriptor) as Stats;
if (!opened.isDirectory() || opened.dev !== info.dev || opened.ino !== info.ino || opened.uid !== info.uid) {
closeSync(descriptor);
throw invalid();
}
ancestors.push({ path: current, descriptor, dev: info.dev, ino: info.ino, uid: info.uid });
}
directoryIdentity(root);
const result = use(true);
for (const ancestor of ancestors) {
const observed = lstatSync(ancestor.path) as Stats;
if (!sameAncestor(ancestor, observed) || realpathSync(ancestor.path) !== ancestor.path) throw invalid();
}
return result;
} catch {
throw invalid();
} finally {
for (const ancestor of ancestors.reverse()) {
try { closeSync(ancestor.descriptor); } catch { /* preflight has already failed closed */ }
}
}
}
function privateDirectory(path: string): void {
withSessionRootPreflight(path, (exists) => {
if (exists) return;
try {
mkdirSync(path, { recursive: false, mode: PRIVATE_DIRECTORY_MODE });
} catch (error) {
if (!isAlreadyExists(error)) throw invalid();
directoryIdentity(path);
return;
}
const descriptor = openSync(path, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0)
| (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
try {
fchmodSync(descriptor, PRIVATE_DIRECTORY_MODE);
const opened = fstatSync(descriptor) as Stats;
const current = directoryIdentity(path);
if (opened.dev !== current.dev || opened.ino !== current.ino || opened.uid !== current.uid
|| (opened.mode & 0o7777) !== current.mode) throw invalid();
} finally {
try { closeSync(descriptor); } catch { /* creation already fails closed */ }
}
});
}
/** Side-effect-free POSIX validator shared by runtime storage and static diagnostics. */
export function validateAuthSessionRoot(root: string): void {
try {
validateSessionRootSyntax(root);
directoryIdentity(root);
withSessionRootPreflight(root, () => undefined);
} catch {
throw invalid();
}