"use strict"; const assert = require("node:assert/strict"); const fs = require("node:fs"); const fsp = require("node:fs/promises"); const os = require("node:os"); const path = require("node:path"); const test = require("node:test"); const { applyHostConfigPatch, buildPatch, cleanupBackups, inspectHostConfig, } = require("../../lib/aas-v1/adapters"); const { inspectRegularFile, runWindowsAcl } = require("../../lib/aas-v1/adapters/safety"); const FIXTURES = path.join(__dirname, "fixtures", "aas-v1-adapters"); const CODEX_SERVER = { command: "/opt/aas/aas-mcp", args: ["--stdio", "--runtime", "14.6.0"], enabled: true }; const CLAUDE_SERVER = { command: "/opt/aas/aas-mcp", args: ["--stdio"], env: { AAS_MODE: "offline", SECRET: "new-secret-never-previewed" } }; async function temporaryDirectory(t, prefix = "aas-adapter-") { const directory = await fsp.mkdtemp(path.join(os.tmpdir(), prefix)); t.after(() => fsp.rm(directory, { recursive: true, force: true })); return directory; } async function copyFixture(t, name, mode = 0o640) { const directory = await temporaryDirectory(t); const target = path.join(directory, name.endsWith("json") ? ".mcp.json" : "config.toml"); await fsp.copyFile(path.join(FIXTURES, name), target); await fsp.chmod(target, mode); return { directory, target }; } async function backupFiles(directory) { return (await fsp.readdir(directory)).filter((name) => name.endsWith(".bak") || name.endsWith(".json")).sort(); } test("fixture provenance pins the validated host contracts without real user data", async () => { const provenance = JSON.parse(await fsp.readFile(path.join(FIXTURES, "provenance.json"), "utf8")); assert.equal(provenance.validatedAt, "2026-07-17"); assert.equal(provenance.anonymized, true); assert.deepEqual(provenance.hosts.map((entry) => entry.host), ["codex", "claude"]); assert.ok(provenance.hosts.every((entry) => entry.hostVersion && entry.documentation.startsWith("https://"))); }); test("Codex preview is value-free and patches only the aas table while preserving unknown text", async (t) => { const { target } = await copyFixture(t, "codex-existing.toml"); const original = await fsp.readFile(target, "utf8"); const inspected = await inspectHostConfig({ host: "codex", scope: "project", configPath: target }); assert.equal(inspected.sectionPresent, true); assert.deepEqual(inspected.unknownKeys, ["startup_timeout_sec"]); const patch = await buildPatch({ host: "codex", scope: "project", configPath: target, server: CODEX_SERVER }); assert.equal(patch.changed, true); assert.deepEqual(patch.redactedDiff.changedPaths, ["mcp_servers.aas.args", "mcp_servers.aas.command", "mcp_servers.aas.enabled"]); const serialized = JSON.stringify(patch); assert.ok(!serialized.includes("fixture-secret")); assert.ok(!serialized.includes(CODEX_SERVER.command)); assert.ok(!serialized.includes("14.6.0")); assert.equal(await fsp.readFile(target, "utf8"), original); }); test("Claude preview preserves unknown objects and never exposes env values", async (t) => { const { directory, target } = await copyFixture(t, "claude-existing.mcp.json"); const patch = await buildPatch({ host: "claude", scope: "user", configPath: target, server: CLAUDE_SERVER }); const serialized = JSON.stringify(patch); for (const forbidden of ["fixture-root-secret", "fixture-other-secret", "fixture-old-secret", "new-secret", CLAUDE_SERVER.command]) { assert.ok(!serialized.includes(forbidden), forbidden); } assert.deepEqual(patch.inspection.unknownKeys, ["disabled"]); const result = await applyHostConfigPatch({ patch, approved: true, backupDirectory: path.join(directory, "backups"), now: "2026-07-17T01:02:03.000Z" }); assert.equal(result.status, "applied"); const parsed = JSON.parse(await fsp.readFile(target, "utf8")); assert.equal(parsed.unrelated.token, "fixture-root-secret-must-not-appear-in-preview"); assert.equal(parsed.mcpServers.other.env.TOKEN, "fixture-other-secret-must-not-appear-in-preview"); assert.equal(parsed.mcpServers.aas.disabled, false); assert.deepEqual(parsed.mcpServers.aas.env, CLAUDE_SERVER.env); }); test("apply requires approval, preserves mode and owner, writes 0600 backups, and leaves no stage or lock", async (t) => { const { directory, target } = await copyFixture(t, "codex-existing.toml", 0o640); const originalStat = await fsp.stat(target); const original = await fsp.readFile(target); const patch = await buildPatch({ host: "codex", scope: "project", configPath: target, server: CODEX_SERVER }); await assert.rejects(applyHostConfigPatch({ patch, backupDirectory: path.join(directory, "backups") }), (error) => error.code === "AAS_ADAPTER_APPROVAL_REQUIRED"); assert.deepEqual(await fsp.readFile(target), original); const result = await applyHostConfigPatch({ patch, approved: true, backupDirectory: path.join(directory, "backups"), retention: 3, now: "2026-07-17T02:00:00.000Z" }); assert.equal(result.status, "applied"); const nextStat = await fsp.stat(target); assert.equal(nextStat.mode & 0o777, originalStat.mode & 0o777); assert.equal(nextStat.uid, originalStat.uid); assert.equal(nextStat.gid, originalStat.gid); const configured = await fsp.readFile(target, "utf8"); assert.match(configured, /startup_timeout_sec = 20/); assert.match(configured, /# preserve this comment/); assert.match(configured, /fixture-secret-must-not-appear-in-preview/); assert.ok(configured.includes('command = "/opt/aas/aas-mcp"')); const files = await backupFiles(path.join(directory, "backups")); assert.equal(files.length, 2); for (const name of files) assert.equal((await fsp.stat(path.join(directory, "backups", name))).mode & 0o777, 0o600); const metadataName = files.find((name) => name.endsWith(".json")); const metadata = JSON.parse(await fsp.readFile(path.join(directory, "backups", metadataName), "utf8")); assert.deepEqual(metadata.retention, { enforcement: "explicit-cleanup", maxEntries: 3 }); assert.deepEqual(await fsp.readFile(result.backup.backupPath), original); assert.ok((await fsp.readdir(directory)).every((name) => !name.includes("aas-stage") && !name.endsWith(".aas.lock"))); }); test("apply blocks drift and an existing lock without overwriting current bytes", async (t) => { const { directory, target } = await copyFixture(t, "codex-existing.toml"); const patch = await buildPatch({ host: "codex", scope: "project", configPath: target, server: CODEX_SERVER }); await fsp.appendFile(target, "\n# concurrent edit\n"); const drifted = await fsp.readFile(target); await assert.rejects( applyHostConfigPatch({ patch, approved: true, backupDirectory: path.join(directory, "backups") }), (error) => error.code === "AAS_ADAPTER_CONFIG_CHANGED", ); assert.deepEqual(await fsp.readFile(target), drifted); const fresh = await buildPatch({ host: "codex", scope: "project", configPath: target, server: CODEX_SERVER }); const lockPath = path.join(directory, ".config.toml.aas.lock"); await fsp.writeFile(lockPath, "occupied", { mode: 0o600 }); await assert.rejects( applyHostConfigPatch({ patch: fresh, approved: true, backupDirectory: path.join(directory, "backups") }), (error) => error.code === "AAS_ADAPTER_LOCKED", ); assert.deepEqual(await fsp.readFile(target), drifted); }); test("symlink, non-regular, and ownership mismatches are rejected", async (t) => { const directory = await temporaryDirectory(t); const real = path.join(directory, "real.toml"); const link = path.join(directory, "link.toml"); const nonRegular = path.join(directory, "non-regular"); await fsp.writeFile(real, ""); await fsp.symlink(real, link); await fsp.mkdir(nonRegular); await assert.rejects(inspectHostConfig({ host: "codex", scope: "project", configPath: link }), (error) => error.code === "AAS_ADAPTER_CONFIG_UNSAFE"); await assert.rejects(inspectHostConfig({ host: "codex", scope: "project", configPath: nonRegular }), (error) => error.code === "AAS_ADAPTER_CONFIG_UNSAFE"); const stat = await fsp.stat(real); await assert.rejects(inspectRegularFile(real, { expectedUid: stat.uid + 1 }), (error) => error.code === "AAS_ADAPTER_OWNERSHIP_MISMATCH"); }); test("Windows ACL runner passes paths out of the command and reports bounded diagnostics", () => { const inspectedPath = String.raw`C:\Users\Example\.codex\config.toml`; let invocation; const runner = (...args) => { invocation = args; return { status: 1, stdout: "", stderr: "Get-Acl failed\r\nwith details\u0000" }; }; assert.throws( () => runWindowsAcl("$p=$env:AAS_WINDOWS_ACL_PATH", inspectedPath, { phase: "inspectAcl", runner }), (error) => { assert.equal(error.code, "AAS_ADAPTER_WINDOWS_ACL_FAILED"); assert.deepEqual(error.details, { status: 1, phase: "inspectAcl", path: inspectedPath, diagnostic: "Get-Acl failed with details", }); return true; }, ); assert.deepEqual(invocation[1], ["-NoProfile", "-NonInteractive", "-Command", "$p=$env:AAS_WINDOWS_ACL_PATH"]); assert.equal(invocation[2].env.AAS_WINDOWS_ACL_PATH, inspectedPath); assert.ok(!invocation[1].includes(inspectedPath)); }); test("ambiguous TOML and duplicate JSON keys fail closed", async (t) => { const directory = await temporaryDirectory(t); const toml = path.join(directory, "config.toml"); await fsp.writeFile(toml, "[mcp_servers]\naas.command = \"ambiguous\"\n"); await assert.rejects(buildPatch({ host: "codex", scope: "project", configPath: toml, server: CODEX_SERVER }), (error) => error.code === "AAS_ADAPTER_CODEX_SECTION_AMBIGUOUS"); const json = path.join(directory, ".mcp.json"); await fsp.writeFile(json, '{"mcpServers":{},"mcpServers":{}}'); await assert.rejects(buildPatch({ host: "claude", scope: "project", configPath: json, server: CLAUDE_SERVER }), (error) => error.code === "AAS_ADAPTER_CLAUDE_JSON_DUPLICATE_KEY"); }); test("a missing explicit path is created with 0600 and no implicit backup", async (t) => { const directory = await temporaryDirectory(t); const target = path.join(directory, ".mcp.json"); const patch = await buildPatch({ host: "claude", scope: "project", configPath: target, server: CLAUDE_SERVER }); assert.equal(patch.exists, false); const result = await applyHostConfigPatch({ patch, approved: true }); assert.equal(result.backup, null); assert.equal((await fsp.stat(target)).mode & 0o777, 0o600); assert.deepEqual(JSON.parse(await fsp.readFile(target, "utf8")).mcpServers.aas, CLAUDE_SERVER); }); test("retention is metadata only until explicit cleanup is approved", async (t) => { const { directory, target } = await copyFixture(t, "codex-existing.toml"); const backups = path.join(directory, "backups"); const first = await buildPatch({ host: "codex", scope: "project", configPath: target, server: CODEX_SERVER }); await applyHostConfigPatch({ patch: first, approved: true, backupDirectory: backups, retention: 1, now: "2026-07-17T03:00:00.000Z" }); const secondServer = { ...CODEX_SERVER, args: [...CODEX_SERVER.args, "--second"] }; const second = await buildPatch({ host: "codex", scope: "project", configPath: target, server: secondServer }); await applyHostConfigPatch({ patch: second, approved: true, backupDirectory: backups, retention: 1, now: "2026-07-17T04:00:00.000Z" }); assert.equal((await backupFiles(backups)).length, 4); await assert.rejects(cleanupBackups({ backupDirectory: backups, configPath: target, keep: 1 }), (error) => error.code === "AAS_ADAPTER_APPROVAL_REQUIRED"); assert.equal((await backupFiles(backups)).length, 4); const cleaned = await cleanupBackups({ backupDirectory: backups, configPath: target, keep: 1, approved: true }); assert.equal(cleaned.removed.length, 1); assert.equal((await backupFiles(backups)).length, 2); }); test("adapters never infer HOME and reject relative config and backup paths", async (t) => { const { directory, target } = await copyFixture(t, "codex-existing.toml"); await assert.rejects(buildPatch({ host: "codex", scope: "user", configPath: "config.toml", server: CODEX_SERVER }), (error) => error.code === "AAS_ADAPTER_PATH_INVALID"); const patch = await buildPatch({ host: "codex", scope: "user", configPath: target, server: CODEX_SERVER }); await assert.rejects(applyHostConfigPatch({ patch, approved: true, backupDirectory: "backups" }), (error) => error.code === "AAS_ADAPTER_BACKUP_PATH_INVALID"); const publicBackups = path.join(directory, "public-backups"); await fsp.mkdir(publicBackups, { mode: 0o755 }); await fsp.chmod(publicBackups, 0o755); await assert.rejects(applyHostConfigPatch({ patch, approved: true, backupDirectory: publicBackups }), (error) => error.code === "AAS_ADAPTER_BACKUP_DIRECTORY_PERMISSIONS"); }); test("Windows sensitive-byte and directory-flush contracts stay fail-closed", async () => { const adapterSource = await fsp.readFile(path.join(__dirname, "../../lib/aas-v1/adapters/index.js"), "utf8"); const safetySource = await fsp.readFile(path.join(__dirname, "../../lib/aas-v1/adapters/safety.js"), "utf8"); const durabilitySource = await fsp.readFile(path.join(__dirname, "../../lib/aas-v1/durability.js"), "utf8"); const stageCreate = adapterSource.indexOf('const handle = await fsp.open(stagePath, "wx", targetMode)'); const stageAcl = adapterSource.indexOf("copyWindowsAcl(patch.configPath, stagePath)", stageCreate); const stageWrite = adapterSource.indexOf("await handle.writeFile(internal.nextBytes)", stageCreate); assert.ok(stageCreate >= 0 && stageAcl > stageCreate && stageAcl < stageWrite); const privateWriteStart = safetySource.indexOf("async function writeExclusiveSynced"); const privateAcl = safetySource.indexOf("hardenWindowsPrivatePath(filePath, false)", privateWriteStart); const privateWrite = safetySource.indexOf("await handle.writeFile(bytes)", privateWriteStart); assert.ok(privateAcl > privateWriteStart && privateAcl < privateWrite); assert.ok(durabilitySource.includes("CreateFileW($directoryPath,0x40000000,7,")); assert.ok(!durabilitySource.includes("CreateFileW($directoryPath,0x80000000,7,")); assert.ok(durabilitySource.includes("$env:${WINDOWS_DIRECTORY_FLUSH_PATH_ENV}")); assert.ok(!durabilitySource.includes("CreateFileW($args[0]")); assert.ok(durabilitySource.includes("AAS_WIN32_DIRECTORY_FLUSH_FAILURE")); assert.ok(durabilitySource.includes("GetLastWin32Error()")); });