File size: 9,052 Bytes
a5d718a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**

 * AGENTSCOPE-SIFT β€” MCP Server

 *

 * "SIFT gives investigators tools. AGENTSCOPE gives the AI an evidence trail."

 *

 * Every tool call passes through:

 *   BOB Plasma Filter β†’ SENTINEL β†’ SealForge WORM chain

 *

 * The agent physically cannot:

 *   - Run destructive commands (no write tools exposed)

 *   - Skip evidence validation (BOB gates every result)

 *   - Produce an unproven finding (every finding sealed to a tool call)

 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
import { z } from 'zod'

import { bob } from './pipeline/bob.js'
import { sentinel } from './pipeline/sentinel.js'
import { sealEntry, initChain } from './pipeline/seal.js'
import * as SIFT from './tools/sift.js'

initChain()

const server = new Server(
  { name: 'agentscope-sift', version: '1.0.0' },
  { capabilities: { tools: {} } }
)

// ── Tool registry ─────────────────────────────────────────────────────────────

const TOOLS = [
  {
    name: 'list_processes',
    description: 'List running processes from a memory image (volatility3 windows.pslist)',
    inputSchema: { type: 'object', properties: { memory_image: { type: 'string', description: 'Path to memory dump (.raw, .mem, .dmp)' } }, required: ['memory_image'] },
  },
  {
    name: 'scan_network_connections',
    description: 'Extract network connections from memory (volatility3 windows.netstat)',
    inputSchema: { type: 'object', properties: { memory_image: { type: 'string' } }, required: ['memory_image'] },
  },
  {
    name: 'dump_cmdline',
    description: 'Extract command-line arguments of all processes from memory',
    inputSchema: { type: 'object', properties: { memory_image: { type: 'string' } }, required: ['memory_image'] },
  },
  {
    name: 'find_injected_code',
    description: 'Detect code injection and suspicious memory regions (volatility3 malfind)',
    inputSchema: { type: 'object', properties: { memory_image: { type: 'string' } }, required: ['memory_image'] },
  },
  {
    name: 'get_registry_hives',
    description: 'List Windows registry hives found in memory',
    inputSchema: { type: 'object', properties: { memory_image: { type: 'string' } }, required: ['memory_image'] },
  },
  {
    name: 'list_disk_partitions',
    description: 'List partitions in a disk image (mmls)',
    inputSchema: { type: 'object', properties: { image_path: { type: 'string' } }, required: ['image_path'] },
  },
  {
    name: 'list_files',
    description: 'List files on a disk image filesystem including deleted files (fls)',
    inputSchema: { type: 'object', properties: { image_path: { type: 'string' }, offset: { type: 'number' } }, required: ['image_path'] },
  },
  {
    name: 'get_filesystem_info',
    description: 'Get filesystem metadata from disk image (fsstat)',
    inputSchema: { type: 'object', properties: { image_path: { type: 'string' }, offset: { type: 'number' } }, required: ['image_path'] },
  },
  {
    name: 'scan_with_yara',
    description: 'Scan a file or directory with YARA malware rules',
    inputSchema: { type: 'object', properties: { rules_file: { type: 'string' }, target_path: { type: 'string' } }, required: ['rules_file', 'target_path'] },
  },
  {
    name: 'extract_strings',
    description: 'Extract printable strings from a binary β€” finds IOCs, URLs, registry keys',
    inputSchema: { type: 'object', properties: { file_path: { type: 'string' }, min_length: { type: 'number' } }, required: ['file_path'] },
  },
  {
    name: 'hash_file',
    description: 'Compute SHA-256 and MD5 hashes of a file for evidence integrity',
    inputSchema: { type: 'object', properties: { file_path: { type: 'string' } }, required: ['file_path'] },
  },
  {
    name: 'get_file_metadata',
    description: 'Extract file type and metadata (exiftool)',
    inputSchema: { type: 'object', properties: { file_path: { type: 'string' } }, required: ['file_path'] },
  },
  {
    name: 'analyze_pcap',
    description: 'Analyze a network capture β€” DNS queries, HTTP requests (tshark)',
    inputSchema: { type: 'object', properties: { pcap_file: { type: 'string' } }, required: ['pcap_file'] },
  },
  {
    name: 'verify_chain',
    description: 'Verify the WORM evidence chain has not been tampered with',
    inputSchema: { type: 'object', properties: {}, required: [] },
  },
]

server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }))

// ── Tool dispatcher ───────────────────────────────────────────────────────────

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  const { name, arguments: args } = req.params
  const a = (args ?? {}) as Record<string, string | number>

  // Seal the call intent before executing
  sealEntry('TOOL_CALL', { tool: name, args: a }, name)

  let rawResult: object
  try {
    rawResult = await dispatch(name, a)
  } catch (err: any) {
    sealEntry('BLOCK', { tool: name, reason: err.message }, name)
    return { content: [{ type: 'text', text: `[BLOCKED] ${err.message}` }], isError: true }
  }

  const rawStr = JSON.stringify(rawResult)

  // BOB Plasma Filter β€” catches adversarial tool output
  const bobResult = bob(rawStr)
  if (!bobResult.pass) {
    sealEntry('BLOCK', {
      tool: name,
      blocked_at: bobResult.blocked_at,
      violation: bobResult.plasma.violation ?? bobResult.coherence.message,
    }, name)
    return {
      content: [{ type: 'text', text: `[BOB BLOCKED at ${bobResult.blocked_at}] Tool output failed integrity check. Finding discarded.` }],
      isError: true,
    }
  }

  // SENTINEL zero-trust gate
  const sentinelResult = sentinel(name, rawStr)
  if (sentinelResult.verdict !== 'APPROVED') {
    sealEntry('BLOCK', {
      tool: name,
      verdict: sentinelResult.verdict,
      finding: sentinelResult.finding,
    }, name)
    return {
      content: [{ type: 'text', text: `[SENTINEL ${sentinelResult.verdict}] ${sentinelResult.finding ?? 'trust gate failed'}` }],
      isError: true,
    }
  }

  // Seal the approved result
  const entry = sealEntry('FINDING', {
    tool: name,
    trust_score: sentinelResult.trust_score,
    result_preview: rawStr.slice(0, 200),
  }, name)

  const output = {
    ...rawResult,
    _agentscope: {
      seal: entry.seal,
      seq: entry.seq,
      trust_score: sentinelResult.trust_score,
      verdict: sentinelResult.verdict,
      entropy: bobResult.coherence.entropy,
    }
  }

  return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] }
})

async function dispatch(name: string, a: Record<string, string | number>): Promise<object> {
  switch (name) {
    case 'list_processes':          return SIFT.list_processes(String(a.memory_image))
    case 'scan_network_connections': return SIFT.scan_network_connections(String(a.memory_image))
    case 'dump_cmdline':            return SIFT.dump_cmdline(String(a.memory_image))
    case 'find_injected_code':      return SIFT.find_injected_code(String(a.memory_image))
    case 'get_registry_hives':      return SIFT.get_registry_hives(String(a.memory_image))
    case 'list_disk_partitions':    return SIFT.list_disk_partitions(String(a.image_path))
    case 'list_files':              return SIFT.list_files(String(a.image_path), a.offset as number | undefined)
    case 'get_filesystem_info':     return SIFT.get_filesystem_info(String(a.image_path), a.offset as number | undefined)
    case 'scan_with_yara':          return SIFT.scan_with_yara(String(a.rules_file), String(a.target_path))
    case 'extract_strings':         return SIFT.extract_strings(String(a.file_path), a.min_length as number | undefined)
    case 'hash_file':               return SIFT.hash_file(String(a.file_path))
    case 'get_file_metadata':       return SIFT.get_file_metadata(String(a.file_path))
    case 'analyze_pcap':            return SIFT.analyze_pcap(String(a.pcap_file))
    case 'verify_chain': {
      const { verifyChain } = await import('./pipeline/seal.js')
      return verifyChain()
    }
    default: throw new Error(`Unknown tool: ${name}`)
  }
}

// ── Start ─────────────────────────────────────────────────────────────────────

const transport = new StdioServerTransport()
await server.connect(transport)
console.error('[AGENTSCOPE-SIFT] MCP server running β€” BOB + SENTINEL + SealForge active')