Share tagged readiness observations, retain partial provider facts, authenticate bridge identity, bound diagnostics, and preserve cancellation and lost-session reasons. Fail closed for unparsed MIPS ABI attributes and macOS saved Hopper databases without a script-capable bridge route. Reviewed fixes: #2010, #2108, #2513, #2695, #2731, #2771, #2828, #2867, #2868, #2870, #2880, #2919, #2945, #2961, #2981, #2998, #3000, #3002. Validation: 285 focused source/boundary tests passed. Real Ghidra, IDA, Linux Hopper, and Windows provider runs were not repeated. BREAKING CHANGE: MIPS targets with unresolved GNU ABI attributes and saved Hopper databases on macOS are refused rather than advertised as supported.
141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
import { constants } from "node:fs";
|
|
import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import {
|
|
windowsNativeCapabilities,
|
|
type WindowsNativeCapabilitySet,
|
|
} from "../process/WindowsAuthority.js";
|
|
import {
|
|
probeProcessCaptureCapability,
|
|
type ProcessCaptureCapability,
|
|
} from "../process/capture/ProcessCaptureCapability.js";
|
|
|
|
/** One named Windows host observation without inferred authority. */
|
|
export type WindowsCapabilityOutcome =
|
|
| { readonly available: true; readonly reason: null }
|
|
| { readonly available: false; readonly reason: string };
|
|
|
|
/** Injectable host probes used by CI and deterministic unit tests. */
|
|
export interface WindowsCapabilityDependencies {
|
|
readonly platform: NodeJS.Platform;
|
|
readonly architecture: NodeJS.Architecture;
|
|
probeSymlinkCreation(): Promise<WindowsCapabilityOutcome>;
|
|
probeNoFollowOpen(): WindowsCapabilityOutcome;
|
|
probePrivateAcl(): WindowsCapabilityOutcome;
|
|
probeUnixDomainSocket(): WindowsCapabilityOutcome;
|
|
probePty(): Promise<ProcessCaptureCapability>;
|
|
nativeCapabilities?(): WindowsNativeCapabilitySet;
|
|
}
|
|
|
|
/** Machine-readable facts for features that need Windows-specific security. */
|
|
export interface WindowsCapabilityReport {
|
|
readonly platform: NodeJS.Platform;
|
|
readonly architecture: NodeJS.Architecture;
|
|
readonly capabilities: Readonly<{
|
|
symlink_creation: WindowsCapabilityOutcome;
|
|
no_follow_open: WindowsCapabilityOutcome;
|
|
private_acl: WindowsCapabilityOutcome;
|
|
unix_domain_socket: WindowsCapabilityOutcome;
|
|
pty: WindowsCapabilityOutcome;
|
|
}>;
|
|
/** Security controls that are unavailable until native authority exists. */
|
|
readonly security: WindowsNativeCapabilitySet;
|
|
}
|
|
|
|
/** Probe named prerequisites without converting missing controls into support. */
|
|
export const probeWindowsCapabilities = async (
|
|
dependencies: WindowsCapabilityDependencies = systemDependencies(),
|
|
): Promise<WindowsCapabilityReport> => {
|
|
const [symlinkCreation, pty] = await Promise.all([
|
|
dependencies.probeSymlinkCreation(),
|
|
dependencies.probePty(),
|
|
]);
|
|
return {
|
|
platform: dependencies.platform,
|
|
architecture: dependencies.architecture,
|
|
capabilities: {
|
|
symlink_creation: symlinkCreation,
|
|
no_follow_open: dependencies.probeNoFollowOpen(),
|
|
private_acl: dependencies.probePrivateAcl(),
|
|
unix_domain_socket: dependencies.probeUnixDomainSocket(),
|
|
pty: pty.available
|
|
? { available: true, reason: null }
|
|
: { available: false, reason: pty.reason },
|
|
},
|
|
security:
|
|
dependencies.nativeCapabilities?.() ??
|
|
windowsNativeCapabilities(dependencies.platform),
|
|
};
|
|
};
|
|
|
|
const systemDependencies = (): WindowsCapabilityDependencies => {
|
|
const security = windowsNativeCapabilities(process.platform);
|
|
return {
|
|
platform: process.platform,
|
|
architecture: process.arch,
|
|
probeSymlinkCreation,
|
|
probeNoFollowOpen: () =>
|
|
systemNoFollowOpenCapability(process.platform, constants.O_NOFOLLOW),
|
|
probePrivateAcl: () => security.private_runtime_dacl,
|
|
nativeCapabilities: () => security,
|
|
probeUnixDomainSocket: () =>
|
|
process.platform === "win32"
|
|
? {
|
|
available: false,
|
|
reason: "Node path-based IPC uses named pipes on Windows",
|
|
}
|
|
: { available: true, reason: null },
|
|
probePty: probeProcessCaptureCapability,
|
|
};
|
|
};
|
|
|
|
/** Report pathname no-follow support without treating it as Windows authority. */
|
|
export const systemNoFollowOpenCapability = (
|
|
platform: NodeJS.Platform,
|
|
noFollow: number | undefined,
|
|
): WindowsCapabilityOutcome => {
|
|
if (platform === "win32")
|
|
return {
|
|
available: false,
|
|
reason:
|
|
"Node pathname O_NOFOLLOW is not Windows handle authority; native admission is reported separately in security.",
|
|
};
|
|
return typeof noFollow === "number"
|
|
? { available: true, reason: null }
|
|
: {
|
|
available: false,
|
|
reason: "O_NOFOLLOW is unavailable in this Node runtime",
|
|
};
|
|
};
|
|
|
|
const probeSymlinkCreation = async (): Promise<WindowsCapabilityOutcome> => {
|
|
const root = await mkdtemp(join(tmpdir(), "rea-symlink-probe-"));
|
|
const target = join(root, "target");
|
|
const link = join(root, "link");
|
|
try {
|
|
await writeFile(target, "probe");
|
|
await symlink(target, link, "file");
|
|
return { available: true, reason: null };
|
|
} catch (cause: unknown) {
|
|
return {
|
|
available: false,
|
|
reason:
|
|
cause instanceof Error &&
|
|
"code" in cause &&
|
|
typeof cause.code === "string"
|
|
? cause.code
|
|
: "symlink creation failed",
|
|
};
|
|
} finally {
|
|
try {
|
|
await rm(root, { recursive: true, force: true });
|
|
} catch (cause: unknown) {
|
|
// Cleanup failure must not replace the machine-readable probe outcome.
|
|
// The probe root lives beneath the OS temporary directory and is unique.
|
|
void cause;
|
|
}
|
|
}
|
|
};
|