@astrasyncai/verification-gateway 3.10.0 → 3.11.0
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/adapter-interface/interface.d.mts +1 -1
- package/dist/adapter-interface/interface.d.ts +1 -1
- package/dist/adapters/express.js +1 -1
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +1 -1
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.js +1 -1
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +1 -1
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.js +1 -1
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +1 -1
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.js +1 -1
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +1 -1
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +317 -35
- package/dist/bin/astrasync-codex-hook.js +5107 -0
- package/dist/bin/astrasync-guard.js +430 -40
- package/dist/bin/astrasync.js +453 -41
- package/dist/browser/background.js +279 -24
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +279 -24
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +1 -1
- package/dist/browser/browser-adapter.d.ts +1 -1
- package/dist/claude-code/claude-code-adapter.d.mts +1 -1
- package/dist/claude-code/claude-code-adapter.d.ts +1 -1
- package/dist/claude-code/claude-code-adapter.js +9 -1
- package/dist/claude-code/claude-code-adapter.js.map +1 -1
- package/dist/claude-code/claude-code-adapter.mjs +9 -1
- package/dist/claude-code/claude-code-adapter.mjs.map +1 -1
- package/dist/cli/index.d.mts +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/codex/index.d.mts +230 -0
- package/dist/codex/index.d.ts +230 -0
- package/dist/codex/index.js +5225 -0
- package/dist/codex/index.js.map +1 -0
- package/dist/codex/index.mjs +5197 -0
- package/dist/codex/index.mjs.map +1 -0
- package/dist/cursor/cursor-adapter.d.mts +1 -1
- package/dist/cursor/cursor-adapter.d.ts +1 -1
- package/dist/cursor/extension.d.mts +1 -1
- package/dist/cursor/extension.d.ts +1 -1
- package/dist/cursor/extension.js +279 -24
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +279 -24
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/gateway/gateway.d.mts +32 -2
- package/dist/gateway/gateway.d.ts +32 -2
- package/dist/gateway/gateway.js +279 -24
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +279 -24
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +1 -1
- package/dist/local-evaluator/evaluator.d.ts +1 -1
- package/dist/local-evaluator/evaluator.js +165 -7
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs +165 -7
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{types-D0lCtFQ7.d.mts → types-BV6abBnF.d.mts} +6 -0
- package/dist/{types-YbiXYKx1.d.ts → types-C23_uaY8.d.ts} +6 -0
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +1 -1
- package/dist/verify.mjs.map +1 -1
- package/package.json +8 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.mjs';
|
|
2
|
-
import { P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-
|
|
2
|
+
import { P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-BV6abBnF.mjs';
|
|
3
3
|
import '../gateway/gateway.mjs';
|
|
4
4
|
import '../types-sy3tq8lp.mjs';
|
|
5
5
|
import '../metadata-capture.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.js';
|
|
2
|
-
import { P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-
|
|
2
|
+
import { P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-C23_uaY8.js';
|
|
3
3
|
import '../gateway/gateway.js';
|
|
4
4
|
import '../types-9tns4wyl.js';
|
|
5
5
|
import '../metadata-capture.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.mjs';
|
|
2
|
-
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-
|
|
2
|
+
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-BV6abBnF.mjs';
|
|
3
3
|
import '../gateway/gateway.mjs';
|
|
4
4
|
import '../types-sy3tq8lp.mjs';
|
|
5
5
|
import '../metadata-capture.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.js';
|
|
2
|
-
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-
|
|
2
|
+
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-C23_uaY8.js';
|
|
3
3
|
import '../gateway/gateway.js';
|
|
4
4
|
import '../types-9tns4wyl.js';
|
|
5
5
|
import '../metadata-capture.js';
|
|
@@ -114,6 +114,11 @@ function extractNetworkDomains(target) {
|
|
|
114
114
|
// src/claude-code/tool-mapping.ts
|
|
115
115
|
var CLAUDE_CODE_TOOL_MAP = {
|
|
116
116
|
Bash: "shell.exec",
|
|
117
|
+
// Native Windows ships shell execution as a distinct PowerShell tool
|
|
118
|
+
// (hooks docs: "Tool Names in PreToolUse"). Found live on 10 Jul 2026 —
|
|
119
|
+
// the desktop-app runtime verification showed shell commands bypassing
|
|
120
|
+
// the guard entirely on Windows because only Bash was mapped/matched.
|
|
121
|
+
PowerShell: "shell.exec",
|
|
117
122
|
Write: "file.write",
|
|
118
123
|
Edit: "file.write",
|
|
119
124
|
MultiEdit: "file.write",
|
|
@@ -258,7 +263,10 @@ var ClaudeCodeAdapter = class {
|
|
|
258
263
|
timestamp: /* @__PURE__ */ new Date()
|
|
259
264
|
};
|
|
260
265
|
const interception = await this.interceptAction(action);
|
|
261
|
-
if (!interception.intercepted || !interception.context)
|
|
266
|
+
if (!interception.intercepted || !interception.context) {
|
|
267
|
+
this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? "not intercepted");
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
262
270
|
const decision = await this.gateway.evaluate(interception.context);
|
|
263
271
|
return this.decisionToHookOutput(decision);
|
|
264
272
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/claude-code/claude-code-adapter.ts","../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts"],"sourcesContent":["/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) return null;\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n","/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/claude-code/claude-code-adapter.ts","../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts"],"sourcesContent":["/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) {\n // Visible skip: an ungoverned tool must be distinguishable from an\n // ALLOW in the traces (a silent skip hid the Windows PowerShell\n // coverage gap for a full passive week).\n this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? 'not intercepted');\n return null;\n }\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n","/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n // Native Windows ships shell execution as a distinct PowerShell tool\n // (hooks docs: \"Tool Names in PreToolUse\"). Found live on 10 Jul 2026 —\n // the desktop-app runtime verification showed shell commands bypassing\n // the guard entirely on Windows because only Bash was mapped/matched.\n PowerShell: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -87,6 +87,11 @@ function extractNetworkDomains(target) {
|
|
|
87
87
|
// src/claude-code/tool-mapping.ts
|
|
88
88
|
var CLAUDE_CODE_TOOL_MAP = {
|
|
89
89
|
Bash: "shell.exec",
|
|
90
|
+
// Native Windows ships shell execution as a distinct PowerShell tool
|
|
91
|
+
// (hooks docs: "Tool Names in PreToolUse"). Found live on 10 Jul 2026 —
|
|
92
|
+
// the desktop-app runtime verification showed shell commands bypassing
|
|
93
|
+
// the guard entirely on Windows because only Bash was mapped/matched.
|
|
94
|
+
PowerShell: "shell.exec",
|
|
90
95
|
Write: "file.write",
|
|
91
96
|
Edit: "file.write",
|
|
92
97
|
MultiEdit: "file.write",
|
|
@@ -231,7 +236,10 @@ var ClaudeCodeAdapter = class {
|
|
|
231
236
|
timestamp: /* @__PURE__ */ new Date()
|
|
232
237
|
};
|
|
233
238
|
const interception = await this.interceptAction(action);
|
|
234
|
-
if (!interception.intercepted || !interception.context)
|
|
239
|
+
if (!interception.intercepted || !interception.context) {
|
|
240
|
+
this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? "not intercepted");
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
235
243
|
const decision = await this.gateway.evaluate(interception.context);
|
|
236
244
|
return this.decisionToHookOutput(decision);
|
|
237
245
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts","../../src/claude-code/claude-code-adapter.ts"],"sourcesContent":["/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n","/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) return null;\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapter-interface/interface.ts","../../src/adapter-interface/purpose-mapping.ts","../../src/claude-code/tool-mapping.ts","../../src/claude-code/claude-code-adapter.ts"],"sourcesContent":["/**\n * PlatformAdapter Interface\n *\n * The contract that Layer 4 platform adapters implement.\n * Agent-side interception: governs what agents are allowed to do before they do it.\n *\n * Each adapter is 200-500 lines of platform-specific code.\n * All verification logic lives in the gateway — adapters just translate\n * between the platform's world and AstraSync's world.\n */\n\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport type { PDLSSContext, VerificationDecision, AgentAction, InterceptResult } from '../gateway/types';\n\nexport interface AdapterConfig {\n /** The AstraSyncGateway instance (handles mode routing) */\n gateway: AstraSyncGateway;\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface PlatformAdapter {\n /**\n * Interface version for compatibility checking.\n * Current version: 1.\n */\n readonly interfaceVersion: number;\n\n /**\n * Platform-specific initialization.\n * Load config, register hooks, establish connections.\n */\n initialize(config: AdapterConfig): Promise<void>;\n\n /**\n * Graceful shutdown.\n * Drain in-flight verifications, deregister hooks, close connections.\n */\n shutdown(): Promise<void>;\n\n /**\n * Intercept an agent action before execution.\n *\n * How this works depends on the platform:\n * - CLI adapter: proxy server captures outbound request\n * - Browser adapter: content script intercepts DOM interaction\n * - Express: middleware captures inbound request\n */\n interceptAction(action: AgentAction): Promise<InterceptResult>;\n\n /**\n * Extract PDLSS-compatible context from a platform-specific action.\n * Maps platform-native action format to the universal PDLSSContext.\n */\n extractContext(action: AgentAction): PDLSSContext;\n\n /**\n * Enforce the verification decision in a platform-specific way.\n *\n * How this works depends on the platform:\n * - CLI: block command / allow command / prompt for approval\n * - Browser: block navigation / show confirmation dialog\n * - Express: return 403 / pass through / inject headers\n */\n enforceDecision(decision: VerificationDecision): Promise<void>;\n}\n\n/**\n * Current adapter interface version.\n */\nexport const ADAPTER_INTERFACE_VERSION = 1;\n\n/**\n * Check if an adapter is compatible with the current interface version.\n */\nexport function isCompatibleAdapter(adapter: PlatformAdapter): boolean {\n return adapter.interfaceVersion === ADAPTER_INTERFACE_VERSION;\n}\n","/**\n * Shared purpose mapping utilities for Layer 4 platform adapters.\n *\n * Maps platform-native action names to PDLSS purpose categories.\n * Used by OpenClaw CLI, Cursor, browser, and future adapters.\n */\n\n// -----------------------------------------------------------------------\n// Tool → Purpose mapping\n// -----------------------------------------------------------------------\n\n/** Standard tool name → PDLSS purpose mapping used by all adapters */\nconst TOOL_PURPOSE_MAP: Record<string, string> = {\n // Shell\n shell_exec: 'shell.exec',\n run_command: 'shell.exec',\n execute: 'shell.exec',\n terminal_exec: 'shell.exec',\n run_terminal_command: 'shell.exec',\n\n // File read\n file_read: 'file.read',\n read_file: 'file.read',\n\n // File write\n file_write: 'file.write',\n write_file: 'file.write',\n create_file: 'file.write',\n edit_file: 'file.write',\n\n // File delete\n file_delete: 'file.delete',\n delete_file: 'file.delete',\n\n // Network\n http_request: 'network.request',\n fetch: 'network.request',\n web_request: 'network.request',\n\n // Email\n send_email: 'email.send',\n read_email: 'email.read',\n\n // Calendar\n create_event: 'calendar.create',\n\n // Database\n query_database: 'database.query',\n write_database: 'database.write',\n\n // Payment\n payment_execute: 'payment.execute',\n};\n\n/**\n * Map a tool/action name to a PDLSS purpose category.\n * Returns `tool.<name>` for unmapped tools (denied by default).\n */\nexport function mapToolToPurpose(toolName: string): string {\n return TOOL_PURPOSE_MAP[toolName] || `tool.${toolName}`;\n}\n\n/**\n * Register additional tool → purpose mappings (e.g. from a platform adapter).\n * Does not overwrite existing mappings.\n */\nexport function registerToolMappings(mappings: Record<string, string>): void {\n for (const [tool, purpose] of Object.entries(mappings)) {\n if (!(tool in TOOL_PURPOSE_MAP)) {\n TOOL_PURPOSE_MAP[tool] = purpose;\n }\n }\n}\n\n// -----------------------------------------------------------------------\n// Target extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract the meaningful target string from tool arguments.\n * Uses the purpose category to determine which argument field is relevant.\n */\nexport function extractTarget(toolName: string, args: Record<string, unknown>): string {\n const purpose = mapToolToPurpose(toolName);\n\n if (purpose.startsWith('shell.')) {\n return String(args.command || args.cmd || args.script || '');\n }\n\n if (purpose.startsWith('file.')) {\n return String(args.path || args.file || args.filename || args.file_path || '');\n }\n\n if (purpose.startsWith('network.')) {\n return String(args.url || args.endpoint || args.uri || '');\n }\n\n if (purpose.startsWith('email.')) {\n return String(args.to || args.recipient || args.address || '');\n }\n\n if (purpose.startsWith('database.')) {\n return String(args.query || args.table || '');\n }\n\n if (purpose.startsWith('payment.')) {\n return String(args.description || args.merchant || args.amount || '');\n }\n\n // Fallback: try common field names\n if (args.command) return String(args.command);\n if (args.path) return String(args.path);\n if (args.url) return String(args.url);\n\n // Default: use first non-empty string argument or tool name\n for (const val of Object.values(args)) {\n if (typeof val === 'string' && val.length > 0) return val;\n }\n return toolName;\n}\n\n// -----------------------------------------------------------------------\n// Network domain extraction\n// -----------------------------------------------------------------------\n\n/**\n * Extract network domains from a URL target.\n * Returns undefined if the target is not a URL.\n */\nexport function extractNetworkDomains(target: string): string[] | undefined {\n try {\n if (target.startsWith('http://') || target.startsWith('https://')) {\n const url = new URL(target);\n return [url.hostname];\n }\n } catch {\n // Not a URL\n }\n return undefined;\n}\n","/**\n * @astrasyncai/adapter-claude-code — tool → purpose mapping\n *\n * Maps Claude Code's built-in tool names (as delivered in PreToolUse hook\n * payloads) to PDLSS purpose categories, and parses MCP tool names\n * (mcp__{server}__{tool}) into app-connector style purposes.\n */\n\nimport { registerToolMappings } from '../adapter-interface/purpose-mapping';\n\n/** Claude Code built-in tool name → PDLSS purpose */\nexport const CLAUDE_CODE_TOOL_MAP: Record<string, string> = {\n Bash: 'shell.exec',\n // Native Windows ships shell execution as a distinct PowerShell tool\n // (hooks docs: \"Tool Names in PreToolUse\"). Found live on 10 Jul 2026 —\n // the desktop-app runtime verification showed shell commands bypassing\n // the guard entirely on Windows because only Bash was mapped/matched.\n PowerShell: 'shell.exec',\n Write: 'file.write',\n Edit: 'file.write',\n MultiEdit: 'file.write',\n NotebookEdit: 'file.write',\n Read: 'file.read',\n Glob: 'file.read',\n Grep: 'file.read',\n WebFetch: 'network.request',\n WebSearch: 'network.request',\n Task: 'sub_agent.spawn',\n Agent: 'sub_agent.spawn',\n};\n\nlet registered = false;\n\n/**\n * Register the Claude Code mappings into the shared adapter purpose map.\n * Idempotent; safe to call from both the adapter and the hook entry.\n */\nexport function registerClaudeCodeMappings(): void {\n if (registered) return;\n registerToolMappings(CLAUDE_CODE_TOOL_MAP);\n registered = true;\n}\n\n/**\n * Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector\n * style purpose `{server}.{tool}`. Returns null for non-MCP tool names.\n */\nexport function mapMcpTool(toolName: string): string | null {\n const match = /^mcp__(.+?)__(.+)$/.exec(toolName);\n if (!match) return null;\n return `${match[1]}.${match[2]}`;\n}\n","/**\n * @astrasyncai/adapter-claude-code\n *\n * Layer 4 adapter for Claude Code (Anthropic's CLI coding agent).\n * Intercepts tool calls via Claude Code's native PreToolUse hook:\n * the hook binary receives {tool_name, tool_input, ...} on stdin and\n * answers with a permissionDecision on stdout.\n *\n * Decision mapping is strictly tightening:\n * - DENY → permissionDecision \"deny\"\n * - MANUAL_REVIEW → permissionDecision \"ask\" (Claude Code's own permission\n * prompt is the human-in-the-loop)\n * - ALLOW → no output at all — the guard NEVER emits \"allow\",\n * because \"allow\" would bypass Claude Code's native\n * permission system rather than defer to it.\n *\n * ~250 lines — thin, disposable, platform-specific.\n */\n\nimport type { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface';\nimport type {\n PDLSSContext,\n VerificationDecision,\n AgentAction,\n InterceptResult,\n} from '../gateway/types';\nimport type { AstraSyncGateway } from '../gateway/gateway';\nimport { ADAPTER_INTERFACE_VERSION } from '../adapter-interface/interface';\nimport {\n mapToolToPurpose,\n extractTarget,\n extractNetworkDomains,\n} from '../adapter-interface/purpose-mapping';\nimport { registerClaudeCodeMappings, mapMcpTool, CLAUDE_CODE_TOOL_MAP } from './tool-mapping';\n\n// -----------------------------------------------------------------------\n// Claude Code hook payload / output shapes\n// (contract: https://code.claude.com/docs/en/hooks.md)\n// -----------------------------------------------------------------------\n\n/** The PreToolUse payload Claude Code writes to the hook's stdin. */\nexport interface ClaudeCodePreToolUsePayload {\n hook_event_name?: string;\n tool_name: string;\n tool_input: Record<string, unknown>;\n session_id?: string;\n cwd?: string;\n transcript_path?: string;\n permission_mode?: string;\n}\n\n/** The JSON the hook prints to stdout when it wants to tighten a decision. */\nexport interface ClaudeHookOutput {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse';\n permissionDecision: 'deny' | 'ask';\n permissionDecisionReason: string;\n };\n}\n\n// -----------------------------------------------------------------------\n// Configuration\n// -----------------------------------------------------------------------\n\n/**\n * Tools that are never governed: session bookkeeping and UI-only tools\n * with no side effects outside the conversation.\n */\nexport const DEFAULT_SKIP_TOOLS = [\n 'TodoWrite',\n 'TaskCreate',\n 'TaskUpdate',\n 'TaskList',\n 'TaskGet',\n 'ExitPlanMode',\n 'EnterPlanMode',\n 'AskUserQuestion',\n 'BashOutput',\n 'KillShell',\n 'TaskStop',\n 'TaskOutput',\n 'ListMcpResourcesTool',\n];\n\nexport interface ClaudeCodeAdapterOptions {\n /** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */\n skipTools?: string[];\n /**\n * When true, unmapped non-MCP tools are still intercepted (purpose\n * `tool.<name>`, denied by default policy). When false (default),\n * unmapped tools are skipped — Claude Code's own permission system\n * remains their only gate.\n */\n strictUnmapped?: boolean;\n /**\n * Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).\n * Defaults to true. Note the evaluator denies purposes missing from the\n * policy, so with governMcp on, every MCP server in use needs purpose\n * rules; installers may default this off until the policy lists them.\n */\n governMcp?: boolean;\n}\n\n// -----------------------------------------------------------------------\n// Adapter implementation\n// -----------------------------------------------------------------------\n\nexport class ClaudeCodeAdapter implements PlatformAdapter {\n readonly interfaceVersion = ADAPTER_INTERFACE_VERSION;\n\n private gateway!: AstraSyncGateway;\n private skipTools: Set<string>;\n private strictUnmapped: boolean;\n private governMcp: boolean;\n private _isRunning = false;\n\n constructor(options?: ClaudeCodeAdapterOptions) {\n this.skipTools = new Set(options?.skipTools ?? DEFAULT_SKIP_TOOLS);\n this.strictUnmapped = options?.strictUnmapped ?? false;\n this.governMcp = options?.governMcp ?? true;\n registerClaudeCodeMappings();\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n async initialize(config: AdapterConfig): Promise<void> {\n this.gateway = config.gateway as AstraSyncGateway;\n\n if (Array.isArray(config.adapterOptions.skipTools)) {\n this.skipTools = new Set(config.adapterOptions.skipTools as string[]);\n }\n if (typeof config.adapterOptions.strictUnmapped === 'boolean') {\n this.strictUnmapped = config.adapterOptions.strictUnmapped;\n }\n\n this._isRunning = true;\n }\n\n async shutdown(): Promise<void> {\n this._isRunning = false;\n }\n\n async interceptAction(action: AgentAction): Promise<InterceptResult> {\n const raw = action.raw as Partial<ClaudeCodePreToolUsePayload> | null;\n\n if (!raw || typeof raw.tool_name !== 'string' || raw.tool_name.length === 0) {\n return { intercepted: false, skipReason: 'No tool_name in payload' };\n }\n\n if (this.skipTools.has(raw.tool_name)) {\n return { intercepted: false, skipReason: `Tool ${raw.tool_name} is not governed` };\n }\n\n const isMcp = mapMcpTool(raw.tool_name) !== null;\n if (isMcp && !this.governMcp) {\n return { intercepted: false, skipReason: `MCP tools not governed (governMcp off)` };\n }\n\n const isMapped = raw.tool_name in CLAUDE_CODE_TOOL_MAP || isMcp;\n if (!isMapped && !this.strictUnmapped) {\n return {\n intercepted: false,\n skipReason: `Unmapped tool ${raw.tool_name} (strictUnmapped off)`,\n };\n }\n\n const context = this.extractContext(action);\n return { intercepted: true, context };\n }\n\n extractContext(action: AgentAction): PDLSSContext {\n const raw = action.raw as ClaudeCodePreToolUsePayload;\n const toolName = raw.tool_name;\n const args = (raw.tool_input ?? {}) as Record<string, unknown>;\n\n const mcpPurpose = mapMcpTool(toolName);\n const purpose = mcpPurpose ?? mapToolToPurpose(toolName);\n const target = extractTarget(toolName, args);\n const networkAccess = extractNetworkDomains(target);\n\n return {\n purpose,\n action: toolName,\n target,\n ...(networkAccess && { networkAccess }),\n metadata: {\n ...(raw.session_id && { sessionId: raw.session_id }),\n ...(raw.cwd && { cwd: raw.cwd }),\n platform: 'claude-code',\n },\n };\n }\n\n /**\n * Translate a gateway decision into hook stdout JSON.\n * Returns null on ALLOW — the hook stays silent and Claude Code's own\n * permission system remains in charge (strictly-tightening invariant).\n */\n decisionToHookOutput(decision: VerificationDecision): ClaudeHookOutput | null {\n switch (decision.recommendation) {\n case 'DENY':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `AstraSync Local Guard: ${decision.reason}`,\n },\n };\n case 'MANUAL_REVIEW':\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'ask',\n permissionDecisionReason: `AstraSync Local Guard requires approval: ${decision.reason}`,\n },\n };\n case 'ALLOW':\n return null;\n }\n }\n\n /**\n * Convenience for the hook entry: full payload → hook output (or null).\n */\n async evaluatePayload(payload: ClaudeCodePreToolUsePayload): Promise<ClaudeHookOutput | null> {\n const action: AgentAction = {\n raw: payload,\n platform: 'claude-code',\n timestamp: new Date(),\n };\n\n const interception = await this.interceptAction(action);\n if (!interception.intercepted || !interception.context) {\n // Visible skip: an ungoverned tool must be distinguishable from an\n // ALLOW in the traces (a silent skip hid the Windows PowerShell\n // coverage gap for a full passive week).\n this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? 'not intercepted');\n return null;\n }\n\n const decision = await this.gateway.evaluate(interception.context);\n return this.decisionToHookOutput(decision);\n }\n\n /**\n * Enforcement happens via the stdout JSON the hook prints (see\n * decisionToHookOutput); there is no in-process side to enforce on.\n */\n async enforceDecision(_decision: VerificationDecision): Promise<void> {\n // Contract no-op: Claude Code enforces based on the hook's stdout.\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as LocalPurposeRule, d as LocalScope, c as LocalRiskThresholds, L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-
|
|
1
|
+
import { b as LocalPurposeRule, d as LocalScope, c as LocalRiskThresholds, L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-BV6abBnF.mjs';
|
|
2
2
|
import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.mjs';
|
|
3
3
|
import '../types-sy3tq8lp.mjs';
|
|
4
4
|
import '../metadata-capture.mjs';
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as LocalPurposeRule, d as LocalScope, c as LocalRiskThresholds, L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-
|
|
1
|
+
import { b as LocalPurposeRule, d as LocalScope, c as LocalRiskThresholds, L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, A as AgentAction, I as InterceptResult } from '../types-C23_uaY8.js';
|
|
2
2
|
import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.js';
|
|
3
3
|
import '../types-9tns4wyl.js';
|
|
4
4
|
import '../metadata-capture.js';
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { PlatformAdapter, AdapterConfig } from '../adapter-interface/interface.mjs';
|
|
2
|
+
import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-BV6abBnF.mjs';
|
|
3
|
+
import { AstraSyncGateway } from '../gateway/gateway.mjs';
|
|
4
|
+
import '../types-sy3tq8lp.mjs';
|
|
5
|
+
import '../metadata-capture.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @astrasyncai/adapter-codex
|
|
9
|
+
*
|
|
10
|
+
* Layer 4 adapter for OpenAI Codex CLI. Intercepts tool calls via Codex's
|
|
11
|
+
* native PreToolUse hooks (stable since Codex v0.124.0): the hook binary
|
|
12
|
+
* receives {tool_name, tool_input, permission_mode, ...} on stdin and may
|
|
13
|
+
* answer with a permissionDecision on stdout.
|
|
14
|
+
*
|
|
15
|
+
* Decision mapping is strictly tightening, with two Codex-specific twists
|
|
16
|
+
* versus the Claude Code adapter (§7.7):
|
|
17
|
+
*
|
|
18
|
+
* - Codex has NO "ask" decision. MANUAL_REVIEW therefore emits nothing —
|
|
19
|
+
* "no matching hook decides" makes Codex fall through to its own native
|
|
20
|
+
* approval prompt, which is the HITL. EXCEPT when the session runs in
|
|
21
|
+
* `dontAsk` / `bypassPermissions` permission mode: no native prompt will
|
|
22
|
+
* catch the fall-through there, so MANUAL_REVIEW escalates to deny.
|
|
23
|
+
* - Codex FAILS OPEN on hook errors/timeouts (it "reports the error and
|
|
24
|
+
* continues the tool call"). The hook binary therefore fails closed
|
|
25
|
+
* internally — every internal failure becomes an explicit deny (unless
|
|
26
|
+
* failOpen is configured). A hard kill/timeout still fails open on
|
|
27
|
+
* Codex's side; that residual gap is documented, and the mitigation is
|
|
28
|
+
* the hook's ~200ms cost against a 10s timeout.
|
|
29
|
+
*
|
|
30
|
+
* ALLOW never emits "allow" (and never `updatedInput`) — a hook allow would
|
|
31
|
+
* suppress Codex's own approval prompt rather than defer to it.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** The PreToolUse payload Codex CLI writes to the hook's stdin. */
|
|
35
|
+
interface CodexPreToolUsePayload {
|
|
36
|
+
hook_event_name?: string;
|
|
37
|
+
tool_name: string;
|
|
38
|
+
tool_input: Record<string, unknown>;
|
|
39
|
+
session_id?: string;
|
|
40
|
+
turn_id?: string;
|
|
41
|
+
tool_use_id?: string;
|
|
42
|
+
cwd?: string;
|
|
43
|
+
model?: string;
|
|
44
|
+
transcript_path?: string | null;
|
|
45
|
+
/** default | acceptEdits | plan | dontAsk | bypassPermissions */
|
|
46
|
+
permission_mode?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The JSON the hook prints to stdout when it wants to tighten a decision.
|
|
50
|
+
* Deny-only by design: Codex has no "ask", and the guard never emits
|
|
51
|
+
* "allow". Any unsupported field would make Codex mark the hook run failed
|
|
52
|
+
* and continue the call — keep this shape strictly to documented fields.
|
|
53
|
+
*/
|
|
54
|
+
interface CodexHookOutput {
|
|
55
|
+
hookSpecificOutput: {
|
|
56
|
+
hookEventName: 'PreToolUse';
|
|
57
|
+
permissionDecision: 'deny';
|
|
58
|
+
permissionDecisionReason: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Permission modes with no native approval prompt to catch a fall-through. */
|
|
62
|
+
declare const UNPROMPTED_PERMISSION_MODES: Set<string>;
|
|
63
|
+
/** Session bookkeeping tools that are never governed. */
|
|
64
|
+
declare const DEFAULT_SKIP_TOOLS: string[];
|
|
65
|
+
interface CodexAdapterOptions {
|
|
66
|
+
/** Tool names to never intercept (replaces DEFAULT_SKIP_TOOLS if given) */
|
|
67
|
+
skipTools?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* When true, unmapped non-MCP tools are still intercepted (purpose
|
|
70
|
+
* `tool.<name>`, denied by default policy). When false (default),
|
|
71
|
+
* unmapped tools are skipped — Codex's own approval flow remains their
|
|
72
|
+
* only gate.
|
|
73
|
+
*/
|
|
74
|
+
strictUnmapped?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Govern MCP tools (mcp__{server}__{tool} → purpose {server}.{tool}).
|
|
77
|
+
* Defaults to true; installers default it off until the policy lists
|
|
78
|
+
* {server}.{tool} rules (the evaluator denies unknown purposes).
|
|
79
|
+
*/
|
|
80
|
+
governMcp?: boolean;
|
|
81
|
+
}
|
|
82
|
+
declare class CodexAdapter implements PlatformAdapter {
|
|
83
|
+
readonly interfaceVersion = 1;
|
|
84
|
+
private gateway;
|
|
85
|
+
private skipTools;
|
|
86
|
+
private strictUnmapped;
|
|
87
|
+
private governMcp;
|
|
88
|
+
private _isRunning;
|
|
89
|
+
constructor(options?: CodexAdapterOptions);
|
|
90
|
+
get isRunning(): boolean;
|
|
91
|
+
initialize(config: AdapterConfig): Promise<void>;
|
|
92
|
+
shutdown(): Promise<void>;
|
|
93
|
+
interceptAction(action: AgentAction): Promise<InterceptResult>;
|
|
94
|
+
extractContext(action: AgentAction): PDLSSContext;
|
|
95
|
+
private buildContext;
|
|
96
|
+
/**
|
|
97
|
+
* Translate a gateway decision into hook stdout JSON.
|
|
98
|
+
*
|
|
99
|
+
* Returns null on ALLOW (silent — Codex's own approval flow stays in
|
|
100
|
+
* charge) and on MANUAL_REVIEW in prompted permission modes (the
|
|
101
|
+
* fall-through IS Codex's native prompt). MANUAL_REVIEW in dontAsk /
|
|
102
|
+
* bypassPermissions escalates to deny — no prompt exists to catch it.
|
|
103
|
+
*/
|
|
104
|
+
decisionToHookOutput(decision: VerificationDecision, permissionMode?: string): CodexHookOutput | null;
|
|
105
|
+
/**
|
|
106
|
+
* Convenience for the hook entry: full payload → hook output (or null).
|
|
107
|
+
*
|
|
108
|
+
* apply_patch envelopes are evaluated per touched file (one context per
|
|
109
|
+
* `*** Add/Update/Delete File:` header) and tightened to the worst
|
|
110
|
+
* decision, so file.write blockedPatterns (including guard
|
|
111
|
+
* self-protection) apply to every path in the patch.
|
|
112
|
+
*/
|
|
113
|
+
evaluatePayload(payload: CodexPreToolUsePayload): Promise<CodexHookOutput | null>;
|
|
114
|
+
private evaluateInterceptedPayload;
|
|
115
|
+
/**
|
|
116
|
+
* Enforcement happens via the stdout JSON the hook prints; there is no
|
|
117
|
+
* in-process side to enforce on.
|
|
118
|
+
*/
|
|
119
|
+
enforceDecision(_decision: VerificationDecision): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse an MCP tool name (mcp__{server}__{tool}) into an app-connector
|
|
124
|
+
* style purpose `{server}.{tool}`. Returns null for non-MCP tool names.
|
|
125
|
+
*/
|
|
126
|
+
declare function mapMcpTool(toolName: string): string | null;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @astrasyncai/adapter-codex — tool → purpose mapping
|
|
130
|
+
*
|
|
131
|
+
* Maps OpenAI Codex CLI's built-in tool names (as delivered in PreToolUse
|
|
132
|
+
* hook payloads) to PDLSS purpose categories. MCP tool names use the same
|
|
133
|
+
* mcp__{server}__{tool} convention as Claude Code, so the parser is shared.
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Codex CLI built-in tool name → PDLSS purpose.
|
|
138
|
+
* Documented canonical names: `Bash`, `apply_patch` (aliases `Edit`/`Write`
|
|
139
|
+
* appear in matcher examples), `web_search`. Undocumented tools stay
|
|
140
|
+
* unmapped and are skipped unless strictUnmapped is on.
|
|
141
|
+
*/
|
|
142
|
+
declare const CODEX_TOOL_MAP: Record<string, string>;
|
|
143
|
+
/**
|
|
144
|
+
* Register the Codex mappings into the shared adapter purpose map.
|
|
145
|
+
* Idempotent; safe to call from both the adapter and the hook entry.
|
|
146
|
+
*/
|
|
147
|
+
declare function registerCodexMappings(): void;
|
|
148
|
+
/**
|
|
149
|
+
* Extract the file paths an apply_patch envelope touches. Codex's patch
|
|
150
|
+
* format marks each file with `*** Add File:` / `*** Update File:` /
|
|
151
|
+
* `*** Delete File:` headers. Returns [] when nothing parseable is found —
|
|
152
|
+
* callers should then fall back to a raw-text target.
|
|
153
|
+
*/
|
|
154
|
+
declare function extractApplyPatchPaths(patchText: string): string[];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* AstraSync Local Guard for OpenAI Codex CLI — PreToolUse hook entry.
|
|
158
|
+
*
|
|
159
|
+
* Codex invokes this binary for each governed tool call, writing the
|
|
160
|
+
* PreToolUse payload to stdin. The hook evaluates the call against the
|
|
161
|
+
* local PDLSS policy (shared with the Claude Code adapter: same
|
|
162
|
+
* ~/.astrasync/policy.yaml + guard.json) and prints a permissionDecision
|
|
163
|
+
* JSON to stdout only when it wants to tighten the outcome (deny). On
|
|
164
|
+
* ALLOW — and on MANUAL_REVIEW in prompted permission modes — it prints
|
|
165
|
+
* nothing, so Codex's own approval flow stays in charge.
|
|
166
|
+
*
|
|
167
|
+
* Fail posture: Codex itself FAILS OPEN on hook errors ("reports the error
|
|
168
|
+
* and continues the tool call"), so unlike the Claude Code hook we cannot
|
|
169
|
+
* lean on exit codes alone — every internal failure is converted into an
|
|
170
|
+
* explicit deny decision (opt-outs: ASTRASYNC_GUARD_FAIL_OPEN=1 or
|
|
171
|
+
* guard.json {"failOpen": true}). A missing policy file everywhere means
|
|
172
|
+
* the guard simply isn't configured — silent no-op, never a block.
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
interface ProcessCodexHookOptions {
|
|
176
|
+
env?: NodeJS.ProcessEnv;
|
|
177
|
+
home?: string;
|
|
178
|
+
/** Injectable for tests */
|
|
179
|
+
gatewayFactory?: (config: ConstructorParameters<typeof AstraSyncGateway>[0]) => AstraSyncGateway;
|
|
180
|
+
}
|
|
181
|
+
interface ProcessCodexHookResult {
|
|
182
|
+
output: CodexHookOutput | null;
|
|
183
|
+
exitCode: number;
|
|
184
|
+
}
|
|
185
|
+
declare function processCodexHookPayload(payloadJson: string, options?: ProcessCodexHookOptions): Promise<ProcessCodexHookResult>;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* AstraSync Local Guard for Claude Code — settings.json installer.
|
|
189
|
+
*
|
|
190
|
+
* Merges the guard's PreToolUse hook entry into a Claude Code settings
|
|
191
|
+
* file (~/.claude/settings.json or <project>/.claude/settings.json)
|
|
192
|
+
* without clobbering anything else in it, and builds the default policy
|
|
193
|
+
* for a coding agent.
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
interface InstallResult {
|
|
197
|
+
installed: boolean;
|
|
198
|
+
alreadyPresent: boolean;
|
|
199
|
+
updated: boolean;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* AstraSync Local Guard for Codex CLI — hooks.json installer.
|
|
204
|
+
*
|
|
205
|
+
* Codex reads hooks from ~/.codex/hooks.json (user scope) or
|
|
206
|
+
* <repo>/.codex/hooks.json (project scope) with the SAME JSON shape as
|
|
207
|
+
* Claude Code's settings.json (`hooks.PreToolUse[].hooks[]`), so the merge
|
|
208
|
+
* and uninstall machinery is shared with the Claude Code installer — only
|
|
209
|
+
* the marker, matcher, and file location differ.
|
|
210
|
+
*
|
|
211
|
+
* The policy itself is shared with every other Local Guard adapter on the
|
|
212
|
+
* machine: ~/.astrasync/policy.yaml + guard.json. One policy per user.
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
/** Substring that identifies the guard's hook entry inside hooks.json */
|
|
216
|
+
declare const ASTRASYNC_CODEX_HOOK_MARKER = "astrasync-codex-hook";
|
|
217
|
+
/**
|
|
218
|
+
* Tools worth spawning the hook for. Codex matchers are regexes over
|
|
219
|
+
* tool_name; bookkeeping tools (update_plan) never pay node startup cost.
|
|
220
|
+
*/
|
|
221
|
+
declare const CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write|web_search|mcp__.*";
|
|
222
|
+
/**
|
|
223
|
+
* Merge the guard's PreToolUse entry into a Codex hooks.json file.
|
|
224
|
+
* Same never-clobber/idempotent semantics as the Claude Code installer.
|
|
225
|
+
*/
|
|
226
|
+
declare function installCodexHooks(hooksPath: string, hookCommand: string): InstallResult;
|
|
227
|
+
/** Remove the guard's PreToolUse entry from a Codex hooks.json file. */
|
|
228
|
+
declare function uninstallCodexHooks(hooksPath: string): boolean;
|
|
229
|
+
|
|
230
|
+
export { ASTRASYNC_CODEX_HOOK_MARKER, CODEX_HOOK_MATCHER, CODEX_TOOL_MAP, CodexAdapter, type CodexAdapterOptions, type CodexHookOutput, type CodexPreToolUsePayload, DEFAULT_SKIP_TOOLS, type ProcessCodexHookOptions, type ProcessCodexHookResult, UNPROMPTED_PERMISSION_MODES, extractApplyPatchPaths, installCodexHooks, mapMcpTool, processCodexHookPayload, registerCodexMappings, uninstallCodexHooks };
|