#!/usr/bin/env node import fs from "node:fs"; import path from "node:path"; import process from "node:process"; const POLICY_PATH = "/Volumes/JZAO/HoloLake/persona-runtime/repo-012-main/routing/persona-host-write-boundary.json"; const policy = JSON.parse(fs.readFileSync(POLICY_PATH, "utf8")); function expandHome(value) { return value.replace(/^~(?=\/|$)/, "/Users/bingshuolingdianyuanhe"); } function normal(value, cwd = process.cwd()) { const expanded = expandHome(String(value || "")); return path.resolve(cwd, expanded); } function patternRegex(pattern) { const escaped = expandHome(pattern).replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*"); return new RegExp(`^${escaped}(?:/.*)?$`); } function isAllowed(host, target) { const rule = policy.hosts[host]; if (!rule) return { allowed: false, code: "HOST_UNKNOWN" }; if (rule.write_mode.startsWith("READ_ONLY")) return { allowed: false, code: "HOST_READ_ONLY" }; const resolved = normal(target); const matched = rule.allowed_write_roots.find((item) => patternRegex(item).test(resolved)); return matched ? { allowed: true, code: "WITHIN_HOST_WRITE_ROOT", resolved, matched } : { allowed: false, code: "WRITE_OUTSIDE_HOST_ROOT", resolved }; } function emit(result, hook = false) { const reason = `${result.code}: ${result.host || "unknown"} -> ${result.resolved || result.path || "path-unresolved"}`; if (hook) { process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: result.allowed ? "allow" : "deny", permissionDecisionReason: reason, } })}\n`); } else { process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); } if (!result.allowed) process.exitCode = 2; } function collectPathValues(value, out = []) { if (Array.isArray(value)) { for (const item of value) collectPathValues(item, out); } else if (value && typeof value === "object") { for (const [key, item] of Object.entries(value)) { if (typeof item === "string" && /(?:path|file|directory|cwd|workdir|target|destination)/i.test(key)) out.push(item); else collectPathValues(item, out); } } return out; } function shellPaths(command) { const values = []; for (const match of command.matchAll(/["'](\/[^"']+)["']/g)) values.push(match[1]); for (const match of command.matchAll(/(?:^|[\s=])(\/[^\s;|&<>]+)/g)) values.push(match[1]); return [...new Set(values)]; } const READ_TOOLS = new Set(["Read", "Glob", "Grep", "Search", "WebSearch", "WebFetch"]); const MUTATING_SHELL = /(?:^|[;&|\s])(?:rm|mv|cp|install|mkdir|rmdir|touch|chmod|chown|ln|tee|truncate|dd|rsync|git\s+(?:add|commit|push|checkout|restore|reset|clean|apply|merge|rebase|tag)|sed\s+-i|perl\s+-i|python\d*\s+[^\n]*(?:write|append|unlink|remove|rename)|node\s+[^\n]*(?:write|install|deploy)|npm\s+(?:install|publish)|pnpm\s+(?:install|publish)|apply_patch)(?:\s|$)|(?:>>?|2>)\s*[^&]/i; function evaluateHook(host, input) { const tool = String(input.tool_name || input.toolName || ""); const toolInput = input.tool_input || input.toolInput || {}; if (READ_TOOLS.has(tool)) return { allowed: true, code: "READ_ONLY_TOOL", host, tool }; if (tool === "Bash") { const command = String(toolInput.command || ""); if (!MUTATING_SHELL.test(command)) return { allowed: true, code: "READ_ONLY_SHELL", host, tool }; const rule = policy.hosts[host]; if (!rule || rule.write_mode.startsWith("READ_ONLY")) return { allowed: false, code: "HOST_READ_ONLY", host, tool }; const candidates = shellPaths(command); const explicitCwd = toolInput.cwd || toolInput.workdir || toolInput.working_directory; const cdMatch = command.match(/(?:^|[;&|]\s*)cd\s+["']?(\/[^\n;&|"']+)/); const cwd = normal(explicitCwd || cdMatch?.[1] || process.cwd()); const cwdCheck = isAllowed(host, cwd); const pathChecks = candidates.map((item) => isAllowed(host, item)); const denied = pathChecks.find((item) => !item.allowed); if (denied) return { ...denied, host, tool }; if (!cwdCheck.allowed && candidates.length === 0) return { ...cwdCheck, code: "MUTATING_SHELL_WITHOUT_ALLOWED_EXPLICIT_TARGET", host, tool }; return { allowed: true, code: "MUTATING_SHELL_WITHIN_HOST_ROOT", host, tool, resolved: cwd }; } const candidates = collectPathValues(toolInput); if (candidates.length === 0) return { allowed: false, code: "MUTATING_TOOL_TARGET_UNRESOLVED", host, tool }; for (const candidate of candidates) { const check = isAllowed(host, candidate); if (!check.allowed) return { ...check, host, tool }; } return { allowed: true, code: "TOOL_TARGETS_WITHIN_HOST_ROOT", host, tool }; } function valueAfter(args, flag) { const index = args.indexOf(flag); return index >= 0 ? args[index + 1] : undefined; } const [mode, ...args] = process.argv.slice(2); if (mode === "check") { const host = valueAfter(args, "--host"); const target = valueAfter(args, "--path"); if (!host || !target) { process.stderr.write("usage: check --host HOST --path PATH\n"); process.exit(2); } emit({ ...isAllowed(host, target), host, path: target }); } else if (mode === "hook") { const host = valueAfter(args, "--host"); if (!host) { process.stderr.write("usage: hook --host HOST\n"); process.exit(2); } let raw = ""; for await (const chunk of process.stdin) raw += chunk; let input; try { input = JSON.parse(raw || "{}"); } catch { emit({ allowed: false, code: "HOOK_INPUT_INVALID_JSON", host }, true); process.exit(2); } emit(evaluateHook(host, input), true); } else if (mode === "audit") { emit({ allowed: true, code: "POLICY_LOADED", policy_id: policy.policy_id, version: policy.version, policy_path: POLICY_PATH }); } else { process.stderr.write("usage: host-write-admission.mjs check|hook|audit\n"); process.exit(2); }