fix(fleet): harden managed launch composition

AMD1213-C: repair stale array consumer, fail closed on foreign link provenance, validate manifests before mutation, and exercise the fleet MCP preflight call path.
This commit is contained in:
terra
2026-08-13 15:37:32 -05:00
parent 2755f86f7b
commit 326a1a58b5
8 changed files with 347 additions and 89 deletions
@@ -390,7 +390,7 @@ function resolveCredential(
profile: FleetAgentLaunchProfile,
userHome: string,
seatHome: string,
managedLinks: ManagedLinkState,
_managedLinks: ManagedLinkState,
): Pick<FleetLaunchComposition, 'bundle' | 'credential'> {
assertRealDirectory(userHome, 'user Mosaic root');
const realUserHome = realpathSync(userHome);
@@ -442,16 +442,6 @@ function resolveCredential(
const credentialLink = join(seatHome, CREDENTIAL_FILES[profile.harness]);
const seatInfo = lstatIfPresent(credentialLink);
if (seatInfo?.isSymbolicLink() && !managedLinks.existed) {
const existingTarget = currentLinkTarget(credentialLink);
try {
assertContained(resolvedAuthRoot, realpathSync(existingTarget), 'credential migration');
// A pre-manifest seat may adopt only a credential link inside the central auth root.
managedLinks.links.set(credentialLink, existingTarget);
} catch {
// Foreign links remain unrecorded and are refused by apply before any writes.
}
}
if (seatInfo && !seatInfo.isSymbolicLink()) {
throw new FleetLaunchError(
'FIRST_AUTH_REFUSAL',
@@ -571,7 +561,11 @@ function resolveManagedLinks(
return { installs, prune };
}
function readManagedLinkState(seatHome: string): ManagedLinkState {
function readManagedLinkState(
seatHome: string,
profile: FleetAgentLaunchProfile,
userHome: string,
): ManagedLinkState {
const path = join(seatHome, '.mosaic-managed-links.json');
const info = lstatIfPresent(path);
if (!info) return { path, links: new Map(), existed: false };
@@ -605,6 +599,24 @@ function readManagedLinkState(seatHome: string): ManagedLinkState {
`managed link manifest has invalid entry: ${path}`,
);
}
const credential = join(seatHome, CREDENTIAL_FILES[profile.harness]);
const pluginRoot = join(seatHome, 'plugins');
const skillRoot = join(seatHome, 'skills');
const authRoot = join(userHome, 'auth', profile.harness);
const inRoot = (root: string, candidate: string): boolean => {
const rel = relative(resolve(root), resolve(candidate));
return rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
};
const valid =
(link === credential && inRoot(authRoot, target)) ||
(inRoot(pluginRoot, link) && inRoot(join(userHome, 'plugins'), target)) ||
(inRoot(skillRoot, link) && inRoot(join(userHome, 'skills'), target));
if (!valid) {
throw new FleetLaunchError(
'COMPOSITION_FAILED',
`managed link manifest entry escapes an approved seat/store root: ${path}`,
);
}
links.set(link, target);
}
return { path, links, existed: true };
@@ -720,7 +732,7 @@ export function resolveFleetLaunchComposition(
: readSettingsLayer('agent', overlayPath, false),
];
const merged = deepMergeSettings(...layers.map((layer) => layer.value));
const managedLinks = readManagedLinkState(seatHome);
const managedLinks = readManagedLinkState(seatHome, profile, roots.userHome);
const credential = resolveCredential(profile, roots.userHome, seatHome, managedLinks);
const plugins = resolveManagedLinks(
'plugin',
@@ -773,10 +785,6 @@ function ensureSymlink(link: string, target: string, managedLinks: ManagedLinkSt
const info = lstatIfPresent(link);
if (info?.isSymbolicLink()) {
const current = currentLinkTarget(link);
if (current === target) {
managedLinks.links.set(link, target);
return;
}
if (managedLinks.links.get(link) !== current) {
throw new FleetLaunchError(
'COMPOSITION_FAILED',
@@ -809,7 +817,6 @@ function assertManagedLinkMutationAllowed(
);
}
const current = currentLinkTarget(link);
if (target !== undefined && current === target) return;
if (managedLinks.links.get(link) !== current) {
throw new FleetLaunchError(
'COMPOSITION_FAILED',
@@ -830,22 +837,20 @@ function canonicalJson(value: unknown): unknown {
/** Apply a previously resolved plan. No caller should apply a dry-run plan. */
export function applyFleetLaunchComposition(plan: FleetLaunchComposition): void {
// All link-state checks must complete before the first filesystem mutation.
// This makes a late foreign/retargeted link refusal leave the seat untouched.
assertManagedLinkMutationAllowed(plan.credential.link, plan.credential.target, plan.managedLinks);
for (const path of plan.prune)
assertManagedLinkMutationAllowed(path, undefined, plan.managedLinks);
for (const install of plan.installs) {
assertManagedLinkMutationAllowed(install.link, install.target, plan.managedLinks);
}
mkdirSync(plan.seatHome, { recursive: true });
const preparedManifest = prepareManagedLinkManifest(plan.managedLinks);
let descriptorOpen = true;
let committedManifest = false;
try {
assertManagedLinkMutationAllowed(
plan.credential.link,
plan.credential.target,
plan.managedLinks,
);
for (const path of plan.prune) {
assertManagedLinkMutationAllowed(path, undefined, plan.managedLinks);
}
for (const install of plan.installs) {
assertManagedLinkMutationAllowed(install.link, install.target, plan.managedLinks);
}
const settings = `${JSON.stringify(canonicalJson(plan.settings.merged), null, 2)}\n`;
writeFileSync(plan.settings.output, settings, { mode: 0o600 });
writeFileSync(plan.settings.snapshot, settings, { mode: 0o600 });
@@ -874,11 +879,12 @@ export function applyFleetLaunchComposition(plan: FleetLaunchComposition): void
}
writeManagedLinkState(plan.managedLinks, preparedManifest);
closeSync(preparedManifest.descriptor);
descriptorOpen = false;
renameSync(preparedManifest.path, plan.managedLinks.path);
committedManifest = true;
} finally {
if (!committedManifest) {
closeSync(preparedManifest.descriptor);
if (descriptorOpen) closeSync(preparedManifest.descriptor);
rmSync(preparedManifest.path, { force: true });
}
}