import crypto from "node:crypto"; import { execSync } from "node:child_process"; import { createHash } from "node:crypto"; import { readFileSync, statSync, readdirSync, existsSync } from "node:fs"; import { join, relative, extname, basename } from "node:path"; import { hostname, platform, arch, cpus, totalmem, freemem } from "node:os"; export interface MachineGraphNode { id: string; type: "file" | "process" | "port" | "project" | "artifact" | "service"; properties: Record; } export interface MachineGraph { hostId: string; timestamp: string; nodes: MachineGraphNode[]; } export interface CorpusEntry { resource_id: string; path: string; name: string; extension: string; hash: string; size: number; modified: string; project?: string; semanticTags: string[]; sensitivity: number; } export interface OperatingState { hostname: string; platform: string; arch: string; cpuCount: number; totalMemoryMB: number; freeMemoryMB: number; processes: ProcessInfo[]; listeningPorts: PortInfo[]; uptime: number; } export interface ProcessInfo { pid: number; name: string; command: string; listeningPort?: number; } export interface PortInfo { port: number; protocol: string; address: string; state: string; } export interface PolicyEntry { principal: string; action: string; resource: string; conditions: Record; decision: "permit" | "forbid"; } export interface Receipt { id: string; timestamp: string; principal: string; action: string; resource: string; resourceId: string; decision: "permit" | "deny"; result: "success" | "failure" | "denied"; contentHash?: string; disclosureTier?: number; details: string; signature: string; } export interface DisclosureLayer { tier: number; name: string; description: string; keyId: string; encryptedContent: string; iv: string; keyLifetimeMs: number; expiresAt: string; revoked: boolean; } export interface Mailbox { id: string; hostId: string; recipientPublicKey: string; resourceId: string; contentHash: string; operations: string[]; issuedAt: string; expiresAt: string; decayPolicy: string; layers: DisclosureLayer[]; claimed: boolean; claimChallenge?: string; attendanceLog: AttendanceRecord[]; } export interface AttendanceRecord { timestamp: string; operation: string; tier: number; result: "success" | "denied" | "expired"; receiptId: string; } export interface HostModel { hostId: string; hostname: string; graph: MachineGraph; corpus: CorpusEntry[]; state: OperatingState; policies: PolicyEntry[]; receipts: Receipt[]; mailboxes: Map; keyPair: { publicKey: string; privateKey: string }; } const SENSITIVITY_BY_EXT: Record = { ".pem": 5, ".key": 5, ".env": 4, ".secret": 5, ".p12": 5, ".pfx": 5, ".keystore": 5, ".ts": 1, ".js": 1, ".py": 1, ".rs": 1, ".md": 1, ".json": 2, ".yaml": 2, ".yml": 2, ".sql": 3, ".db": 3, ".sqlite": 3, }; function hashContent(content: Buffer | string): string { return createHash("sha256").update(content).digest("hex"); } function generateHostId(): string { const raw = `${hostname()}-${platform()}-${arch()}-${cpus().length}`; return `host_${hashContent(raw).substring(0, 12)}`; } function generateKeyPair(): { publicKey: string; privateKey: string } { const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519"); return { publicKey: publicKey.export({ type: "spki", format: "pem" }).toString(), privateKey: privateKey.export({ type: "pkcs8", format: "pem" }).toString(), }; } export function signData(privateKey: string, data: string): string { const key = crypto.createPrivateKey(privateKey); return crypto.sign(null, Buffer.from(data), key).toString("base64url"); } export function verifySignature(publicKey: string, data: string, signature: string): boolean { try { const key = crypto.createPublicKey(publicKey); return crypto.verify(null, Buffer.from(data), key, Buffer.from(signature, "base64url")); } catch { return false; } } function encryptLayer(plaintext: string, tierKey: Buffer): { content: string; iv: string } { const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv("aes-256-gcm", tierKey, iv); const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]); const tag = cipher.getAuthTag(); return { content: Buffer.concat([encrypted, tag]).toString("base64"), iv: iv.toString("base64"), }; } export function decryptLayer(encryptedContent: string, iv: string, tierKey: Buffer): string { const data = Buffer.from(encryptedContent, "base64"); const tag = data.subarray(data.length - 16); const ciphertext = data.subarray(0, data.length - 16); const decipher = crypto.createDecipheriv("aes-256-gcm", tierKey, Buffer.from(iv, "base64")); decipher.setAuthTag(tag); return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8"); } function deriveTierKey(masterKey: Buffer, tier: number): Buffer { return crypto.createHash("sha256").update(masterKey).update(Buffer.from([tier])).digest(); } function scanDirectory(dir: string, maxDepth: number, currentDepth: number = 0): CorpusEntry[] { if (currentDepth >= maxDepth || !existsSync(dir)) return []; const entries: CorpusEntry[] = []; try { const items = readdirSync(dir, { withFileTypes: true }); for (const item of items) { if (item.name.startsWith(".") || item.name === "node_modules" || item.name === ".git") continue; const fullPath = join(dir, item.name); if (item.isDirectory()) { entries.push(...scanDirectory(fullPath, maxDepth, currentDepth + 1)); } else if (item.isFile()) { try { const stat = statSync(fullPath); const content = readFileSync(fullPath); const hash = hashContent(content); const ext = extname(item.name); const sensitivity = SENSITIVITY_BY_EXT[ext] ?? 1; entries.push({ resource_id: `filecap://${generateHostId()}/object_${hash.substring(0, 16)}/version_${hash.substring(0, 8)}`, path: fullPath, name: item.name, extension: ext, hash, size: stat.size, modified: stat.mtime.toISOString(), semanticTags: deriveSemanticTags(item.name, ext, fullPath), sensitivity, }); } catch { // Skip unreadable files } } } } catch { // Skip inaccessible directories } return entries; } function deriveSemanticTags(name: string, ext: string, path: string): string[] { const tags: string[] = []; const lower = name.toLowerCase(); if (ext === ".ts" || ext === ".js") tags.push("code"); if (ext === ".md") tags.push("documentation"); if (ext === ".json") tags.push("config"); if (ext === ".py") tags.push("code", "python"); if (ext === ".rs") tags.push("code", "rust"); if (lower.includes("test")) tags.push("test"); if (lower.includes("spec")) tags.push("spec"); if (lower.includes("proof")) tags.push("proof"); if (lower.includes("key") || lower.includes("secret")) tags.push("sensitive"); if (path.includes("src/")) tags.push("source"); if (path.includes("docs/")) tags.push("docs"); if (path.includes("test")) tags.push("test"); return tags; } function getRunningProcesses(): ProcessInfo[] { const processes: ProcessInfo[] = []; try { if (platform() === "darwin") { const output = execSync("ps aux | head -50", { encoding: "utf8", timeout: 3000 }); const lines = output.split("\n").slice(1); for (const line of lines) { const parts = line.trim().split(/\s+/); if (parts.length >= 11) { const pid = parseInt(parts[1], 10); const command = parts.slice(10).join(" "); const name = parts[10] ?? "unknown"; if (!isNaN(pid) && pid > 0) { processes.push({ pid, name, command }); } } } } else { const output = execSync("ps aux | head -50", { encoding: "utf8", timeout: 3000 }); const lines = output.split("\n").slice(1); for (const line of lines) { const parts = line.trim().split(/\s+/); if (parts.length >= 11) { const pid = parseInt(parts[1], 10); const command = parts.slice(10).join(" "); const name = parts[10] ?? "unknown"; if (!isNaN(pid) && pid > 0) { processes.push({ pid, name, command }); } } } } } catch { // ps failed — return empty } return processes.slice(0, 30); } function getListeningPorts(): PortInfo[] { const ports: PortInfo[] = []; try { const output = execSync("lsof -i -P -n 2>/dev/null | grep LISTEN | head -20", { encoding: "utf8", timeout: 3000, }); for (const line of output.split("\n")) { const parts = line.trim().split(/\s+/); if (parts.length >= 9) { const addr = parts[8] ?? ""; const portMatch = addr.match(/:(\d+)$/); if (portMatch) { ports.push({ port: parseInt(portMatch[1], 10), protocol: parts[7] ?? "tcp", address: addr.split(":")[0] ?? "*", state: "LISTEN", }); } } } } catch { // lsof failed } return ports; } export function buildHostModel(scanRoots: string[]): HostModel { const hostId = generateHostId(); const keyPair = generateKeyPair(); const corpus: CorpusEntry[] = []; for (const root of scanRoots) { corpus.push(...scanDirectory(root, 4)); } const state: OperatingState = { hostname: hostname(), platform: platform(), arch: arch(), cpuCount: cpus().length, totalMemoryMB: Math.round(totalmem() / 1024 / 1024), freeMemoryMB: Math.round(freemem() / 1024 / 1024), processes: getRunningProcesses(), listeningPorts: getListeningPorts(), uptime: process.uptime(), }; const graphNodes: MachineGraphNode[] = []; for (const entry of corpus) { graphNodes.push({ id: entry.resource_id, type: "file", properties: { name: entry.name, hash: entry.hash, extension: entry.extension, size: entry.size, modified: entry.modified, semanticTags: entry.semanticTags, sensitivity: entry.sensitivity, project: entry.project, }, }); } for (const proc of state.processes) { graphNodes.push({ id: `proc:${proc.pid}`, type: "process", properties: { pid: proc.pid, name: proc.name, command: proc.command, listeningPort: proc.listeningPort, }, }); } for (const port of state.listeningPorts) { graphNodes.push({ id: `port:${port.port}`, type: "port", properties: { port: port.port, protocol: port.protocol, address: port.address, state: port.state, }, }); } const defaultPolicies: PolicyEntry[] = [ { principal: "*", action: "read", resource: "file[sensitivity<=2]", conditions: {}, decision: "permit", }, { principal: "*", action: "read", resource: "file[sensitivity>=4]", conditions: {}, decision: "forbid", }, { principal: "*", action: "search", resource: "*", conditions: {}, decision: "permit", }, { principal: "*", action: "describe", resource: "*", conditions: {}, decision: "permit", }, { principal: "*", action: "verify", resource: "*", conditions: {}, decision: "permit", }, { principal: "*", action: "write", resource: "*", conditions: {}, decision: "forbid", }, ]; return { hostId, hostname: hostname(), graph: { hostId, timestamp: new Date().toISOString(), nodes: graphNodes, }, corpus, state, policies: defaultPolicies, receipts: [], mailboxes: new Map(), keyPair, }; } export function createMailbox( model: HostModel, recipientPublicKey: string, resourcePath: string, operations: string[], decayPolicy: string = "full→extract→summary→receipt" ): Mailbox { const hostId = model.hostId; const mailboxId = `mbx_${crypto.randomUUID().substring(0, 12)}`; let content: string; let contentHash: string; let resourceId: string; try { const raw = readFileSync(resourcePath); content = raw.toString("utf8"); contentHash = hashContent(raw); resourceId = `filecap://${hostId}/object_${contentHash.substring(0, 16)}/version_${contentHash.substring(0, 8)}`; } catch { content = ""; contentHash = hashContent(resourcePath); resourceId = `filecap://${hostId}/object_${contentHash.substring(0, 16)}`; } const masterKey = crypto.randomBytes(32); const now = Date.now(); const tiers = [ { tier: 0, name: "full", desc: "Full file content", lifetimeMs: 15 * 60 * 1000 }, { tier: 1, name: "extract", desc: "Selected exact passages", lifetimeMs: 60 * 60 * 1000 }, { tier: 2, name: "structured", desc: "Structured extraction", lifetimeMs: 6 * 60 * 60 * 1000 }, { tier: 3, name: "summary", desc: "Semantic summary and embeddings", lifetimeMs: 7 * 24 * 60 * 60 * 1000 }, { tier: 4, name: "receipt", desc: "Hash, provenance and access receipt", lifetimeMs: Infinity }, ]; const layers: DisclosureLayer[] = tiers.map((t) => { const tierKey = deriveTierKey(masterKey, t.tier); let plaintext: string; switch (t.tier) { case 0: plaintext = content; break; case 1: plaintext = content.split("\n").slice(0, 50).join("\n"); break; case 2: plaintext = JSON.stringify({ name: basename(resourcePath), size: content.length, hash: contentHash, firstLines: content.split("\n").slice(0, 10), lineCount: content.split("\n").length, }, null, 2); break; case 3: plaintext = JSON.stringify({ name: basename(resourcePath), hash: contentHash, size: content.length, summary: `File contains ${content.split("\n").length} lines, ${content.length} bytes`, tags: deriveSemanticTags(basename(resourcePath), extname(resourcePath), resourcePath), }); break; case 4: plaintext = JSON.stringify({ resourceId, hash: contentHash, hostId, mailboxId, provenance: "HostModel receipt layer", }); break; default: plaintext = ""; } const encrypted = encryptLayer(plaintext, tierKey); const expiresAt = t.lifetimeMs === Infinity ? "permanent" : new Date(now + t.lifetimeMs).toISOString(); return { tier: t.tier, name: t.name, description: t.desc, keyId: `key_${mailboxId}_t${t.tier}`, encryptedContent: encrypted.content, iv: encrypted.iv, keyLifetimeMs: t.lifetimeMs, expiresAt, revoked: false, }; }); const claimChallenge = crypto.randomBytes(16).toString("base64url"); const mailbox: Mailbox = { id: mailboxId, hostId, recipientPublicKey, resourceId, contentHash, operations, issuedAt: new Date(now).toISOString(), expiresAt: new Date(now + 24 * 60 * 60 * 1000).toISOString(), decayPolicy, layers, claimed: false, claimChallenge, attendanceLog: [], }; model.mailboxes.set(mailboxId, mailbox); return mailbox; } export function getCurrentDecayTier(mailbox: Mailbox): number { const now = Date.now(); const issued = new Date(mailbox.issuedAt).getTime(); const tierLifetimes = [ 15 * 60 * 1000, 60 * 60 * 1000, 6 * 60 * 60 * 1000, 7 * 24 * 60 * 60 * 1000, Infinity, ]; const elapsed = now - issued; for (let i = 0; i < tierLifetimes.length; i++) { if (elapsed < tierLifetimes[i]) { if (mailbox.layers[i] && !mailbox.layers[i].revoked) { return i; } } } return 4; } export function revokeExpiredLayers(mailbox: Mailbox): void { const now = Date.now(); const issued = new Date(mailbox.issuedAt).getTime(); const tierLifetimes = [ 15 * 60 * 1000, 60 * 60 * 1000, 6 * 60 * 60 * 1000, 7 * 24 * 60 * 60 * 1000, Infinity, ]; for (let i = 0; i < mailbox.layers.length; i++) { const elapsed = now - issued; if (elapsed >= tierLifetimes[i] && !mailbox.layers[i].revoked) { mailbox.layers[i].revoked = true; } } } export function evaluatePolicy( model: HostModel, principal: string, action: string, resource: string, context: Record = {} ): { decision: "permit" | "deny"; matchedPolicy?: PolicyEntry } { let permitMatch: PolicyEntry | undefined; let forbidMatch: PolicyEntry | undefined; for (const policy of model.policies) { if (policy.principal !== "*" && policy.principal !== principal) continue; if (policy.action !== "*" && policy.action !== action) continue; if (policy.resource === "*" || matchesResource(policy.resource, resource)) { if (policy.decision === "permit" && !permitMatch) { permitMatch = policy; } if (policy.decision === "forbid" && !forbidMatch) { forbidMatch = policy; } } } if (forbidMatch) { return { decision: "deny", matchedPolicy: forbidMatch }; } if (permitMatch) { return { decision: "permit", matchedPolicy: permitMatch }; } return { decision: "deny" }; } function matchesResource(pattern: string, resource: string): boolean { if (pattern === resource) return true; if (pattern.includes("[") && pattern.includes("]")) { const baseMatch = pattern.split("[")[0]; if (resource.startsWith(baseMatch)) { const condStr = pattern.substring(pattern.indexOf("[") + 1, pattern.lastIndexOf("]")); const conditions = condStr.split("][").map((c) => c.replace(/[\[\]]/g, "")); for (const cond of conditions) { if (cond.includes("<=")) { const [key, val] = cond.split("<="); const resourceVal = extractResourceProperty(resource, key); if (resourceVal !== undefined && resourceVal > parseInt(val, 10)) return false; } else if (cond.includes(">=")) { const [key, val] = cond.split(">="); const resourceVal = extractResourceProperty(resource, key); if (resourceVal !== undefined && resourceVal < parseInt(val, 10)) return false; } else if (cond.includes("=")) { const [key, val] = cond.split("="); const resourceVal = extractResourceProperty(resource, key); if (resourceVal !== undefined && String(resourceVal) !== val) return false; } } return true; } } return false; } function extractResourceProperty(resource: string, key: string): number | undefined { if (key === "sensitivity") { const match = resource.match(/sensitivity=(\d+)/); return match ? parseInt(match[1], 10) : undefined; } return undefined; } export function createReceipt( model: HostModel, principal: string, action: string, resource: string, resourceId: string, decision: "permit" | "deny", result: "success" | "failure" | "denied", details: string, contentHash?: string, disclosureTier?: number ): Receipt { const id = `receipt_${crypto.randomUUID().substring(0, 12)}`; const timestamp = new Date().toISOString(); const receiptData = JSON.stringify({ id, timestamp, principal, action, resource, resourceId, decision, result, details, contentHash, disclosureTier, }); const signature = signData(model.keyPair.privateKey, receiptData); const receipt: Receipt = { id, timestamp, principal, action, resource, resourceId, decision, result, details, contentHash, disclosureTier, signature, }; model.receipts.push(receipt); return receipt; } export function resolveMachineXPath(model: HostModel, selector: string): MachineGraphNode[] { const results: MachineGraphNode[] = []; const parsed = parseMXPath(selector); if (!parsed) return results; for (const node of model.graph.nodes) { if (parsed.type && node.type !== parsed.type) continue; let matches = true; for (const [key, condition] of Object.entries(parsed.conditions)) { const nodeValue = node.properties[key]; if (nodeValue === undefined) { matches = false; break; } if (condition.op === "=" && String(nodeValue) !== condition.value) { matches = false; break; } if (condition.op === "<=" && Number(nodeValue) > Number(condition.value)) { matches = false; break; } if (condition.op === ">=" && Number(nodeValue) < Number(condition.value)) { matches = false; break; } if (condition.op === "<" && Number(nodeValue) >= Number(condition.value)) { matches = false; break; } if (condition.op === ">" && Number(nodeValue) <= Number(condition.value)) { matches = false; break; } if (condition.op === "contains" && !String(nodeValue).includes(condition.value)) { matches = false; break; } } if (matches) { results.push(node); } } return results; } interface ParsedMXPath { type?: string; conditions: Record; } function parseMXPath(selector: string): ParsedMXPath | null { const cleaned = selector.replace(/^mx:\/\//, "").replace(/^\/+/, ""); const parts = cleaned.split("/"); const lastSegment = parts[parts.length - 1] || parts[0] || ""; const typeMatch = lastSegment.match(/^(\w+)\[/); const type = typeMatch ? typeMatch[1] : (lastSegment.match(/^(\w+)$/) ? lastSegment : undefined); const conditions: Record = {}; const condMatches = lastSegment.matchAll(/\[([^\]]+)\]/g); for (const match of condMatches) { const cond = match[1]; const operators = ["<=", ">=", "!=", "=", "<", ">"]; let matched = false; for (const op of operators) { const idx = cond.indexOf(op); if (idx > 0) { const key = cond.substring(0, idx).trim(); const value = cond.substring(idx + op.length).trim().replace(/^["']|["']$/g, ""); conditions[key] = { op, value }; matched = true; break; } } if (!matched) { conditions[cond] = { op: "contains", value: cond }; } } return { type, conditions }; } export function getHostSummary(model: HostModel): Record { return { hostId: model.hostId, hostname: model.hostname, platform: model.state.platform, arch: model.state.arch, cpuCount: model.state.cpuCount, totalMemoryMB: model.state.totalMemoryMB, freeMemoryMB: model.state.freeMemoryMB, corpusSize: model.corpus.length, processCount: model.state.processes.length, listeningPortCount: model.state.listeningPorts.length, graphNodeCount: model.graph.nodes.length, mailboxCount: model.mailboxes.size, receiptCount: model.receipts.length, publicKey: model.keyPair.publicKey, }; }