642 lines
20 KiB
TypeScript
642 lines
20 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import {
|
|
closeSync,
|
|
fstatSync,
|
|
fsyncSync,
|
|
lstatSync,
|
|
readlinkSync,
|
|
readSync,
|
|
type Stats,
|
|
writeSync,
|
|
createReadStream,
|
|
} from "node:fs";
|
|
import { link, lstat, unlink } from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
import {
|
|
decodeProviderGuardianCommit,
|
|
decodeProviderGuardianGuard,
|
|
decodeProviderGuardianPublish,
|
|
encodeProviderGuardianPublished,
|
|
encodeProviderGuardianReady,
|
|
MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES,
|
|
MAX_PROVIDER_GUARDIAN_LEASE_MS,
|
|
providerGuardianRawStagingLeaf,
|
|
providerGuardianSealedTempLeaf,
|
|
type ProviderGuardianGuard,
|
|
type ProviderGuardianKind,
|
|
} from "./provider-guardian-protocol.ts";
|
|
import { cleanupOwnedProviderReport } from "./provider-raw-cleanup.ts";
|
|
|
|
type OwnedIdentity = Readonly<{ dev: number; ino: number }>;
|
|
type BootstrapAuthority = Readonly<{
|
|
kind: ProviderGuardianKind;
|
|
noncePrefix: string;
|
|
rawStagingLeaf: string;
|
|
rawStagingPath: string;
|
|
rawPath: string;
|
|
rawIdentity: OwnedIdentity;
|
|
sealedTempLeaf: string;
|
|
sealedTempPath: string;
|
|
sealedPath: string;
|
|
sealedIdentity: OwnedIdentity;
|
|
}>;
|
|
type BoundPrivateAuthority = Readonly<{
|
|
identity: OwnedIdentity;
|
|
leaf: string;
|
|
noncePrefix: string;
|
|
path: string;
|
|
stem: string;
|
|
}>;
|
|
type GuardianTransaction = Readonly<{ guard: ProviderGuardianGuard }>;
|
|
|
|
const RAW_DIRECTORY_FD = 3;
|
|
const EVIDENCE_DIRECTORY_FD = 4;
|
|
const RAW_STAGING_FD = 5;
|
|
const SEALED_TEMP_FD = 6;
|
|
const RAW_DIRECTORY_PATH = `/proc/self/fd/${RAW_DIRECTORY_FD}`;
|
|
const EVIDENCE_DIRECTORY_PATH = `/proc/self/fd/${EVIDENCE_DIRECTORY_FD}`;
|
|
const RAW_STAGING_FD_PATH = `/proc/self/fd/${RAW_STAGING_FD}`;
|
|
const SEALED_TEMP_FD_PATH = `/proc/self/fd/${SEALED_TEMP_FD}`;
|
|
const RAW_STAGING_PATTERN =
|
|
/^\.(vulnerability-report|provenance-attestation)\.json\.guardian-([0-9a-f]{32})\.raw\.tmp$/u;
|
|
const SEALED_TEMP_PATTERN =
|
|
/^\.(vulnerability-report|provenance-attestation)\.json\.guardian-([0-9a-f]{32})\.tmp$/u;
|
|
|
|
let privateFdsClosed = false;
|
|
const bootstrap = await initializeBootstrap();
|
|
let pending = Buffer.alloc(0);
|
|
let expectedBytes: number | undefined;
|
|
let state: "starting" | "guarding" | "published" | "commitPending" = "starting";
|
|
let transaction: GuardianTransaction | undefined;
|
|
let terminal = false;
|
|
let deadline: NodeJS.Timeout | undefined;
|
|
let operations = Promise.resolve();
|
|
const liveness = createReadStream("", { fd: 0, autoClose: false });
|
|
|
|
liveness.on("data", consumeChunk);
|
|
liveness.once("end", () => {
|
|
enqueue(async () => {
|
|
if (state === "commitPending" && pending.byteLength === 0 && expectedBytes === undefined) {
|
|
await succeedOnCommittedEof();
|
|
return;
|
|
}
|
|
if (state === "starting" && pending.byteLength > 0) {
|
|
await failClosed(126, "provider guardian frame is truncated");
|
|
return;
|
|
}
|
|
await failClosed(125, "provider guardian liveness EOF");
|
|
});
|
|
});
|
|
liveness.once("error", (error) => {
|
|
enqueue(async () => failClosed(125, `provider guardian liveness error: ${error.message}`));
|
|
});
|
|
|
|
async function initializeBootstrap(): Promise<BootstrapAuthority> {
|
|
const rawDirectory = path.resolve(process.cwd(), "provider-evidence/untrusted");
|
|
const evidenceDirectory = path.resolve(process.cwd(), "provider-evidence");
|
|
const failures: Error[] = [];
|
|
const rawDirectoryValid = captureInheritedDirectory(
|
|
RAW_DIRECTORY_FD,
|
|
rawDirectory,
|
|
"raw",
|
|
failures,
|
|
);
|
|
const evidenceDirectoryValid = captureInheritedDirectory(
|
|
EVIDENCE_DIRECTORY_FD,
|
|
evidenceDirectory,
|
|
"evidence",
|
|
failures,
|
|
);
|
|
|
|
const rawDescriptor = capturePrivateDescriptor(RAW_STAGING_FD, "raw staging", failures);
|
|
const sealedDescriptor = capturePrivateDescriptor(SEALED_TEMP_FD, "sealed temp", failures);
|
|
const rawAuthority = rawDescriptor && rawDirectoryValid
|
|
? capturePrivateAlias({
|
|
descriptorMetadata: rawDescriptor,
|
|
descriptorTarget: RAW_STAGING_FD_PATH,
|
|
expectedDirectory: rawDirectory,
|
|
descriptorDirectory: RAW_DIRECTORY_PATH,
|
|
grammar: RAW_STAGING_PATTERN,
|
|
label: "raw staging",
|
|
}, failures)
|
|
: undefined;
|
|
const sealedAuthority = sealedDescriptor && evidenceDirectoryValid
|
|
? capturePrivateAlias({
|
|
descriptorMetadata: sealedDescriptor,
|
|
descriptorTarget: SEALED_TEMP_FD_PATH,
|
|
expectedDirectory: evidenceDirectory,
|
|
descriptorDirectory: EVIDENCE_DIRECTORY_PATH,
|
|
grammar: SEALED_TEMP_PATTERN,
|
|
label: "sealed temp",
|
|
}, failures)
|
|
: undefined;
|
|
|
|
if (!rawAuthority || !sealedAuthority) {
|
|
return await failBootstrap(rawAuthority, sealedAuthority, failures);
|
|
}
|
|
try {
|
|
if (
|
|
rawAuthority.stem !== sealedAuthority.stem ||
|
|
rawAuthority.noncePrefix !== sealedAuthority.noncePrefix
|
|
) {
|
|
throw new TypeError("provider guardian inherited private aliases disagree");
|
|
}
|
|
const kind = providerKindFromStem(rawAuthority.stem);
|
|
const rawLeaf = kind === "vulnerability"
|
|
? "vulnerability-report.json"
|
|
: "provenance-attestation.json";
|
|
return Object.freeze({
|
|
kind,
|
|
noncePrefix: rawAuthority.noncePrefix,
|
|
rawStagingLeaf: rawAuthority.leaf,
|
|
rawStagingPath: rawAuthority.path,
|
|
rawPath: `${RAW_DIRECTORY_PATH}/${rawLeaf}`,
|
|
rawIdentity: rawAuthority.identity,
|
|
sealedTempLeaf: sealedAuthority.leaf,
|
|
sealedTempPath: sealedAuthority.path,
|
|
sealedPath: `${EVIDENCE_DIRECTORY_PATH}/${rawLeaf}`,
|
|
sealedIdentity: sealedAuthority.identity,
|
|
});
|
|
} catch (error) {
|
|
failures.push(toError(error));
|
|
return await failBootstrap(rawAuthority, sealedAuthority, failures);
|
|
}
|
|
}
|
|
|
|
function captureInheritedDirectory(
|
|
fd: number,
|
|
canonicalPath: string,
|
|
label: string,
|
|
failures: Error[],
|
|
): boolean {
|
|
try {
|
|
assertInheritedDirectory(fd, canonicalPath, label);
|
|
return true;
|
|
} catch (error) {
|
|
failures.push(toError(error));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function capturePrivateDescriptor(
|
|
fd: number,
|
|
label: string,
|
|
failures: Error[],
|
|
): Stats | undefined {
|
|
try {
|
|
return fstatSync(fd);
|
|
} catch (error) {
|
|
failures.push(new Error(`provider guardian inherited ${label} fd is invalid`, {
|
|
cause: error,
|
|
}));
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function capturePrivateAlias(
|
|
input: Readonly<{
|
|
descriptorMetadata: Stats;
|
|
descriptorTarget: string;
|
|
expectedDirectory: string;
|
|
descriptorDirectory: string;
|
|
grammar: RegExp;
|
|
label: string;
|
|
}>,
|
|
failures: Error[],
|
|
): BoundPrivateAuthority | undefined {
|
|
try {
|
|
return bindPrivateAlias(input);
|
|
} catch (error) {
|
|
failures.push(toError(error));
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function bindPrivateAlias(input: Readonly<{
|
|
descriptorMetadata: Stats;
|
|
descriptorTarget: string;
|
|
expectedDirectory: string;
|
|
descriptorDirectory: string;
|
|
grammar: RegExp;
|
|
label: string;
|
|
}>): BoundPrivateAuthority {
|
|
const descriptorTarget = readlinkSync(input.descriptorTarget);
|
|
if (path.dirname(descriptorTarget) !== input.expectedDirectory) {
|
|
throw new TypeError(
|
|
`provider guardian inherited ${input.label} alias is outside its directory`,
|
|
);
|
|
}
|
|
const leaf = path.basename(descriptorTarget);
|
|
const match = input.grammar.exec(leaf);
|
|
if (!match) {
|
|
throw new TypeError(`provider guardian inherited ${input.label} alias is invalid`);
|
|
}
|
|
const boundPath = `${input.descriptorDirectory}/${leaf}`;
|
|
const pathnameMetadata = lstatSync(boundPath);
|
|
assertPrivateMetadata(input.descriptorMetadata, pathnameMetadata, input.label);
|
|
return Object.freeze({
|
|
identity: Object.freeze({
|
|
dev: input.descriptorMetadata.dev,
|
|
ino: input.descriptorMetadata.ino,
|
|
}),
|
|
leaf,
|
|
noncePrefix: match[2]!,
|
|
path: boundPath,
|
|
stem: match[1]!,
|
|
});
|
|
}
|
|
|
|
async function failBootstrap(
|
|
rawAuthority: BoundPrivateAuthority | undefined,
|
|
sealedAuthority: BoundPrivateAuthority | undefined,
|
|
failures: Error[],
|
|
): Promise<never> {
|
|
for (const authority of [rawAuthority, sealedAuthority]) {
|
|
if (!authority) continue;
|
|
await cleanupOwnedPath(authority.path, authority.identity, failures);
|
|
}
|
|
closePrivateFds(failures);
|
|
writeAggregateDiagnostic("provider guardian bootstrap failed", failures);
|
|
process.exit(126);
|
|
}
|
|
|
|
function assertPrivateMetadata(
|
|
descriptorMetadata: Stats,
|
|
pathnameMetadata: Stats,
|
|
label: string,
|
|
): void {
|
|
if (
|
|
!descriptorMetadata.isFile() || !pathnameMetadata.isFile() ||
|
|
pathnameMetadata.isSymbolicLink() || descriptorMetadata.dev !== pathnameMetadata.dev ||
|
|
descriptorMetadata.ino !== pathnameMetadata.ino || descriptorMetadata.nlink !== 1 ||
|
|
pathnameMetadata.nlink !== 1 || (descriptorMetadata.mode & 0o777) !== 0o600 ||
|
|
(pathnameMetadata.mode & 0o777) !== 0o600 || descriptorMetadata.size !== 0 ||
|
|
pathnameMetadata.size !== 0
|
|
) {
|
|
throw new TypeError(`provider guardian inherited ${label} identity is invalid`);
|
|
}
|
|
}
|
|
|
|
function providerKindFromStem(stem: string): ProviderGuardianKind {
|
|
if (stem === "vulnerability-report") return "vulnerability";
|
|
if (stem === "provenance-attestation") return "provenance";
|
|
throw new TypeError("provider guardian inherited private kind is invalid");
|
|
}
|
|
|
|
function consumeChunk(chunk: Buffer | string): void {
|
|
if (terminal) return;
|
|
pending = Buffer.concat([pending, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
if (expectedBytes === undefined && pending.byteLength >= 4) {
|
|
expectedBytes = pending.readUInt32BE(0);
|
|
if (expectedBytes <= 0 || expectedBytes > MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES) {
|
|
enqueue(async () => failClosed(126, "provider guardian frame length is invalid"));
|
|
return;
|
|
}
|
|
}
|
|
if (expectedBytes !== undefined && pending.byteLength === expectedBytes + 4) {
|
|
const payload = pending.subarray(4);
|
|
pending = Buffer.alloc(0);
|
|
expectedBytes = undefined;
|
|
enqueue(async () => handleFrame(payload));
|
|
} else if (expectedBytes !== undefined && pending.byteLength > expectedBytes + 4) {
|
|
enqueue(async () => failClosed(126, "provider guardian frame has trailing bytes"));
|
|
}
|
|
}
|
|
|
|
function enqueue(operation: () => Promise<void>): void {
|
|
operations = operations.then(operation).catch(async (error) => {
|
|
await failClosed(126, error instanceof Error ? error.message : String(error));
|
|
});
|
|
}
|
|
|
|
async function handleFrame(payload: Buffer): Promise<void> {
|
|
if (state === "starting") {
|
|
await establishTransaction(payload);
|
|
} else if (state === "guarding") {
|
|
await publishSealedArtifact(payload);
|
|
} else if (state === "published") {
|
|
await prepareCommit(payload);
|
|
} else {
|
|
await failClosed(126, "provider guardian received data after commit");
|
|
}
|
|
}
|
|
|
|
async function establishTransaction(payload: Buffer): Promise<void> {
|
|
const nowEpochMs = Date.now();
|
|
const guard = decodeProviderGuardianGuard(payload, {
|
|
nowEpochMs,
|
|
maxLeaseMs: MAX_PROVIDER_GUARDIAN_LEASE_MS,
|
|
});
|
|
if (
|
|
guard.kind !== bootstrap.kind ||
|
|
guard.nonce.subarray(0, 16).toString("hex") !== bootstrap.noncePrefix ||
|
|
providerGuardianRawStagingLeaf(guard.kind, guard.nonce) !== bootstrap.rawStagingLeaf ||
|
|
providerGuardianSealedTempLeaf(guard.kind, guard.nonce) !== bootstrap.sealedTempLeaf
|
|
) {
|
|
throw new TypeError("provider guardian guard does not match inherited private aliases");
|
|
}
|
|
assertBoundPrivateLeaf(
|
|
RAW_STAGING_FD,
|
|
bootstrap.rawStagingPath,
|
|
bootstrap.rawIdentity,
|
|
"raw staging",
|
|
);
|
|
assertBoundPrivateLeaf(
|
|
SEALED_TEMP_FD,
|
|
bootstrap.sealedTempPath,
|
|
bootstrap.sealedIdentity,
|
|
"sealed temp",
|
|
);
|
|
transaction = Object.freeze({ guard });
|
|
|
|
await link(bootstrap.rawStagingPath, bootstrap.rawPath);
|
|
assertOwnedPathMetadata(bootstrap.rawStagingPath, bootstrap.rawIdentity, 2, 0o600, 0,
|
|
"raw staging link");
|
|
assertOwnedPathMetadata(bootstrap.rawPath, bootstrap.rawIdentity, 2, 0o600, 0,
|
|
"canonical raw link");
|
|
await unlink(bootstrap.rawStagingPath);
|
|
fsyncSync(RAW_DIRECTORY_FD);
|
|
assertOwnedPathMetadata(bootstrap.rawPath, bootstrap.rawIdentity, 1, 0o600, 0,
|
|
"canonical raw");
|
|
assertBoundPrivateLeaf(
|
|
SEALED_TEMP_FD,
|
|
bootstrap.sealedTempPath,
|
|
bootstrap.sealedIdentity,
|
|
"sealed temp",
|
|
);
|
|
|
|
const remainingLeaseMs = guard.deadlineEpochMs - Date.now();
|
|
if (remainingLeaseMs <= 0) {
|
|
throw new TypeError("provider guardian guard deadline expired during startup");
|
|
}
|
|
deadline = setTimeout(() => {
|
|
enqueue(async () => failClosed(124, "provider guardian lease deadline expired", true));
|
|
}, remainingLeaseMs);
|
|
writeSync(1, encodeProviderGuardianReady({
|
|
nonce: guard.nonce,
|
|
rawDev: bootstrap.rawIdentity.dev,
|
|
rawIno: bootstrap.rawIdentity.ino,
|
|
sealedTempLeaf: bootstrap.sealedTempLeaf,
|
|
sealedDev: bootstrap.sealedIdentity.dev,
|
|
sealedIno: bootstrap.sealedIdentity.ino,
|
|
}));
|
|
state = "guarding";
|
|
}
|
|
|
|
function assertBoundPrivateLeaf(
|
|
fd: number,
|
|
target: string,
|
|
identity: OwnedIdentity,
|
|
label: string,
|
|
): void {
|
|
const descriptorMetadata = fstatSync(fd);
|
|
const pathnameMetadata = lstatSync(target);
|
|
assertOwnedMetadata(descriptorMetadata, identity, 1, 0o600, 0, label);
|
|
assertOwnedMetadata(pathnameMetadata, identity, 1, 0o600, 0, label);
|
|
if (pathnameMetadata.isSymbolicLink()) {
|
|
throw new TypeError(`provider guardian ${label} alias became symbolic`);
|
|
}
|
|
}
|
|
|
|
async function publishSealedArtifact(payload: Buffer): Promise<void> {
|
|
if (!transaction) {
|
|
throw new Error("provider guardian transaction identity is unavailable");
|
|
}
|
|
const publication = decodeProviderGuardianPublish(payload, transaction.guard.nonce);
|
|
if (
|
|
publication.sealedDev !== bootstrap.sealedIdentity.dev ||
|
|
publication.sealedIno !== bootstrap.sealedIdentity.ino
|
|
) {
|
|
throw new TypeError("provider guardian publish identity is invalid");
|
|
}
|
|
assertOwnedMetadata(
|
|
fstatSync(SEALED_TEMP_FD),
|
|
bootstrap.sealedIdentity,
|
|
1,
|
|
0o400,
|
|
publication.size,
|
|
"sealed publish descriptor",
|
|
);
|
|
const pathnameMetadata = await lstat(bootstrap.sealedTempPath);
|
|
assertOwnedMetadata(
|
|
pathnameMetadata,
|
|
bootstrap.sealedIdentity,
|
|
1,
|
|
0o400,
|
|
publication.size,
|
|
"sealed publish pathname",
|
|
);
|
|
if (pathnameMetadata.isSymbolicLink()) {
|
|
throw new TypeError("provider guardian sealed publish pathname became symbolic");
|
|
}
|
|
const actualSha256 = hashInheritedFile(SEALED_TEMP_FD, publication.size);
|
|
if (actualSha256 !== publication.sha256) {
|
|
throw new TypeError("provider guardian publish hash is invalid");
|
|
}
|
|
try {
|
|
await lstat(bootstrap.sealedPath);
|
|
throw new Error("provider guardian sealed output already exists");
|
|
} catch (error) {
|
|
if (!hasErrorCode(error, "ENOENT")) throw error;
|
|
}
|
|
await link(bootstrap.sealedTempPath, bootstrap.sealedPath);
|
|
await unlink(bootstrap.sealedTempPath);
|
|
fsyncSync(EVIDENCE_DIRECTORY_FD);
|
|
const finalMetadata = await lstat(bootstrap.sealedPath);
|
|
assertOwnedMetadata(
|
|
finalMetadata,
|
|
bootstrap.sealedIdentity,
|
|
1,
|
|
0o400,
|
|
publication.size,
|
|
"sealed final",
|
|
);
|
|
if (finalMetadata.isSymbolicLink()) {
|
|
throw new TypeError("provider guardian sealed final became symbolic");
|
|
}
|
|
state = "published";
|
|
writeSync(1, encodeProviderGuardianPublished({
|
|
nonce: transaction.guard.nonce,
|
|
sealedDev: bootstrap.sealedIdentity.dev,
|
|
sealedIno: bootstrap.sealedIdentity.ino,
|
|
}));
|
|
}
|
|
|
|
function assertOwnedPathMetadata(
|
|
target: string,
|
|
identity: OwnedIdentity,
|
|
expectedLinks: number,
|
|
expectedMode: number,
|
|
expectedSize: number,
|
|
label: string,
|
|
): void {
|
|
const metadata = lstatSync(target);
|
|
assertOwnedMetadata(metadata, identity, expectedLinks, expectedMode, expectedSize, label);
|
|
if (metadata.isSymbolicLink()) {
|
|
throw new TypeError(`provider guardian ${label} became symbolic`);
|
|
}
|
|
}
|
|
|
|
function assertOwnedMetadata(
|
|
metadata: Stats,
|
|
identity: OwnedIdentity,
|
|
expectedLinks: number,
|
|
expectedMode: number,
|
|
expectedSize: number,
|
|
label: string,
|
|
): void {
|
|
if (
|
|
!metadata.isFile() || metadata.dev !== identity.dev || metadata.ino !== identity.ino ||
|
|
metadata.nlink !== expectedLinks || (metadata.mode & 0o777) !== expectedMode ||
|
|
metadata.size !== expectedSize
|
|
) {
|
|
throw new TypeError(`provider guardian ${label} metadata is invalid`);
|
|
}
|
|
}
|
|
|
|
function hashInheritedFile(fd: number, size: number): string {
|
|
const digest = createHash("sha256");
|
|
const buffer = Buffer.allocUnsafe(Math.min(65_536, size));
|
|
let position = 0;
|
|
while (position < size) {
|
|
const requested = Math.min(buffer.byteLength, size - position);
|
|
const bytesRead = readSync(fd, buffer, 0, requested, position);
|
|
if (bytesRead <= 0) throw new Error("provider guardian sealed publish read was truncated");
|
|
digest.update(buffer.subarray(0, bytesRead));
|
|
position += bytesRead;
|
|
}
|
|
return digest.digest("hex");
|
|
}
|
|
|
|
async function prepareCommit(payload: Buffer): Promise<void> {
|
|
if (!transaction) throw new Error("provider guardian transaction identity is unavailable");
|
|
decodeProviderGuardianCommit(payload, transaction.guard.nonce);
|
|
const removedRaw = await cleanupOwnedProviderReport({
|
|
reportPath: bootstrap.rawPath,
|
|
reportDev: bootstrap.rawIdentity.dev,
|
|
reportIno: bootstrap.rawIdentity.ino,
|
|
});
|
|
if (!removedRaw) throw new Error("provider guardian raw output disappeared before commit");
|
|
state = "commitPending";
|
|
}
|
|
|
|
async function succeedOnCommittedEof(): Promise<void> {
|
|
const closeErrors: Error[] = [];
|
|
closePrivateFds(closeErrors);
|
|
if (closeErrors.length > 0) {
|
|
await failClosed(
|
|
126,
|
|
"provider guardian private descriptor close failed",
|
|
false,
|
|
closeErrors,
|
|
);
|
|
return;
|
|
}
|
|
terminal = true;
|
|
if (deadline) clearTimeout(deadline);
|
|
liveness.removeAllListeners();
|
|
liveness.destroy();
|
|
closeControlInputBestEffort();
|
|
process.exit(0);
|
|
}
|
|
|
|
async function failClosed(
|
|
exitCode: number,
|
|
message: string,
|
|
forceSignal = false,
|
|
priorErrors: readonly Error[] = [],
|
|
): Promise<void> {
|
|
if (terminal) return;
|
|
terminal = true;
|
|
if (deadline) clearTimeout(deadline);
|
|
liveness.removeAllListeners();
|
|
liveness.destroy();
|
|
const failures = [new Error(message), ...priorErrors];
|
|
await cleanupOwnedPath(bootstrap.rawStagingPath, bootstrap.rawIdentity, failures);
|
|
await cleanupOwnedPath(bootstrap.rawPath, bootstrap.rawIdentity, failures);
|
|
await cleanupOwnedPath(bootstrap.sealedTempPath, bootstrap.sealedIdentity, failures);
|
|
await cleanupOwnedPath(bootstrap.sealedPath, bootstrap.sealedIdentity, failures);
|
|
closePrivateFds(failures);
|
|
closeControlInputBestEffort();
|
|
writeAggregateDiagnostic("provider guardian failed", failures);
|
|
if (forceSignal) {
|
|
try {
|
|
process.kill(process.pid, "SIGKILL");
|
|
} finally {
|
|
process.exit(exitCode);
|
|
}
|
|
}
|
|
process.exit(exitCode);
|
|
}
|
|
|
|
async function cleanupOwnedPath(
|
|
target: string,
|
|
identity: OwnedIdentity,
|
|
errors: Error[],
|
|
): Promise<void> {
|
|
try {
|
|
await cleanupOwnedProviderReport({
|
|
reportPath: target,
|
|
reportDev: identity.dev,
|
|
reportIno: identity.ino,
|
|
});
|
|
} catch (error) {
|
|
errors.push(toError(error));
|
|
}
|
|
}
|
|
|
|
function closePrivateFds(errors: Error[]): void {
|
|
if (privateFdsClosed) return;
|
|
privateFdsClosed = true;
|
|
for (const fd of [RAW_STAGING_FD, SEALED_TEMP_FD]) {
|
|
try {
|
|
closeSync(fd);
|
|
} catch (error) {
|
|
errors.push(toError(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
function closeControlInputBestEffort(): void {
|
|
try {
|
|
closeSync(0);
|
|
} catch {
|
|
// Terminal cleanup and the exit status must not depend on a diagnostic fd.
|
|
}
|
|
}
|
|
|
|
function writeAggregateDiagnostic(label: string, failures: readonly Error[]): void {
|
|
const aggregate = failures.length > 1
|
|
? new AggregateError(failures, label, { cause: failures[0] })
|
|
: failures[0];
|
|
const detail = aggregate instanceof AggregateError
|
|
? aggregate.errors.map((error) => toError(error).message).join("; ")
|
|
: aggregate?.message ?? label;
|
|
try {
|
|
writeSync(2, `${label}: ${detail}\n`);
|
|
} catch {
|
|
// A closed parent-side pipe must not convert fail-closed termination to exit 0.
|
|
}
|
|
}
|
|
|
|
function assertInheritedDirectory(fd: number, canonicalPath: string, label: string): void {
|
|
const descriptorMetadata = fstatSync(fd);
|
|
const pathMetadata = lstatSync(canonicalPath);
|
|
if (
|
|
!descriptorMetadata.isDirectory() || !pathMetadata.isDirectory() ||
|
|
pathMetadata.isSymbolicLink() || descriptorMetadata.dev !== pathMetadata.dev ||
|
|
descriptorMetadata.ino !== pathMetadata.ino
|
|
) {
|
|
throw new TypeError(`provider guardian inherited ${label} fd is not a directory`);
|
|
}
|
|
}
|
|
|
|
function toError(error: unknown): Error {
|
|
return error instanceof Error ? error : new Error(String(error));
|
|
}
|
|
|
|
function hasErrorCode(error: unknown, code: string): boolean {
|
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
}
|