@24klynx/permissions 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { CommandSafety, ToolDescriptor, ToolInvocation } from "@lynx/tools";
1
+ import { CommandSafety, ToolDescriptor, ToolInvocation } from "@24klynx/tools";
2
2
 
3
3
  //#region src/modes.d.ts
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/modes.ts","../src/rules.ts","../src/pipeline.ts","../src/classifier.ts","../src/denial.ts","../src/sources.ts","../src/mcp-channel.ts"],"sourcesContent":["/**\n * Permission modes — the 6 operating modes that control how\n * tool execution permissions are resolved.\n *\n * default — ask user for every RequiresApproval tool\n * auto — LLM YOLO classifier decides, user can override\n * yolo — auto‑approve everything, max throughput\n * dontAsk — deny all RequiresApproval tools silently\n * headless — no TUI, auto‑deny interactive prompts\n * plan — read‑only mode, all mutating tools denied\n */\n\n// ── Types ────────────────────────────────────────────\n\nexport type PermissionMode = \"default\" | \"auto\" | \"yolo\" | \"dontAsk\" | \"headless\" | \"plan\";\n\nexport interface ModeBehavior {\n /** Auto‑approve Safe tools without asking. */\n autoApproveSafe: boolean;\n /** Auto‑approve WorkspaceSafe tools. */\n autoApproveWorkspaceSafe: boolean;\n /** Auto‑deny Dangerous tools. */\n autoDenyDangerous: boolean;\n /** Use LLM classifier for RequiresApproval tools. */\n useClassifier: boolean;\n /** Show interactive prompts to the user. */\n showPrompts: boolean;\n /** Allow any file‑modifying tools. */\n allowWrites: boolean;\n}\n\n// ── Mode table ───────────────────────────────────────\n\nconst MODE_TABLE: Record<PermissionMode, ModeBehavior> = {\n default: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: true,\n allowWrites: true,\n },\n auto: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: true,\n autoDenyDangerous: true,\n useClassifier: true,\n showPrompts: false,\n allowWrites: true,\n },\n yolo: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: true,\n autoDenyDangerous: false,\n useClassifier: false,\n showPrompts: false,\n allowWrites: true,\n },\n dontAsk: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: false,\n allowWrites: false,\n },\n headless: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: false,\n allowWrites: true,\n },\n plan: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: false,\n allowWrites: false,\n },\n};\n\n// ── Public API ───────────────────────────────────────\n\n/** Get the behavior profile for a given mode. */\nexport function getModeBehavior(mode: PermissionMode): ModeBehavior {\n return MODE_TABLE[mode];\n}\n\n/** List all valid mode names. */\nexport function listModes(): PermissionMode[] {\n return Object.keys(MODE_TABLE) as PermissionMode[];\n}\n\n/** Validate that a string is a recognized mode. */\nexport function isValidMode(value: string): value is PermissionMode {\n return value in MODE_TABLE;\n}\n","/**\n * Permission rule matching engine.\n *\n * Rules are evaluated in priority order from multiple sources:\n * 1. Session overrides (highest)\n * 2. User settings\n * 3. Project settings\n * 4. Workspace settings\n * 5. Plugin defaults\n * 6. Built‑in defaults\n * 7. Hardcoded blocklist (lowest — always evaluated first)\n *\n * Each rule has a glob pattern that matches tool names.\n * When multiple rules match, higher‑priority sources win.\n */\n\nimport type { CommandSafety } from \"@lynx/tools\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface PermissionRule {\n /** Glob pattern matching tool names (e.g. \"bash\", \"read_*\", \"*\"). */\n glob: string;\n /** Optional argument pattern for tool‑specific rules (e.g. \"git push *\"). */\n args?: string;\n /** Override the safety classification for matched tools. */\n safety?: CommandSafety;\n /** Explicitly allow matched tools. */\n allow?: boolean;\n /** Explicitly deny matched tools. */\n deny?: boolean;\n /** Human‑readable reason shown to the user. */\n reason?: string;\n}\n\nexport interface RuleMatch {\n rule: PermissionRule;\n source: RuleSource;\n}\n\nexport type RuleSource =\n | \"blocklist\" // 0 — hardcoded, always deny\n | \"builtin\" // 1 — built‑in defaults\n | \"plugin\" // 2 — plugin‑provided rules\n | \"config\" // 3 — ~/.lynx/config.json permissions.allow/deny\n | \"workspace\" // 4 — .lynx/permissions.json in the workspace\n | \"project\" // 5 — CLAUDE.md project settings\n | \"user\" // 6 — ~/.lynx/settings.json\n | \"session\"; // 7 — runtime session override\n\n/** Priority weight — higher number = higher priority. */\nconst SOURCE_PRIORITY: Record<RuleSource, number> = {\n blocklist: 0,\n builtin: 1,\n plugin: 2,\n config: 3,\n workspace: 4,\n project: 5,\n user: 6,\n session: 7,\n};\n\n// ── Glob matching ────────────────────────────────────\n\nfunction globMatch(pattern: string, value: string): boolean {\n // Simple wildcard matching: * matches any sequence, ? matches one char\n const regex = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*/g, \".*\")\n .replace(/\\?/g, \".\");\n return new RegExp(`^${regex}$`).test(value);\n}\n\n// ── Public API ───────────────────────────────────────\n\nexport interface RuleEngine {\n /** Register a rule from a specific source. */\n add(source: RuleSource, rule: PermissionRule): void;\n\n /** Remove all rules from a source. */\n clearSource(source: RuleSource): void;\n\n /** Find all rules that match a tool name, sorted by priority (highest first). */\n match(toolName: string): RuleMatch[];\n\n /** Check if any matching rule explicitly denies the tool. */\n isDenied(toolName: string): boolean;\n\n /** Check if any matching rule explicitly allows the tool. */\n isAllowed(toolName: string): boolean;\n}\n\n/**\n * Create a rule matching engine.\n *\n * Rules are evaluated lazily — each match() call re‑scans\n * the rule set for the given tool name.\n */\nexport function createRuleEngine(): RuleEngine {\n const rulesBySource = new Map<RuleSource, PermissionRule[]>();\n\n // Hardcoded blocklist — always denied\n const BLOCKLIST: PermissionRule[] = [\n { glob: \"sudo\", deny: true, reason: \"Privilege escalation is forbidden\" },\n { glob: \"su\", deny: true, reason: \"Privilege escalation is forbidden\" },\n { glob: \"doas\", deny: true, reason: \"Privilege escalation is forbidden\" },\n ];\n\n // Register the blocklist immediately\n for (const rule of BLOCKLIST) {\n const existing = rulesBySource.get(\"blocklist\") ?? [];\n existing.push(rule);\n rulesBySource.set(\"blocklist\", existing);\n }\n\n const engine: RuleEngine = {\n add(source: RuleSource, rule: PermissionRule): void {\n const existing = rulesBySource.get(source) ?? [];\n existing.push(rule);\n rulesBySource.set(source, existing);\n },\n\n clearSource(source: RuleSource): void {\n rulesBySource.delete(source);\n },\n\n match(toolName: string): RuleMatch[] {\n const matches: RuleMatch[] = [];\n\n for (const [source, rules] of rulesBySource) {\n for (const rule of rules) {\n if (globMatch(rule.glob, toolName)) {\n matches.push({ rule, source });\n }\n }\n }\n\n // Sort by source priority (highest first)\n matches.sort((a, b) => SOURCE_PRIORITY[b.source] - SOURCE_PRIORITY[a.source]);\n\n return matches;\n },\n\n isDenied(toolName: string): boolean {\n const matches = this.match(toolName);\n // Higher‑priority source wins\n if (matches.length === 0) return false;\n const top = matches[0];\n return top.rule.deny === true;\n },\n\n isAllowed(toolName: string): boolean {\n const matches = this.match(toolName);\n if (matches.length === 0) return false;\n const top = matches[0];\n return top.rule.allow === true;\n },\n };\n\n return engine;\n}\n","/**\n * 14‑step permission pipeline — evaluates every tool invocation\n * through a sequence of checks, short‑circuiting on the first\n * definitive answer.\n *\n * Steps:\n * 1. Tool not found in registry → deny\n * 2. Tool is Safe → auto‑approve\n * 3. Tool is Dangerous → auto‑deny (unless yolo mode)\n * 4. Rule engine explicit deny → deny\n * 5. Rule engine explicit allow → allow\n * 6. Mode behavior check (plan mode denies writes, etc.)\n * 7. Denial tracker circuit breaker → deny\n * 8. Mode: yolo → allow\n * 9. Mode: dontAsk → deny\n * 10. Mode: headless → deny (no interactive TUI)\n * 11. Mode: auto → LLM classifier\n * 12. Mode: default → ask user\n * 13. User response → allow/deny\n * 14. Default: deny\n */\n\nimport type { ToolDescriptor, ToolInvocation } from \"@lynx/tools\";\nimport type { PermissionMode, ModeBehavior } from \"./modes.js\";\nimport type { RuleEngine } from \"./rules.js\";\nimport type { DenialTracker } from \"./denial.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface PipelineContext {\n mode: PermissionMode;\n behavior: ModeBehavior;\n ruleEngine: RuleEngine;\n denialTracker: DenialTracker;\n /** User callback — returns true to allow, false to deny. */\n askUser?: (invocation: ToolInvocation) => Promise<boolean>;\n /** LLM classifier callback — returns true to allow. */\n classify?: (invocation: ToolInvocation) => Promise<boolean>;\n}\n\nexport interface PipelineResult {\n allowed: boolean;\n reason: string;\n step: number;\n /** If true, the result was determined without user interaction. */\n automatic: boolean;\n}\n\n// ── Step function type ────────────────────────────────\n\n/** A single pipeline step. Returns a result to short‑circuit, or null to continue. */\ntype StepFn = (\n tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n) => Promise<PipelineResult | null> | PipelineResult | null;\n\n// ── Step implementations ──────────────────────────────\n\nfunction step1_unknownTool(tool: ToolDescriptor): PipelineResult | null {\n if (!tool) {\n return { allowed: false, reason: \"未知工具\", step: 1, automatic: true };\n }\n return null;\n}\n\nfunction step2_safeTool(tool: ToolDescriptor): PipelineResult | null {\n if (tool.safety === \"Safe\") {\n return { allowed: true, reason: \"安全工具 — 始终允许\", step: 2, automatic: true };\n }\n return null;\n}\n\nfunction step3_dangerousTool(\n tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (tool.safety === \"Dangerous\" && ctx.behavior.autoDenyDangerous) {\n return { allowed: false, reason: \"危险工具 — 已自动拒绝\", step: 3, automatic: true };\n }\n return null;\n}\n\nfunction step4_ruleDeny(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.ruleEngine.isDenied(invocation.toolName)) {\n return { allowed: false, reason: \"被权限规则拒绝\", step: 4, automatic: true };\n }\n return null;\n}\n\nfunction step5_ruleAllow(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.ruleEngine.isAllowed(invocation.toolName)) {\n return { allowed: true, reason: \"被权限规则允许\", step: 5, automatic: true };\n }\n return null;\n}\n\nfunction step6_planModeDeniesWrites(\n tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"plan\" && tool.kind !== \"ReadOnly\") {\n return {\n allowed: false,\n reason: `写入工具 \"${tool.name}\" 在计划模式下被拒绝`,\n step: 6,\n automatic: true,\n };\n }\n return null;\n}\n\nfunction step7_circuitBreaker(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.denialTracker.isTripped()) {\n return {\n allowed: false,\n reason: \"熔断器已触发 — 自动拒绝\",\n step: 7,\n automatic: true,\n };\n }\n return null;\n}\n\nfunction step8_yolo(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"yolo\") {\n return { allowed: true, reason: \"YOLO 模式 — 已自动批准\", step: 8, automatic: true };\n }\n return null;\n}\n\nfunction step9_dontAsk(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"dontAsk\") {\n return { allowed: false, reason: \"免问模式 — 已自动拒绝\", step: 9, automatic: true };\n }\n return null;\n}\n\nfunction step10_headless(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"headless\") {\n return {\n allowed: false,\n reason: \"无头模式 — 无交互式提示\",\n step: 10,\n automatic: true,\n };\n }\n return null;\n}\n\nasync function step11_autoClassify(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): Promise<PipelineResult | null> {\n if (ctx.mode !== \"auto\" || !ctx.classify) return null;\n try {\n const allowed = await ctx.classify(invocation);\n return {\n allowed,\n reason: allowed ? \"自动分类为安全\" : \"自动分类为不安全\",\n step: 11,\n automatic: true,\n };\n } catch {\n return null; // Classifier failed — fall through\n }\n}\n\nasync function step12_defaultAskUser(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): Promise<PipelineResult | null> {\n if (ctx.mode !== \"default\") return null;\n if (!ctx.askUser) {\n return {\n allowed: false,\n reason: \"默认模式 — 无用户提示可用\",\n step: 12,\n automatic: true,\n };\n }\n try {\n const allowed = await ctx.askUser(invocation);\n return {\n allowed,\n reason: allowed ? \"用户已批准\" : \"用户已拒绝\",\n step: 13,\n automatic: false,\n };\n } catch {\n return { allowed: false, reason: \"用户提示失败\", step: 13, automatic: false };\n }\n}\n\n/** 兜底步骤 — 始终返回结果。 */\nfunction step14_defaultDeny(): PipelineResult {\n return { allowed: false, reason: \"默认拒绝 — 无规则匹配\", step: 14, automatic: true };\n}\n\n// ── Ordered pipeline ──────────────────────────────────\n\nconst PIPELINE: StepFn[] = [\n step1_unknownTool,\n step2_safeTool,\n step3_dangerousTool,\n step4_ruleDeny,\n step5_ruleAllow,\n step6_planModeDeniesWrites,\n step7_circuitBreaker,\n step8_yolo,\n step9_dontAsk,\n step10_headless,\n step11_autoClassify,\n step12_defaultAskUser,\n step14_defaultDeny,\n];\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Run a tool invocation through the 14‑step permission pipeline.\n *\n * Each step is an independent function. The pipeline short‑circuits\n * on the first non‑null result.\n */\nexport async function runPipeline(\n tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): Promise<PipelineResult> {\n for (const step of PIPELINE) {\n const result = await step(tool, invocation, ctx);\n if (result) return result;\n }\n // 不可达 — step14 始终返回结果\n return { allowed: false, reason: \"默认拒绝 — 无规则匹配\", step: 14, automatic: true };\n}\n","/**\n * YOLO classifier — a lightweight LLM‑based permission decider\n * for \"auto\" mode. Uses a ~200‑token prompt and expects a\n * yes/no response within 500ms.\n *\n * Pattern (inspired by FengMing's `classifyPermissionTool` +\n * Claude Code's auto‑mode classifier):\n * 1. Check cache → return immediately if hit\n * 2. Call fast LLM with compact prompt → allow/deny\n * 3. On timeout or error → fall back to heuristic\n *\n * The classifier is stateless — each call is independent.\n * It should NOT be used for Dangerous tools (those are\n * auto‑denied regardless of classifier output).\n */\n\nimport type { ToolInvocation, ToolDescriptor } from \"@lynx/tools\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface YoloClassifier {\n /**\n * Ask the classifier whether a tool invocation should be allowed.\n *\n * Returns true if the invocation looks safe, false otherwise.\n * Never throws — on error, falls back to a conservative heuristic.\n */\n classify(tool: ToolDescriptor, invocation: ToolInvocation): Promise<boolean>;\n}\n\n/** LLM completion function signature — injected via DI. */\nexport type ClassifyLlmCall = (prompt: string) => Promise<string>;\n\n/** Options for creating the classifier. */\nexport interface CreateYoloClassifierOptions {\n /**\n * Optional LLM completion function.\n *\n * When provided, the classifier sends a ~200‑token prompt to a\n * fast model (e.g. Haiku / Flash) and parses \"allow\" / \"deny\".\n * When omitted, falls back to the heuristic classifier only.\n */\n llmCall?: ClassifyLlmCall;\n\n /** Timeout in milliseconds for the LLM call. Default 500. */\n timeoutMs?: number;\n}\n\n// ── Constants ────────────────────────────────────────\n\nconst DEFAULT_TIMEOUT_MS = 500;\n\n/** Compact prompt template (~200 tokens). */\nconst CLASSIFIER_PROMPT = `You are a security classifier. Given a tool invocation, respond with ONLY \"allow\" or \"deny\".\n\nRules:\n- Read-only operations (read_file, ls, grep, search) → allow\n- File writes within the workspace → allow\n- Network requests → allow\n- Shell commands that modify system state → deny\n- Commands that delete files outside the workspace → deny\n- Commands that install packages → deny\n- Escalation commands (sudo, su) → deny\n\nTool: {toolName}\nDescription: {toolDescription}\nInput: {toolInput}\n\nResponse:`;\n\n// ── Heuristic fallback ───────────────────────────────\n\n/** Patterns that always result in deny. */\nconst DENY_PATTERNS = [\n \"sudo\",\n \"rm -rf /\",\n \"chmod 777\",\n \"DROP TABLE\",\n \"DELETE FROM\",\n \"shutdown\",\n \"reboot\",\n \"format\",\n \"mkfs\",\n \"curl | bash\",\n \"wget -O - |\",\n \"> /dev/sda\",\n \"dd if=\",\n] as const;\n\n/** Patterns that indicate safe dev workflows. */\nconst ALLOW_PATTERNS = [\n \"npm test\",\n \"npm run\",\n \"pnpm test\",\n \"pnpm build\",\n \"git \",\n \"node \",\n \"tsc\",\n \"vitest\",\n \"eslint\",\n \"prettier\",\n] as const;\n\nfunction heuristicClassify(tool: ToolDescriptor, invocation: ToolInvocation): boolean {\n // ReadOnly tools are always safe\n if (tool.kind === \"ReadOnly\") return true;\n\n // Check for dangerous patterns in the payload\n const payload = JSON.stringify(invocation.payload).toLowerCase();\n\n for (const pattern of DENY_PATTERNS) {\n if (payload.includes(pattern.toLowerCase())) {\n return false;\n }\n }\n\n for (const pattern of ALLOW_PATTERNS) {\n if (payload.includes(pattern.toLowerCase())) {\n return true;\n }\n }\n\n // Conservative default\n return tool.safety === \"Safe\";\n}\n\n// ── Cache ────────────────────────────────────────────\n\n/** Cache key derived from tool identity + input hash. */\nfunction cacheKey(tool: ToolDescriptor, invocation: ToolInvocation): string {\n const inputHash = JSON.stringify(invocation.payload);\n return `${tool.name}:${tool.safety}:${inputHash}`;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Create a YOLO classifier.\n *\n * When `llmCall` is provided, prompts a fast LLM to decide each\n * classification. A cache avoids redundant calls for identical\n * (tool, safety, args) combinations within the same session.\n *\n * Falls back to a heuristic classifier on timeout or error.\n */\nexport function createYoloClassifier(opts: CreateYoloClassifierOptions = {}): YoloClassifier {\n const { llmCall, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;\n\n // Session‑scoped cache\n const cache = new Map<string, boolean>();\n\n const classifier: YoloClassifier = {\n async classify(tool: ToolDescriptor, invocation: ToolInvocation): Promise<boolean> {\n // 1. Check cache\n const key = cacheKey(tool, invocation);\n const cached = cache.get(key);\n if (cached !== undefined) return cached;\n\n // 2. Try LLM classifier\n if (llmCall) {\n try {\n const prompt = CLASSIFIER_PROMPT.replace(\"{toolName}\", tool.name)\n .replace(\"{toolDescription}\", tool.description)\n .replace(\"{toolInput}\", JSON.stringify(invocation.payload));\n\n const raw = await Promise.race([\n llmCall(prompt),\n new Promise<string>((_, reject) =>\n setTimeout(() => reject(new Error(\"Classifier timeout\")), timeoutMs),\n ),\n ]);\n\n const decision = raw.trim().toLowerCase().startsWith(\"allow\");\n cache.set(key, decision);\n return decision;\n } catch {\n // Timeout or LLM error → fall through to heuristic\n }\n }\n\n // 3. Heuristic fallback\n const decision = heuristicClassify(tool, invocation);\n cache.set(key, decision);\n return decision;\n },\n };\n\n return classifier;\n}\n","/**\n * Denial tracker with circuit breaker.\n *\n * When a user denies 3 consecutive tool requests within the same\n * turn, the circuit breaker trips and auto‑denies all subsequent\n * requests for the rest of that turn.\n *\n * The breaker resets at the start of each new turn.\n */\n\n// ── Types ────────────────────────────────────────────\n\nexport interface DenialTracker {\n /** Record a denial from the user. */\n recordDenial(): void;\n /** Record an approval (resets the consecutive count). */\n recordApproval(): void;\n /** Check whether the circuit breaker has tripped. */\n isTripped(): boolean;\n /** Reset the tracker for a new turn. */\n resetTurn(): void;\n /** Current consecutive denial count. */\n readonly consecutiveDenials: number;\n}\n\n// ── Constants ────────────────────────────────────────\n\n/** Number of consecutive denials before the breaker trips. */\nconst DENIAL_THRESHOLD = 3;\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Create a denial tracker.\n *\n * One instance per session — it tracks user behavior across\n * multiple tool invocations within a single turn.\n */\nexport function createDenialTracker(): DenialTracker {\n let consecutive = 0;\n\n const tracker: DenialTracker = {\n get consecutiveDenials() {\n return consecutive;\n },\n\n recordDenial(): void {\n consecutive++;\n },\n\n recordApproval(): void {\n consecutive = 0;\n },\n\n isTripped(): boolean {\n return consecutive >= DENIAL_THRESHOLD;\n },\n\n resetTurn(): void {\n consecutive = 0;\n },\n };\n\n return tracker;\n}\n","/**\n * Permission rule source loading — reads rules from each of the\n * 7 priority layers and feeds them into the RuleEngine.\n *\n * Sources (lowest to highest priority):\n * 0. blocklist — hardcoded (loaded by RuleEngine at construction)\n * 1. builtin — default safety rules\n * 2. plugin — plugin‑declared rules\n * 3. config — ~/.lynx/config.json permissions.allow/deny\n * 4. workspace — .lynx/permissions.json in the session workspace\n * 5. project — directive files (CLAUDE.md)\n * 6. user — ~/.lynx/settings.json\n * 7. session — runtime overrides (set via /permissions command)\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { RuleEngine, PermissionRule, RuleSource } from \"./rules.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface RulesLoader {\n /** Load all rules from a source into the engine. */\n load(source: RuleSource, rules: PermissionRule[]): void;\n /** Reload rules from a source (clears previous entries). */\n reload(source: RuleSource, rules: PermissionRule[]): void;\n}\n\n// ── Built‑in defaults ────────────────────────────────\n\nconst BUILTIN_RULES: PermissionRule[] = [\n { glob: \"read_file\", allow: true, reason: \"Read‑only file access\" },\n { glob: \"write_file\", allow: true, reason: \"Workspace‑scoped file write\" },\n { glob: \"glob\", allow: true, reason: \"File pattern matching\" },\n { glob: \"grep\", allow: true, reason: \"Content search\" },\n { glob: \"ls\", allow: true, reason: \"Directory listing\" },\n { glob: \"bash\", allow: true, reason: \"Shell access (permission‑gated)\" },\n { glob: \"rm\", deny: true, reason: \"File deletion requires confirmation\" },\n];\n\n// ── Permission string parser ────────────────────────\n\n/** 常见工具名映射(config.json 友好名 → 实际工具名)。 */\nconst TOOL_NAME_MAP: Record<string, string> = {\n Bash: \"bash\",\n Read: \"read_file\",\n Write: \"write_file\",\n Edit: \"edit\",\n Glob: \"glob\",\n Grep: \"grep\",\n Web: \"web_fetch\",\n WebSearch: \"web_search\",\n Task: \"task\",\n Ask: \"ask_user_question\",\n};\n\n/**\n * 解析 config.json 权限条目。\n *\n * 格式:\n * \"ToolName\" → { glob: \"tool_name\", allow: true/false }\n * \"ToolName(args)\" → { glob: \"tool_name\", args: \"args\", allow: true/false }\n *\n * 友好名(如 Bash、Read)会自动映射为实际工具名。\n */\nfunction parsePermissionEntry(raw: string, allow: boolean): PermissionRule | null {\n const match = raw.match(/^(\\w+)(?:\\((.+)\\))?$/);\n if (!match) return null;\n\n const friendlyName = match[1]!;\n const toolName = TOOL_NAME_MAP[friendlyName] ?? friendlyName.toLowerCase();\n const args = match[2]?.trim();\n\n const rule: PermissionRule = { glob: toolName, allow };\n if (args) rule.args = args;\n return rule;\n}\n\n/**\n * 从 config.json 的 permissions.allow/deny 解析权限规则。\n * 返回解析出的规则数组;解析失败静默跳过。\n */\nfunction parseConfigPermissions(raw: unknown): PermissionRule[] {\n if (!raw || typeof raw !== \"object\") return [];\n\n const perms = raw as Record<string, unknown>;\n const allowEntries = Array.isArray(perms.allow) ? (perms.allow as string[]) : [];\n const denyEntries = Array.isArray(perms.deny) ? (perms.deny as string[]) : [];\n\n const rules: PermissionRule[] = [];\n for (const entry of allowEntries) {\n const rule = parsePermissionEntry(entry, true);\n if (rule) rules.push(rule);\n }\n for (const entry of denyEntries) {\n const rule = parsePermissionEntry(entry, false);\n if (rule) rules.push(rule);\n }\n return rules;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Create a rules loader that populates a RuleEngine from\n * configuration sources.\n *\n * Call load() for each source at startup, then reload() if\n * the user edits their settings mid‑session.\n */\nexport function createRulesLoader(engine: RuleEngine): RulesLoader {\n const loader: RulesLoader = {\n load(source: RuleSource, rules: PermissionRule[]): void {\n for (const rule of rules) {\n engine.add(source, rule);\n }\n },\n\n reload(source: RuleSource, rules: PermissionRule[]): void {\n engine.clearSource(source);\n loader.load(source, rules);\n },\n };\n\n // Load built‑in defaults immediately\n engine.clearSource(\"builtin\");\n for (const rule of BUILTIN_RULES) {\n engine.add(\"builtin\", rule);\n }\n\n return loader;\n}\n\n/**\n * Load permission rules from the user's config directory.\n *\n * Reads two files:\n * - ~/.lynx/settings.json → `permissions.rules` array\n * - <workspace>/.lynx/permissions.json → top‑level rules array\n *\n * Parsing errors are silently ignored (don't block startup).\n *\n * @returns Number of rules loaded, or 0 if no files found.\n */\nexport function loadFromDisk(loader: RulesLoader, workspace?: string): number {\n let loaded = 0;\n\n // 1. User‑level settings (~/.lynx/settings.json)\n const userSettingsPath = join(homedir(), \".lynx\", \"settings.json\");\n try {\n if (existsSync(userSettingsPath)) {\n const raw = readFileSync(userSettingsPath, \"utf-8\");\n const settings = JSON.parse(raw) as {\n permissions?: { rules?: PermissionRule[] };\n };\n const rules = settings?.permissions?.rules;\n if (Array.isArray(rules) && rules.length > 0) {\n loader.reload(\"user\", rules);\n loaded += rules.length;\n }\n }\n } catch {\n // Corrupt file or permission denied — don't block startup\n }\n\n // 2. Config‑level permissions (~/.lynx/config.json → permissions.allow/deny)\n const configPath = join(homedir(), \".lynx\", \"config.json\");\n try {\n if (existsSync(configPath)) {\n const raw = readFileSync(configPath, \"utf-8\");\n const cfg = JSON.parse(raw) as { permissions?: { allow?: string[]; deny?: string[] } };\n const configRules = parseConfigPermissions(cfg.permissions ?? {});\n if (configRules.length > 0) {\n loader.reload(\"config\", configRules);\n loaded += configRules.length;\n }\n }\n } catch {\n // Corrupt config — don't block startup\n }\n\n // 3. Project‑level settings (<workspace>/.lynx/permissions.json)\n if (workspace) {\n const projectSettingsPath = join(workspace, \".lynx\", \"permissions.json\");\n try {\n if (existsSync(projectSettingsPath)) {\n const raw = readFileSync(projectSettingsPath, \"utf-8\");\n const settings = JSON.parse(raw) as { rules?: PermissionRule[] };\n const rules = settings?.rules;\n if (Array.isArray(rules) && rules.length > 0) {\n loader.reload(\"workspace\", rules);\n loaded += rules.length;\n }\n }\n } catch {\n // Corrupt file or permission denied — don't block startup\n }\n }\n\n return loaded;\n}\n","/**\n * MCP 频道权限 — 按频道限制可用的 MCP 服务器和工具。\n *\n * 每个频道可配置允许的服务器列表和工具列表,\n * 以及是否需要用户批准。未匹配频道时回退到默认策略。\n */\n\n// ── Types ────────────────────────────────────────────\n\n/** MCP 频道权限策略 — 每条规则绑定一个频道。 */\nexport interface McpChannelPolicy {\n /** 频道名称(\"wechat\"、\"feishu\"、\"web\" 等)。 */\n channelName: string;\n /** 允许的 MCP 服务器名称列表。 */\n allowedServers: string[];\n /** 允许的工具名列表(原始名称,非命名空间格式)。 */\n allowedTools: string[];\n /** 是否需要用户批准。 */\n requireApproval: boolean;\n}\n\n/** 权限检查结果。 */\nexport interface McpCheckResult {\n allowed: boolean;\n reason: string;\n}\n\n// ── Constants ──────────────────────────────────────────\n\n/**\n * 默认策略 — 无限制(所有频道可访问所有 MCP 工具)。\n *\n * 适用于内部工具和未明确配置频道的场景。\n */\nexport const DEFAULT_MCP_CHANNEL_POLICY: McpChannelPolicy[] = [];\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 创建 MCP 频道权限检查器。\n *\n * 返回一个函数:给定频道名和工具名,返回是否允许及原因。\n * 检查逻辑:\n * 1. 如果 channel 为 undefined,使用默认策略(全部允许)\n * 2. 查找匹配 channelName 的策略\n * 3. 未找到匹配策略 → 回退到第一个匹配的频道或拒绝\n * 4. 找到匹配策略 → 检查 server 是否在 allowedServers 中\n * 5. 检查 toolName 是否在 allowedTools 中(或 allowedTools 包含 \"*\")\n * 6. 全部通过 → 返回 allowed: true,附带是否需要批准的信息\n *\n * @param policies - 频道权限策略列表\n * @returns 权限检查函数\n */\nexport function createMcpChannelChecker(\n policies: McpChannelPolicy[],\n): (channel: string | undefined, toolName: string, serverName?: string) => McpCheckResult {\n return function checkChannel(\n channel: string | undefined,\n toolName: string,\n serverName?: string,\n ): McpCheckResult {\n // 未指定频道 → 默认允许\n if (!channel) {\n return { allowed: true, reason: \"无频道限制(channel 未指定)\" };\n }\n\n // 查找匹配策略\n const policy = policies.find((p) => p.channelName === channel);\n\n if (!policy) {\n // 无匹配策略 — 默认拒绝(安全优先)\n return {\n allowed: false,\n reason: `频道 \"${channel}\" 未配置 MCP 权限策略`,\n };\n }\n\n // 检查服务器\n if (serverName && policy.allowedServers.length > 0) {\n const serverAllowed = policy.allowedServers.some((s) => s === serverName || s === \"*\");\n if (!serverAllowed) {\n return {\n allowed: false,\n reason: `MCP 服务器 \"${serverName}\" 不在频道 \"${channel}\" 的允许列表中`,\n };\n }\n }\n\n // 检查工具\n const toolAllowed = policy.allowedTools.some((t) => t === toolName || t === \"*\");\n if (!toolAllowed) {\n return {\n allowed: false,\n reason: `工具 \"${toolName}\" 不在频道 \"${channel}\" 的允许列表中`,\n };\n }\n\n const approvalNote = policy.requireApproval ? \"(需要用户批准)\" : \"\";\n return {\n allowed: true,\n reason: `频道 \"${channel}\" 允许使用 \"${toolName}\"${approvalNote}`,\n };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/modes.ts","../src/rules.ts","../src/pipeline.ts","../src/classifier.ts","../src/denial.ts","../src/sources.ts","../src/mcp-channel.ts"],"sourcesContent":["/**\n * Permission modes — the 6 operating modes that control how\n * tool execution permissions are resolved.\n *\n * default — ask user for every RequiresApproval tool\n * auto — LLM YOLO classifier decides, user can override\n * yolo — auto‑approve everything, max throughput\n * dontAsk — deny all RequiresApproval tools silently\n * headless — no TUI, auto‑deny interactive prompts\n * plan — read‑only mode, all mutating tools denied\n */\n\n// ── Types ────────────────────────────────────────────\n\nexport type PermissionMode = \"default\" | \"auto\" | \"yolo\" | \"dontAsk\" | \"headless\" | \"plan\";\n\nexport interface ModeBehavior {\n /** Auto‑approve Safe tools without asking. */\n autoApproveSafe: boolean;\n /** Auto‑approve WorkspaceSafe tools. */\n autoApproveWorkspaceSafe: boolean;\n /** Auto‑deny Dangerous tools. */\n autoDenyDangerous: boolean;\n /** Use LLM classifier for RequiresApproval tools. */\n useClassifier: boolean;\n /** Show interactive prompts to the user. */\n showPrompts: boolean;\n /** Allow any file‑modifying tools. */\n allowWrites: boolean;\n}\n\n// ── Mode table ───────────────────────────────────────\n\nconst MODE_TABLE: Record<PermissionMode, ModeBehavior> = {\n default: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: true,\n allowWrites: true,\n },\n auto: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: true,\n autoDenyDangerous: true,\n useClassifier: true,\n showPrompts: false,\n allowWrites: true,\n },\n yolo: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: true,\n autoDenyDangerous: false,\n useClassifier: false,\n showPrompts: false,\n allowWrites: true,\n },\n dontAsk: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: false,\n allowWrites: false,\n },\n headless: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: false,\n allowWrites: true,\n },\n plan: {\n autoApproveSafe: true,\n autoApproveWorkspaceSafe: false,\n autoDenyDangerous: true,\n useClassifier: false,\n showPrompts: false,\n allowWrites: false,\n },\n};\n\n// ── Public API ───────────────────────────────────────\n\n/** Get the behavior profile for a given mode. */\nexport function getModeBehavior(mode: PermissionMode): ModeBehavior {\n return MODE_TABLE[mode];\n}\n\n/** List all valid mode names. */\nexport function listModes(): PermissionMode[] {\n return Object.keys(MODE_TABLE) as PermissionMode[];\n}\n\n/** Validate that a string is a recognized mode. */\nexport function isValidMode(value: string): value is PermissionMode {\n return value in MODE_TABLE;\n}\n","/**\n * Permission rule matching engine.\n *\n * Rules are evaluated in priority order from multiple sources:\n * 1. Session overrides (highest)\n * 2. User settings\n * 3. Project settings\n * 4. Workspace settings\n * 5. Plugin defaults\n * 6. Built‑in defaults\n * 7. Hardcoded blocklist (lowest — always evaluated first)\n *\n * Each rule has a glob pattern that matches tool names.\n * When multiple rules match, higher‑priority sources win.\n */\n\nimport type { CommandSafety } from \"@24klynx/tools\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface PermissionRule {\n /** Glob pattern matching tool names (e.g. \"bash\", \"read_*\", \"*\"). */\n glob: string;\n /** Optional argument pattern for tool‑specific rules (e.g. \"git push *\"). */\n args?: string;\n /** Override the safety classification for matched tools. */\n safety?: CommandSafety;\n /** Explicitly allow matched tools. */\n allow?: boolean;\n /** Explicitly deny matched tools. */\n deny?: boolean;\n /** Human‑readable reason shown to the user. */\n reason?: string;\n}\n\nexport interface RuleMatch {\n rule: PermissionRule;\n source: RuleSource;\n}\n\nexport type RuleSource =\n | \"blocklist\" // 0 — hardcoded, always deny\n | \"builtin\" // 1 — built‑in defaults\n | \"plugin\" // 2 — plugin‑provided rules\n | \"config\" // 3 — ~/.lynx/config.json permissions.allow/deny\n | \"workspace\" // 4 — .lynx/permissions.json in the workspace\n | \"project\" // 5 — CLAUDE.md project settings\n | \"user\" // 6 — ~/.lynx/settings.json\n | \"session\"; // 7 — runtime session override\n\n/** Priority weight — higher number = higher priority. */\nconst SOURCE_PRIORITY: Record<RuleSource, number> = {\n blocklist: 0,\n builtin: 1,\n plugin: 2,\n config: 3,\n workspace: 4,\n project: 5,\n user: 6,\n session: 7,\n};\n\n// ── Glob matching ────────────────────────────────────\n\nfunction globMatch(pattern: string, value: string): boolean {\n // Simple wildcard matching: * matches any sequence, ? matches one char\n const regex = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*/g, \".*\")\n .replace(/\\?/g, \".\");\n return new RegExp(`^${regex}$`).test(value);\n}\n\n// ── Public API ───────────────────────────────────────\n\nexport interface RuleEngine {\n /** Register a rule from a specific source. */\n add(source: RuleSource, rule: PermissionRule): void;\n\n /** Remove all rules from a source. */\n clearSource(source: RuleSource): void;\n\n /** Find all rules that match a tool name, sorted by priority (highest first). */\n match(toolName: string): RuleMatch[];\n\n /** Check if any matching rule explicitly denies the tool. */\n isDenied(toolName: string): boolean;\n\n /** Check if any matching rule explicitly allows the tool. */\n isAllowed(toolName: string): boolean;\n}\n\n/**\n * Create a rule matching engine.\n *\n * Rules are evaluated lazily — each match() call re‑scans\n * the rule set for the given tool name.\n */\nexport function createRuleEngine(): RuleEngine {\n const rulesBySource = new Map<RuleSource, PermissionRule[]>();\n\n // Hardcoded blocklist — always denied\n const BLOCKLIST: PermissionRule[] = [\n { glob: \"sudo\", deny: true, reason: \"Privilege escalation is forbidden\" },\n { glob: \"su\", deny: true, reason: \"Privilege escalation is forbidden\" },\n { glob: \"doas\", deny: true, reason: \"Privilege escalation is forbidden\" },\n ];\n\n // Register the blocklist immediately\n for (const rule of BLOCKLIST) {\n const existing = rulesBySource.get(\"blocklist\") ?? [];\n existing.push(rule);\n rulesBySource.set(\"blocklist\", existing);\n }\n\n const engine: RuleEngine = {\n add(source: RuleSource, rule: PermissionRule): void {\n const existing = rulesBySource.get(source) ?? [];\n existing.push(rule);\n rulesBySource.set(source, existing);\n },\n\n clearSource(source: RuleSource): void {\n rulesBySource.delete(source);\n },\n\n match(toolName: string): RuleMatch[] {\n const matches: RuleMatch[] = [];\n\n for (const [source, rules] of rulesBySource) {\n for (const rule of rules) {\n if (globMatch(rule.glob, toolName)) {\n matches.push({ rule, source });\n }\n }\n }\n\n // Sort by source priority (highest first)\n matches.sort((a, b) => SOURCE_PRIORITY[b.source] - SOURCE_PRIORITY[a.source]);\n\n return matches;\n },\n\n isDenied(toolName: string): boolean {\n const matches = this.match(toolName);\n // Higher‑priority source wins\n if (matches.length === 0) return false;\n const top = matches[0];\n return top.rule.deny === true;\n },\n\n isAllowed(toolName: string): boolean {\n const matches = this.match(toolName);\n if (matches.length === 0) return false;\n const top = matches[0];\n return top.rule.allow === true;\n },\n };\n\n return engine;\n}\n","/**\n * 14‑step permission pipeline — evaluates every tool invocation\n * through a sequence of checks, short‑circuiting on the first\n * definitive answer.\n *\n * Steps:\n * 1. Tool not found in registry → deny\n * 2. Tool is Safe → auto‑approve\n * 3. Tool is Dangerous → auto‑deny (unless yolo mode)\n * 4. Rule engine explicit deny → deny\n * 5. Rule engine explicit allow → allow\n * 6. Mode behavior check (plan mode denies writes, etc.)\n * 7. Denial tracker circuit breaker → deny\n * 8. Mode: yolo → allow\n * 9. Mode: dontAsk → deny\n * 10. Mode: headless → deny (no interactive TUI)\n * 11. Mode: auto → LLM classifier\n * 12. Mode: default → ask user\n * 13. User response → allow/deny\n * 14. Default: deny\n */\n\nimport type { ToolDescriptor, ToolInvocation } from \"@24klynx/tools\";\nimport type { PermissionMode, ModeBehavior } from \"./modes.js\";\nimport type { RuleEngine } from \"./rules.js\";\nimport type { DenialTracker } from \"./denial.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface PipelineContext {\n mode: PermissionMode;\n behavior: ModeBehavior;\n ruleEngine: RuleEngine;\n denialTracker: DenialTracker;\n /** User callback — returns true to allow, false to deny. */\n askUser?: (invocation: ToolInvocation) => Promise<boolean>;\n /** LLM classifier callback — returns true to allow. */\n classify?: (invocation: ToolInvocation) => Promise<boolean>;\n}\n\nexport interface PipelineResult {\n allowed: boolean;\n reason: string;\n step: number;\n /** If true, the result was determined without user interaction. */\n automatic: boolean;\n}\n\n// ── Step function type ────────────────────────────────\n\n/** A single pipeline step. Returns a result to short‑circuit, or null to continue. */\ntype StepFn = (\n tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n) => Promise<PipelineResult | null> | PipelineResult | null;\n\n// ── Step implementations ──────────────────────────────\n\nfunction step1_unknownTool(tool: ToolDescriptor): PipelineResult | null {\n if (!tool) {\n return { allowed: false, reason: \"未知工具\", step: 1, automatic: true };\n }\n return null;\n}\n\nfunction step2_safeTool(tool: ToolDescriptor): PipelineResult | null {\n if (tool.safety === \"Safe\") {\n return { allowed: true, reason: \"安全工具 — 始终允许\", step: 2, automatic: true };\n }\n return null;\n}\n\nfunction step3_dangerousTool(\n tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (tool.safety === \"Dangerous\" && ctx.behavior.autoDenyDangerous) {\n return { allowed: false, reason: \"危险工具 — 已自动拒绝\", step: 3, automatic: true };\n }\n return null;\n}\n\nfunction step4_ruleDeny(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.ruleEngine.isDenied(invocation.toolName)) {\n return { allowed: false, reason: \"被权限规则拒绝\", step: 4, automatic: true };\n }\n return null;\n}\n\nfunction step5_ruleAllow(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.ruleEngine.isAllowed(invocation.toolName)) {\n return { allowed: true, reason: \"被权限规则允许\", step: 5, automatic: true };\n }\n return null;\n}\n\nfunction step6_planModeDeniesWrites(\n tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"plan\" && tool.kind !== \"ReadOnly\") {\n return {\n allowed: false,\n reason: `写入工具 \"${tool.name}\" 在计划模式下被拒绝`,\n step: 6,\n automatic: true,\n };\n }\n return null;\n}\n\nfunction step7_circuitBreaker(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.denialTracker.isTripped()) {\n return {\n allowed: false,\n reason: \"熔断器已触发 — 自动拒绝\",\n step: 7,\n automatic: true,\n };\n }\n return null;\n}\n\nfunction step8_yolo(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"yolo\") {\n return { allowed: true, reason: \"YOLO 模式 — 已自动批准\", step: 8, automatic: true };\n }\n return null;\n}\n\nfunction step9_dontAsk(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"dontAsk\") {\n return { allowed: false, reason: \"免问模式 — 已自动拒绝\", step: 9, automatic: true };\n }\n return null;\n}\n\nfunction step10_headless(\n _tool: ToolDescriptor,\n _inv: ToolInvocation,\n ctx: PipelineContext,\n): PipelineResult | null {\n if (ctx.mode === \"headless\") {\n return {\n allowed: false,\n reason: \"无头模式 — 无交互式提示\",\n step: 10,\n automatic: true,\n };\n }\n return null;\n}\n\nasync function step11_autoClassify(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): Promise<PipelineResult | null> {\n if (ctx.mode !== \"auto\" || !ctx.classify) return null;\n try {\n const allowed = await ctx.classify(invocation);\n return {\n allowed,\n reason: allowed ? \"自动分类为安全\" : \"自动分类为不安全\",\n step: 11,\n automatic: true,\n };\n } catch {\n return null; // Classifier failed — fall through\n }\n}\n\nasync function step12_defaultAskUser(\n _tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): Promise<PipelineResult | null> {\n if (ctx.mode !== \"default\") return null;\n if (!ctx.askUser) {\n return {\n allowed: false,\n reason: \"默认模式 — 无用户提示可用\",\n step: 12,\n automatic: true,\n };\n }\n try {\n const allowed = await ctx.askUser(invocation);\n return {\n allowed,\n reason: allowed ? \"用户已批准\" : \"用户已拒绝\",\n step: 13,\n automatic: false,\n };\n } catch {\n return { allowed: false, reason: \"用户提示失败\", step: 13, automatic: false };\n }\n}\n\n/** 兜底步骤 — 始终返回结果。 */\nfunction step14_defaultDeny(): PipelineResult {\n return { allowed: false, reason: \"默认拒绝 — 无规则匹配\", step: 14, automatic: true };\n}\n\n// ── Ordered pipeline ──────────────────────────────────\n\nconst PIPELINE: StepFn[] = [\n step1_unknownTool,\n step2_safeTool,\n step3_dangerousTool,\n step4_ruleDeny,\n step5_ruleAllow,\n step6_planModeDeniesWrites,\n step7_circuitBreaker,\n step8_yolo,\n step9_dontAsk,\n step10_headless,\n step11_autoClassify,\n step12_defaultAskUser,\n step14_defaultDeny,\n];\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Run a tool invocation through the 14‑step permission pipeline.\n *\n * Each step is an independent function. The pipeline short‑circuits\n * on the first non‑null result.\n */\nexport async function runPipeline(\n tool: ToolDescriptor,\n invocation: ToolInvocation,\n ctx: PipelineContext,\n): Promise<PipelineResult> {\n for (const step of PIPELINE) {\n const result = await step(tool, invocation, ctx);\n if (result) return result;\n }\n // 不可达 — step14 始终返回结果\n return { allowed: false, reason: \"默认拒绝 — 无规则匹配\", step: 14, automatic: true };\n}\n","/**\n * YOLO classifier — a lightweight LLM‑based permission decider\n * for \"auto\" mode. Uses a ~200‑token prompt and expects a\n * yes/no response within 500ms.\n *\n * Pattern (inspired by FengMing's `classifyPermissionTool` +\n * Claude Code's auto‑mode classifier):\n * 1. Check cache → return immediately if hit\n * 2. Call fast LLM with compact prompt → allow/deny\n * 3. On timeout or error → fall back to heuristic\n *\n * The classifier is stateless — each call is independent.\n * It should NOT be used for Dangerous tools (those are\n * auto‑denied regardless of classifier output).\n */\n\nimport type { ToolInvocation, ToolDescriptor } from \"@24klynx/tools\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface YoloClassifier {\n /**\n * Ask the classifier whether a tool invocation should be allowed.\n *\n * Returns true if the invocation looks safe, false otherwise.\n * Never throws — on error, falls back to a conservative heuristic.\n */\n classify(tool: ToolDescriptor, invocation: ToolInvocation): Promise<boolean>;\n}\n\n/** LLM completion function signature — injected via DI. */\nexport type ClassifyLlmCall = (prompt: string) => Promise<string>;\n\n/** Options for creating the classifier. */\nexport interface CreateYoloClassifierOptions {\n /**\n * Optional LLM completion function.\n *\n * When provided, the classifier sends a ~200‑token prompt to a\n * fast model (e.g. Haiku / Flash) and parses \"allow\" / \"deny\".\n * When omitted, falls back to the heuristic classifier only.\n */\n llmCall?: ClassifyLlmCall;\n\n /** Timeout in milliseconds for the LLM call. Default 500. */\n timeoutMs?: number;\n}\n\n// ── Constants ────────────────────────────────────────\n\nconst DEFAULT_TIMEOUT_MS = 500;\n\n/** Compact prompt template (~200 tokens). */\nconst CLASSIFIER_PROMPT = `You are a security classifier. Given a tool invocation, respond with ONLY \"allow\" or \"deny\".\n\nRules:\n- Read-only operations (read_file, ls, grep, search) → allow\n- File writes within the workspace → allow\n- Network requests → allow\n- Shell commands that modify system state → deny\n- Commands that delete files outside the workspace → deny\n- Commands that install packages → deny\n- Escalation commands (sudo, su) → deny\n\nTool: {toolName}\nDescription: {toolDescription}\nInput: {toolInput}\n\nResponse:`;\n\n// ── Heuristic fallback ───────────────────────────────\n\n/** Patterns that always result in deny. */\nconst DENY_PATTERNS = [\n \"sudo\",\n \"rm -rf /\",\n \"chmod 777\",\n \"DROP TABLE\",\n \"DELETE FROM\",\n \"shutdown\",\n \"reboot\",\n \"format\",\n \"mkfs\",\n \"curl | bash\",\n \"wget -O - |\",\n \"> /dev/sda\",\n \"dd if=\",\n] as const;\n\n/** Patterns that indicate safe dev workflows. */\nconst ALLOW_PATTERNS = [\n \"npm test\",\n \"npm run\",\n \"pnpm test\",\n \"pnpm build\",\n \"git \",\n \"node \",\n \"tsc\",\n \"vitest\",\n \"eslint\",\n \"prettier\",\n] as const;\n\nfunction heuristicClassify(tool: ToolDescriptor, invocation: ToolInvocation): boolean {\n // ReadOnly tools are always safe\n if (tool.kind === \"ReadOnly\") return true;\n\n // Check for dangerous patterns in the payload\n const payload = JSON.stringify(invocation.payload).toLowerCase();\n\n for (const pattern of DENY_PATTERNS) {\n if (payload.includes(pattern.toLowerCase())) {\n return false;\n }\n }\n\n for (const pattern of ALLOW_PATTERNS) {\n if (payload.includes(pattern.toLowerCase())) {\n return true;\n }\n }\n\n // Conservative default\n return tool.safety === \"Safe\";\n}\n\n// ── Cache ────────────────────────────────────────────\n\n/** Cache key derived from tool identity + input hash. */\nfunction cacheKey(tool: ToolDescriptor, invocation: ToolInvocation): string {\n const inputHash = JSON.stringify(invocation.payload);\n return `${tool.name}:${tool.safety}:${inputHash}`;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Create a YOLO classifier.\n *\n * When `llmCall` is provided, prompts a fast LLM to decide each\n * classification. A cache avoids redundant calls for identical\n * (tool, safety, args) combinations within the same session.\n *\n * Falls back to a heuristic classifier on timeout or error.\n */\nexport function createYoloClassifier(opts: CreateYoloClassifierOptions = {}): YoloClassifier {\n const { llmCall, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;\n\n // Session‑scoped cache\n const cache = new Map<string, boolean>();\n\n const classifier: YoloClassifier = {\n async classify(tool: ToolDescriptor, invocation: ToolInvocation): Promise<boolean> {\n // 1. Check cache\n const key = cacheKey(tool, invocation);\n const cached = cache.get(key);\n if (cached !== undefined) return cached;\n\n // 2. Try LLM classifier\n if (llmCall) {\n try {\n const prompt = CLASSIFIER_PROMPT.replace(\"{toolName}\", tool.name)\n .replace(\"{toolDescription}\", tool.description)\n .replace(\"{toolInput}\", JSON.stringify(invocation.payload));\n\n const raw = await Promise.race([\n llmCall(prompt),\n new Promise<string>((_, reject) =>\n setTimeout(() => reject(new Error(\"Classifier timeout\")), timeoutMs),\n ),\n ]);\n\n const decision = raw.trim().toLowerCase().startsWith(\"allow\");\n cache.set(key, decision);\n return decision;\n } catch {\n // Timeout or LLM error → fall through to heuristic\n }\n }\n\n // 3. Heuristic fallback\n const decision = heuristicClassify(tool, invocation);\n cache.set(key, decision);\n return decision;\n },\n };\n\n return classifier;\n}\n","/**\n * Denial tracker with circuit breaker.\n *\n * When a user denies 3 consecutive tool requests within the same\n * turn, the circuit breaker trips and auto‑denies all subsequent\n * requests for the rest of that turn.\n *\n * The breaker resets at the start of each new turn.\n */\n\n// ── Types ────────────────────────────────────────────\n\nexport interface DenialTracker {\n /** Record a denial from the user. */\n recordDenial(): void;\n /** Record an approval (resets the consecutive count). */\n recordApproval(): void;\n /** Check whether the circuit breaker has tripped. */\n isTripped(): boolean;\n /** Reset the tracker for a new turn. */\n resetTurn(): void;\n /** Current consecutive denial count. */\n readonly consecutiveDenials: number;\n}\n\n// ── Constants ────────────────────────────────────────\n\n/** Number of consecutive denials before the breaker trips. */\nconst DENIAL_THRESHOLD = 3;\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Create a denial tracker.\n *\n * One instance per session — it tracks user behavior across\n * multiple tool invocations within a single turn.\n */\nexport function createDenialTracker(): DenialTracker {\n let consecutive = 0;\n\n const tracker: DenialTracker = {\n get consecutiveDenials() {\n return consecutive;\n },\n\n recordDenial(): void {\n consecutive++;\n },\n\n recordApproval(): void {\n consecutive = 0;\n },\n\n isTripped(): boolean {\n return consecutive >= DENIAL_THRESHOLD;\n },\n\n resetTurn(): void {\n consecutive = 0;\n },\n };\n\n return tracker;\n}\n","/**\n * Permission rule source loading — reads rules from each of the\n * 7 priority layers and feeds them into the RuleEngine.\n *\n * Sources (lowest to highest priority):\n * 0. blocklist — hardcoded (loaded by RuleEngine at construction)\n * 1. builtin — default safety rules\n * 2. plugin — plugin‑declared rules\n * 3. config — ~/.lynx/config.json permissions.allow/deny\n * 4. workspace — .lynx/permissions.json in the session workspace\n * 5. project — directive files (CLAUDE.md)\n * 6. user — ~/.lynx/settings.json\n * 7. session — runtime overrides (set via /permissions command)\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport type { RuleEngine, PermissionRule, RuleSource } from \"./rules.js\";\n\n// ── Types ────────────────────────────────────────────\n\nexport interface RulesLoader {\n /** Load all rules from a source into the engine. */\n load(source: RuleSource, rules: PermissionRule[]): void;\n /** Reload rules from a source (clears previous entries). */\n reload(source: RuleSource, rules: PermissionRule[]): void;\n}\n\n// ── Built‑in defaults ────────────────────────────────\n\nconst BUILTIN_RULES: PermissionRule[] = [\n { glob: \"read_file\", allow: true, reason: \"Read‑only file access\" },\n { glob: \"write_file\", allow: true, reason: \"Workspace‑scoped file write\" },\n { glob: \"glob\", allow: true, reason: \"File pattern matching\" },\n { glob: \"grep\", allow: true, reason: \"Content search\" },\n { glob: \"ls\", allow: true, reason: \"Directory listing\" },\n { glob: \"bash\", allow: true, reason: \"Shell access (permission‑gated)\" },\n { glob: \"rm\", deny: true, reason: \"File deletion requires confirmation\" },\n];\n\n// ── Permission string parser ────────────────────────\n\n/** 常见工具名映射(config.json 友好名 → 实际工具名)。 */\nconst TOOL_NAME_MAP: Record<string, string> = {\n Bash: \"bash\",\n Read: \"read_file\",\n Write: \"write_file\",\n Edit: \"edit\",\n Glob: \"glob\",\n Grep: \"grep\",\n Web: \"web_fetch\",\n WebSearch: \"web_search\",\n Task: \"task\",\n Ask: \"ask_user_question\",\n};\n\n/**\n * 解析 config.json 权限条目。\n *\n * 格式:\n * \"ToolName\" → { glob: \"tool_name\", allow: true/false }\n * \"ToolName(args)\" → { glob: \"tool_name\", args: \"args\", allow: true/false }\n *\n * 友好名(如 Bash、Read)会自动映射为实际工具名。\n */\nfunction parsePermissionEntry(raw: string, allow: boolean): PermissionRule | null {\n const match = raw.match(/^(\\w+)(?:\\((.+)\\))?$/);\n if (!match) return null;\n\n const friendlyName = match[1]!;\n const toolName = TOOL_NAME_MAP[friendlyName] ?? friendlyName.toLowerCase();\n const args = match[2]?.trim();\n\n const rule: PermissionRule = { glob: toolName, allow };\n if (args) rule.args = args;\n return rule;\n}\n\n/**\n * 从 config.json 的 permissions.allow/deny 解析权限规则。\n * 返回解析出的规则数组;解析失败静默跳过。\n */\nfunction parseConfigPermissions(raw: unknown): PermissionRule[] {\n if (!raw || typeof raw !== \"object\") return [];\n\n const perms = raw as Record<string, unknown>;\n const allowEntries = Array.isArray(perms.allow) ? (perms.allow as string[]) : [];\n const denyEntries = Array.isArray(perms.deny) ? (perms.deny as string[]) : [];\n\n const rules: PermissionRule[] = [];\n for (const entry of allowEntries) {\n const rule = parsePermissionEntry(entry, true);\n if (rule) rules.push(rule);\n }\n for (const entry of denyEntries) {\n const rule = parsePermissionEntry(entry, false);\n if (rule) rules.push(rule);\n }\n return rules;\n}\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * Create a rules loader that populates a RuleEngine from\n * configuration sources.\n *\n * Call load() for each source at startup, then reload() if\n * the user edits their settings mid‑session.\n */\nexport function createRulesLoader(engine: RuleEngine): RulesLoader {\n const loader: RulesLoader = {\n load(source: RuleSource, rules: PermissionRule[]): void {\n for (const rule of rules) {\n engine.add(source, rule);\n }\n },\n\n reload(source: RuleSource, rules: PermissionRule[]): void {\n engine.clearSource(source);\n loader.load(source, rules);\n },\n };\n\n // Load built‑in defaults immediately\n engine.clearSource(\"builtin\");\n for (const rule of BUILTIN_RULES) {\n engine.add(\"builtin\", rule);\n }\n\n return loader;\n}\n\n/**\n * Load permission rules from the user's config directory.\n *\n * Reads two files:\n * - ~/.lynx/settings.json → `permissions.rules` array\n * - <workspace>/.lynx/permissions.json → top‑level rules array\n *\n * Parsing errors are silently ignored (don't block startup).\n *\n * @returns Number of rules loaded, or 0 if no files found.\n */\nexport function loadFromDisk(loader: RulesLoader, workspace?: string): number {\n let loaded = 0;\n\n // 1. User‑level settings (~/.lynx/settings.json)\n const userSettingsPath = join(homedir(), \".lynx\", \"settings.json\");\n try {\n if (existsSync(userSettingsPath)) {\n const raw = readFileSync(userSettingsPath, \"utf-8\");\n const settings = JSON.parse(raw) as {\n permissions?: { rules?: PermissionRule[] };\n };\n const rules = settings?.permissions?.rules;\n if (Array.isArray(rules) && rules.length > 0) {\n loader.reload(\"user\", rules);\n loaded += rules.length;\n }\n }\n } catch {\n // Corrupt file or permission denied — don't block startup\n }\n\n // 2. Config‑level permissions (~/.lynx/config.json → permissions.allow/deny)\n const configPath = join(homedir(), \".lynx\", \"config.json\");\n try {\n if (existsSync(configPath)) {\n const raw = readFileSync(configPath, \"utf-8\");\n const cfg = JSON.parse(raw) as { permissions?: { allow?: string[]; deny?: string[] } };\n const configRules = parseConfigPermissions(cfg.permissions ?? {});\n if (configRules.length > 0) {\n loader.reload(\"config\", configRules);\n loaded += configRules.length;\n }\n }\n } catch {\n // Corrupt config — don't block startup\n }\n\n // 3. Project‑level settings (<workspace>/.lynx/permissions.json)\n if (workspace) {\n const projectSettingsPath = join(workspace, \".lynx\", \"permissions.json\");\n try {\n if (existsSync(projectSettingsPath)) {\n const raw = readFileSync(projectSettingsPath, \"utf-8\");\n const settings = JSON.parse(raw) as { rules?: PermissionRule[] };\n const rules = settings?.rules;\n if (Array.isArray(rules) && rules.length > 0) {\n loader.reload(\"workspace\", rules);\n loaded += rules.length;\n }\n }\n } catch {\n // Corrupt file or permission denied — don't block startup\n }\n }\n\n return loaded;\n}\n","/**\n * MCP 频道权限 — 按频道限制可用的 MCP 服务器和工具。\n *\n * 每个频道可配置允许的服务器列表和工具列表,\n * 以及是否需要用户批准。未匹配频道时回退到默认策略。\n */\n\n// ── Types ────────────────────────────────────────────\n\n/** MCP 频道权限策略 — 每条规则绑定一个频道。 */\nexport interface McpChannelPolicy {\n /** 频道名称(\"wechat\"、\"feishu\"、\"web\" 等)。 */\n channelName: string;\n /** 允许的 MCP 服务器名称列表。 */\n allowedServers: string[];\n /** 允许的工具名列表(原始名称,非命名空间格式)。 */\n allowedTools: string[];\n /** 是否需要用户批准。 */\n requireApproval: boolean;\n}\n\n/** 权限检查结果。 */\nexport interface McpCheckResult {\n allowed: boolean;\n reason: string;\n}\n\n// ── Constants ──────────────────────────────────────────\n\n/**\n * 默认策略 — 无限制(所有频道可访问所有 MCP 工具)。\n *\n * 适用于内部工具和未明确配置频道的场景。\n */\nexport const DEFAULT_MCP_CHANNEL_POLICY: McpChannelPolicy[] = [];\n\n// ── Public API ───────────────────────────────────────\n\n/**\n * 创建 MCP 频道权限检查器。\n *\n * 返回一个函数:给定频道名和工具名,返回是否允许及原因。\n * 检查逻辑:\n * 1. 如果 channel 为 undefined,使用默认策略(全部允许)\n * 2. 查找匹配 channelName 的策略\n * 3. 未找到匹配策略 → 回退到第一个匹配的频道或拒绝\n * 4. 找到匹配策略 → 检查 server 是否在 allowedServers 中\n * 5. 检查 toolName 是否在 allowedTools 中(或 allowedTools 包含 \"*\")\n * 6. 全部通过 → 返回 allowed: true,附带是否需要批准的信息\n *\n * @param policies - 频道权限策略列表\n * @returns 权限检查函数\n */\nexport function createMcpChannelChecker(\n policies: McpChannelPolicy[],\n): (channel: string | undefined, toolName: string, serverName?: string) => McpCheckResult {\n return function checkChannel(\n channel: string | undefined,\n toolName: string,\n serverName?: string,\n ): McpCheckResult {\n // 未指定频道 → 默认允许\n if (!channel) {\n return { allowed: true, reason: \"无频道限制(channel 未指定)\" };\n }\n\n // 查找匹配策略\n const policy = policies.find((p) => p.channelName === channel);\n\n if (!policy) {\n // 无匹配策略 — 默认拒绝(安全优先)\n return {\n allowed: false,\n reason: `频道 \"${channel}\" 未配置 MCP 权限策略`,\n };\n }\n\n // 检查服务器\n if (serverName && policy.allowedServers.length > 0) {\n const serverAllowed = policy.allowedServers.some((s) => s === serverName || s === \"*\");\n if (!serverAllowed) {\n return {\n allowed: false,\n reason: `MCP 服务器 \"${serverName}\" 不在频道 \"${channel}\" 的允许列表中`,\n };\n }\n }\n\n // 检查工具\n const toolAllowed = policy.allowedTools.some((t) => t === toolName || t === \"*\");\n if (!toolAllowed) {\n return {\n allowed: false,\n reason: `工具 \"${toolName}\" 不在频道 \"${channel}\" 的允许列表中`,\n };\n }\n\n const approvalNote = policy.requireApproval ? \"(需要用户批准)\" : \"\";\n return {\n allowed: true,\n reason: `频道 \"${channel}\" 允许使用 \"${toolName}\"${approvalNote}`,\n };\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@24klynx/permissions",
3
- "version": "0.1.0",
3
+ "version": "0.1.4",
4
4
  "description": "Permission system — 14-step pipeline, rule engine, YOLO classifier, denial tracker",
5
+ "files": [
6
+ "dist"
7
+ ],
5
8
  "type": "module",
6
9
  "main": "./dist/index.mjs",
7
10
  "types": "./dist/index.d.mts",
@@ -11,16 +14,13 @@
11
14
  "types": "./dist/index.d.mts"
12
15
  }
13
16
  },
14
- "dependencies": {
15
- "@24klynx/core": "0.1.0",
16
- "@24klynx/tools": "0.1.0"
17
- },
18
- "files": [
19
- "dist"
20
- ],
21
17
  "publishConfig": {
22
18
  "access": "public"
23
19
  },
20
+ "dependencies": {
21
+ "@24klynx/core": "0.1.4",
22
+ "@24klynx/tools": "0.1.4"
23
+ },
24
24
  "scripts": {
25
25
  "build": "tsdown --config-loader tsx",
26
26
  "test": "vitest run --passWithNoTests",