sonobat 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2800 -2909
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/db/schema.ts","../src/db/migrate.ts","../src/mcp/server.ts","../src/mcp/tools/query.ts","../src/db/repository/host-repository.ts","../src/db/repository/service-repository.ts","../src/db/repository/vhost-repository.ts","../src/db/repository/http-endpoint-repository.ts","../src/db/repository/input-repository.ts","../src/db/repository/observation-repository.ts","../src/db/repository/credential-repository.ts","../src/db/repository/vulnerability-repository.ts","../src/mcp/tools/ingest.ts","../src/engine/ingest.ts","../src/db/repository/artifact-repository.ts","../src/parser/nmap-parser.ts","../src/types/parser.ts","../src/parser/ffuf-parser.ts","../src/parser/nuclei-parser.ts","../src/db/repository/service-observation-repository.ts","../src/db/repository/endpoint-input-repository.ts","../src/db/repository/cve-repository.ts","../src/engine/normalizer.ts","../src/mcp/tools/propose.ts","../src/engine/proposer.ts","../src/mcp/tools/mutation.ts","../src/mcp/tools/datalog.ts","../src/engine/datalog/types.ts","../src/engine/datalog/tokenizer.ts","../src/engine/datalog/parser.ts","../src/engine/datalog/evaluator.ts","../src/engine/datalog/fact-extractor.ts","../src/engine/datalog/preset-rules.ts","../src/db/repository/datalog-rule-repository.ts","../src/engine/datalog/index.ts","../src/mcp/resources.ts"],"sourcesContent":["/**\n * sonobat — AttackDataGraph for autonomous penetration testing\n *\n * MCP Server エントリポイント。\n * stdio トランスポートで LLM Agent と接続する。\n */\n\nimport Database from 'better-sqlite3';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { migrateDatabase } from './db/migrate.js';\nimport { createMcpServer } from './mcp/server.js';\n\nconst DB_PATH = process.env['SONOBAT_DB_PATH'] ?? 'sonobat.db';\nconst db = new Database(DB_PATH);\nmigrateDatabase(db);\n\nconst server = createMcpServer(db);\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","/**\n * sonobat — AttackDataGraph SQLite schema\n *\n * This schema is the single source of truth for the database structure.\n * It is derived from docs/architecture.md Section 4.\n */\n\nexport const SCHEMA_SQL = `\nPRAGMA foreign_keys = ON;\n\n-- ============================================================\n-- 実行単位(任意)\n-- ============================================================\nCREATE TABLE IF NOT EXISTS scans (\n id TEXT PRIMARY KEY,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n notes TEXT\n);\n\n-- ============================================================\n-- 生出力(ファイル)参照\n-- ============================================================\nCREATE TABLE IF NOT EXISTS artifacts (\n id TEXT PRIMARY KEY,\n scan_id TEXT,\n tool TEXT NOT NULL, -- \"nmap\" | \"ffuf\" | \"nuclei\"\n kind TEXT NOT NULL, -- \"tool_output\" | \"http_request\" | \"http_response\"\n path TEXT NOT NULL, -- ローカルファイルパス or URI\n sha256 TEXT,\n captured_at TEXT NOT NULL,\n attrs_json TEXT, -- 任意メタ(コマンド、引数等)\n FOREIGN KEY (scan_id) REFERENCES scans(id) ON DELETE SET NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_artifacts_tool ON artifacts(tool);\n\n-- ============================================================\n-- ホスト\n-- ============================================================\nCREATE TABLE IF NOT EXISTS hosts (\n id TEXT PRIMARY KEY,\n authority_kind TEXT NOT NULL, -- \"IP\" | \"DOMAIN\"\n authority TEXT NOT NULL UNIQUE, -- IP アドレスまたはドメイン名\n resolved_ips_json TEXT NOT NULL DEFAULT '[]',\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\n-- ============================================================\n-- バーチャルホスト\n-- ============================================================\nCREATE TABLE IF NOT EXISTS vhosts (\n id TEXT PRIMARY KEY,\n host_id TEXT NOT NULL,\n hostname TEXT NOT NULL,\n source TEXT, -- \"nmap\" | \"cert\" | \"header\" | \"manual\"\n evidence_artifact_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (host_id, hostname),\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_vhosts_host ON vhosts(host_id);\n\n-- ============================================================\n-- サービス\n-- ============================================================\nCREATE TABLE IF NOT EXISTS services (\n id TEXT PRIMARY KEY,\n host_id TEXT NOT NULL,\n transport TEXT NOT NULL, -- \"tcp\" | \"udp\"\n port INTEGER NOT NULL,\n app_proto TEXT NOT NULL, -- \"http\" | \"ssh\" | \"ftp\" 等\n proto_confidence TEXT NOT NULL, -- \"high\" | \"medium\" | \"low\"\n banner TEXT,\n product TEXT,\n version TEXT,\n state TEXT NOT NULL, -- \"open\" | \"closed\" | \"filtered\"\n evidence_artifact_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (host_id, transport, port),\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_services_host ON services(host_id);\n\n-- ============================================================\n-- サービス観測(key-value)\n-- ============================================================\nCREATE TABLE IF NOT EXISTS service_observations (\n id TEXT PRIMARY KEY,\n service_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n confidence TEXT NOT NULL, -- \"high\" | \"medium\" | \"low\"\n evidence_artifact_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_svc_obs_service ON service_observations(service_id);\n\n-- ============================================================\n-- HTTP エンドポイント\n-- ============================================================\nCREATE TABLE IF NOT EXISTS http_endpoints (\n id TEXT PRIMARY KEY,\n service_id TEXT NOT NULL,\n vhost_id TEXT,\n base_uri TEXT NOT NULL, -- \"http://example.com:80\"\n method TEXT NOT NULL, -- \"GET\" | \"POST\" | ...\n path TEXT NOT NULL, -- \"/admin\"(クエリは含めない)\n status_code INTEGER,\n content_length INTEGER,\n words INTEGER,\n lines INTEGER,\n evidence_artifact_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (service_id, method, path),\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\n FOREIGN KEY (vhost_id) REFERENCES vhosts(id) ON DELETE SET NULL,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_endpoints_service ON http_endpoints(service_id);\n\n-- ============================================================\n-- 入力パラメータ\n-- ============================================================\nCREATE TABLE IF NOT EXISTS inputs (\n id TEXT PRIMARY KEY,\n service_id TEXT NOT NULL,\n location TEXT NOT NULL, -- \"query\" | \"path\" | \"body\" | \"header\" | \"cookie\"\n name TEXT NOT NULL,\n type_hint TEXT, -- \"string\" | \"int\" | \"json\" 等(任意)\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE (service_id, location, name),\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_inputs_service ON inputs(service_id);\n\n-- ============================================================\n-- エンドポイント ↔ 入力(多対多)\n-- ============================================================\nCREATE TABLE IF NOT EXISTS endpoint_inputs (\n id TEXT PRIMARY KEY,\n endpoint_id TEXT NOT NULL,\n input_id TEXT NOT NULL,\n evidence_artifact_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (endpoint_id, input_id),\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE CASCADE,\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_ep_inputs_endpoint ON endpoint_inputs(endpoint_id);\nCREATE INDEX IF NOT EXISTS idx_ep_inputs_input ON endpoint_inputs(input_id);\n\n-- ============================================================\n-- 観測値\n-- ============================================================\nCREATE TABLE IF NOT EXISTS observations (\n id TEXT PRIMARY KEY,\n input_id TEXT NOT NULL,\n raw_value TEXT NOT NULL,\n norm_value TEXT NOT NULL,\n body_path TEXT, -- JSON Pointer 等(例: \"/user/name\")\n source TEXT NOT NULL, -- \"ffuf_url\" | \"req_query\" | \"req_body\" | \"manual\"\n confidence TEXT NOT NULL, -- \"high\" | \"medium\" | \"low\"\n evidence_artifact_id TEXT NOT NULL,\n observed_at TEXT NOT NULL,\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_obs_input ON observations(input_id);\n\n-- ============================================================\n-- 認証情報\n-- ============================================================\nCREATE TABLE IF NOT EXISTS credentials (\n id TEXT PRIMARY KEY,\n service_id TEXT NOT NULL,\n endpoint_id TEXT, -- HTTP の場合のみ(任意)\n username TEXT NOT NULL,\n secret TEXT NOT NULL,\n secret_type TEXT NOT NULL, -- \"password\" | \"token\" | \"api_key\" | \"ssh_key\"\n source TEXT NOT NULL, -- \"brute_force\" | \"default\" | \"leaked\" | \"manual\"\n confidence TEXT NOT NULL, -- \"high\" | \"medium\" | \"low\"\n evidence_artifact_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_creds_service ON credentials(service_id);\nCREATE INDEX IF NOT EXISTS idx_creds_endpoint ON credentials(endpoint_id);\n\n-- ============================================================\n-- 脆弱性\n-- ============================================================\nCREATE TABLE IF NOT EXISTS vulnerabilities (\n id TEXT PRIMARY KEY,\n service_id TEXT NOT NULL,\n endpoint_id TEXT, -- HTTP の場合のみ(任意)\n vuln_type TEXT NOT NULL, -- \"sqli\" | \"xss\" | \"rce\" | \"lfi\" | \"ssrf\" | ...\n title TEXT NOT NULL,\n description TEXT,\n severity TEXT NOT NULL, -- \"critical\" | \"high\" | \"medium\" | \"low\" | \"info\"\n confidence TEXT NOT NULL, -- \"high\" | \"medium\" | \"low\"\n evidence_artifact_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\n);\n\nCREATE INDEX IF NOT EXISTS idx_vulns_service ON vulnerabilities(service_id);\nCREATE INDEX IF NOT EXISTS idx_vulns_endpoint ON vulnerabilities(endpoint_id);\nCREATE INDEX IF NOT EXISTS idx_vulns_severity ON vulnerabilities(severity);\n\n-- ============================================================\n-- CVE 情報\n-- ============================================================\nCREATE TABLE IF NOT EXISTS cves (\n id TEXT PRIMARY KEY,\n vulnerability_id TEXT NOT NULL,\n cve_id TEXT NOT NULL, -- \"CVE-YYYY-NNNNN\"\n description TEXT,\n cvss_score REAL,\n cvss_vector TEXT,\n reference_url TEXT,\n created_at TEXT NOT NULL,\n FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE\n);\n\nCREATE INDEX IF NOT EXISTS idx_cves_vuln ON cves(vulnerability_id);\nCREATE INDEX IF NOT EXISTS idx_cves_cveid ON cves(cve_id);\n\n-- ============================================================\n-- Datalog ルール保存\n-- ============================================================\nCREATE TABLE IF NOT EXISTS datalog_rules (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n rule_text TEXT NOT NULL,\n generated_by TEXT NOT NULL, -- \"human\" | \"ai\" | \"preset\"\n is_preset INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_datalog_rules_name ON datalog_rules(name);\n` as const;\n","import type Database from 'better-sqlite3';\nimport { SCHEMA_SQL } from './schema.js';\n\n/**\n * Run schema SQL to create all tables.\n * Enables foreign key enforcement before executing the schema.\n */\nexport function migrateDatabase(db: Database.Database): void {\n db.pragma('foreign_keys = ON');\n db.exec(SCHEMA_SQL);\n}\n","/**\r\n * sonobat — MCP Server\r\n *\r\n * Creates and configures the MCP server with all tools and resources.\r\n */\r\n\r\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { registerQueryTools } from './tools/query.js';\r\nimport { registerIngestTool } from './tools/ingest.js';\r\nimport { registerProposeTool } from './tools/propose.js';\r\nimport { registerMutationTools } from './tools/mutation.js';\r\nimport { registerDatalogTools } from './tools/datalog.js';\r\nimport { registerResources } from './resources.js';\r\n\r\n/**\r\n * Create a fully configured MCP server with all sonobat tools and resources.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @returns Configured McpServer instance\r\n */\r\nexport function createMcpServer(db: Database.Database): McpServer {\r\n const server = new McpServer({\r\n name: 'sonobat',\r\n version: '0.2.0',\r\n });\r\n\r\n // Register tools\r\n registerQueryTools(server, db);\r\n registerIngestTool(server, db);\r\n registerProposeTool(server, db);\r\n registerMutationTools(server, db);\r\n registerDatalogTools(server, db);\r\n\r\n // Register resources\r\n registerResources(server, db);\r\n\r\n return server;\r\n}\r\n","/**\n * sonobat — MCP Query Tools\n *\n * Read-only tools for querying the AttackDataGraph.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { HostRepository } from '../../db/repository/host-repository.js';\nimport { ServiceRepository } from '../../db/repository/service-repository.js';\nimport { VhostRepository } from '../../db/repository/vhost-repository.js';\nimport { HttpEndpointRepository } from '../../db/repository/http-endpoint-repository.js';\nimport { InputRepository } from '../../db/repository/input-repository.js';\nimport { ObservationRepository } from '../../db/repository/observation-repository.js';\nimport { CredentialRepository } from '../../db/repository/credential-repository.js';\nimport { VulnerabilityRepository } from '../../db/repository/vulnerability-repository.js';\n\nexport function registerQueryTools(server: McpServer, db: Database.Database): void {\n const hostRepo = new HostRepository(db);\n const serviceRepo = new ServiceRepository(db);\n const vhostRepo = new VhostRepository(db);\n const httpEndpointRepo = new HttpEndpointRepository(db);\n const inputRepo = new InputRepository(db);\n const observationRepo = new ObservationRepository(db);\n const credentialRepo = new CredentialRepository(db);\n const vulnRepo = new VulnerabilityRepository(db);\n\n // 1. list_hosts\n server.tool('list_hosts', 'List all discovered hosts', {}, async () => {\n const hosts = hostRepo.findAll();\n return { content: [{ type: 'text', text: JSON.stringify(hosts, null, 2) }] };\n });\n\n // 2. get_host\n server.tool(\n 'get_host',\n 'Get detailed information about a host including services and vhosts',\n { hostId: z.string().describe('Host UUID') },\n async ({ hostId }) => {\n const host = hostRepo.findById(hostId);\n if (!host) {\n return { content: [{ type: 'text', text: `Host not found: ${hostId}` }], isError: true };\n }\n const services = serviceRepo.findByHostId(hostId);\n const vhosts = vhostRepo.findByHostId(hostId);\n const result = { ...host, services, vhosts };\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\n },\n );\n\n // 3. list_services\n server.tool(\n 'list_services',\n 'List all services for a host',\n { hostId: z.string().describe('Host UUID') },\n async ({ hostId }) => {\n const services = serviceRepo.findByHostId(hostId);\n return { content: [{ type: 'text', text: JSON.stringify(services, null, 2) }] };\n },\n );\n\n // 4. list_endpoints\n server.tool(\n 'list_endpoints',\n 'List all HTTP endpoints for a service',\n { serviceId: z.string().describe('Service UUID') },\n async ({ serviceId }) => {\n const endpoints = httpEndpointRepo.findByServiceId(serviceId);\n return { content: [{ type: 'text', text: JSON.stringify(endpoints, null, 2) }] };\n },\n );\n\n // 5. list_inputs\n server.tool(\n 'list_inputs',\n 'List all input parameters for a service, optionally filtered by location',\n {\n serviceId: z.string().describe('Service UUID'),\n location: z.string().optional().describe('Filter by location (query, path, body, header, cookie)'),\n },\n async ({ serviceId, location }) => {\n const inputs = inputRepo.findByServiceId(serviceId, location);\n return { content: [{ type: 'text', text: JSON.stringify(inputs, null, 2) }] };\n },\n );\n\n // 6. list_observations\n server.tool(\n 'list_observations',\n 'List all observations for an input parameter',\n { inputId: z.string().describe('Input UUID') },\n async ({ inputId }) => {\n const observations = observationRepo.findByInputId(inputId);\n return { content: [{ type: 'text', text: JSON.stringify(observations, null, 2) }] };\n },\n );\n\n // 7. list_credentials\n server.tool(\n 'list_credentials',\n 'List credentials, optionally filtered by service',\n {\n serviceId: z.string().optional().describe('Service UUID (optional, omit to list all)'),\n },\n async ({ serviceId }) => {\n const credentials = serviceId\n ? credentialRepo.findByServiceId(serviceId)\n : credentialRepo.findAll();\n return { content: [{ type: 'text', text: JSON.stringify(credentials, null, 2) }] };\n },\n );\n\n // 8. list_vulnerabilities\n server.tool(\n 'list_vulnerabilities',\n 'List vulnerabilities, optionally filtered by service and/or severity',\n {\n serviceId: z.string().optional().describe('Service UUID (optional, omit to list all)'),\n severity: z.string().optional().describe('Filter by severity (critical, high, medium, low, info)'),\n },\n async ({ serviceId, severity }) => {\n const vulns = serviceId\n ? vulnRepo.findByServiceId(serviceId, severity)\n : vulnRepo.findAll(severity);\n return { content: [{ type: 'text', text: JSON.stringify(vulns, null, 2) }] };\n },\n );\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Host } from '../../types/entities.js';\nimport type { CreateHostInput, UpdateHostInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `hosts` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface HostRow {\n id: string;\n authority_kind: string;\n authority: string;\n resolved_ips_json: string;\n created_at: string;\n updated_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Host entity. */\nfunction rowToHost(row: HostRow): Host {\n return {\n id: row.id,\n authorityKind: row.authority_kind,\n authority: row.authority,\n resolvedIpsJson: row.resolved_ips_json,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/**\n * Repository for the `hosts` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class HostRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Host and return the full entity. */\n create(input: CreateHostInput): Host {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string, string, string, string]\n >(\n `INSERT INTO hosts (id, authority_kind, authority, resolved_ips_json, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.authorityKind,\n input.authority,\n input.resolvedIpsJson,\n now,\n now,\n );\n\n return {\n id,\n authorityKind: input.authorityKind,\n authority: input.authority,\n resolvedIpsJson: input.resolvedIpsJson,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /** Find a Host by its primary key. Returns undefined if not found. */\n findById(id: string): Host | undefined {\n const stmt = this.db.prepare<[string], HostRow>(\n `SELECT id, authority_kind, authority, resolved_ips_json, created_at, updated_at\n FROM hosts\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToHost(row) : undefined;\n }\n\n /** Return all Hosts. */\n findAll(): Host[] {\n const stmt = this.db.prepare<[], HostRow>(\n `SELECT id, authority_kind, authority, resolved_ips_json, created_at, updated_at\n FROM hosts`,\n );\n\n const rows = stmt.all();\n return rows.map(rowToHost);\n }\n\n /** Find a Host by its unique authority value. Returns undefined if not found. */\n findByAuthority(authority: string): Host | undefined {\n const stmt = this.db.prepare<[string], HostRow>(\n `SELECT id, authority_kind, authority, resolved_ips_json, created_at, updated_at\n FROM hosts\n WHERE authority = ?`,\n );\n\n const row = stmt.get(authority);\n return row ? rowToHost(row) : undefined;\n }\n\n /**\n * Update an existing Host with the provided fields.\n * Always bumps `updated_at`. Returns the updated entity, or undefined if\n * the Host was not found.\n */\n update(id: string, input: UpdateHostInput): Host | undefined {\n const setClauses: string[] = [];\n const params: unknown[] = [];\n\n if (input.resolvedIpsJson !== undefined) {\n setClauses.push('resolved_ips_json = ?');\n params.push(input.resolvedIpsJson);\n }\n\n const now = new Date().toISOString();\n setClauses.push('updated_at = ?');\n params.push(now);\n\n // Always include the WHERE id\n params.push(id);\n\n const sql = `UPDATE hosts SET ${setClauses.join(', ')} WHERE id = ?`;\n const stmt = this.db.prepare(sql);\n const result = stmt.run(...params);\n\n if (result.changes === 0) {\n return undefined;\n }\n\n return this.findById(id);\n }\n\n /** Delete a Host by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM hosts WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Service } from '../../types/entities.js';\nimport type { CreateServiceInput, UpdateServiceInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `services` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface ServiceRow {\n id: string;\n host_id: string;\n transport: string;\n port: number;\n app_proto: string;\n proto_confidence: string;\n banner: string | null;\n product: string | null;\n version: string | null;\n state: string;\n evidence_artifact_id: string;\n created_at: string;\n updated_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Service entity. */\nfunction rowToService(row: ServiceRow): Service {\n return {\n id: row.id,\n hostId: row.host_id,\n transport: row.transport,\n port: row.port,\n appProto: row.app_proto,\n protoConfidence: row.proto_confidence,\n banner: row.banner ?? undefined,\n product: row.product ?? undefined,\n version: row.version ?? undefined,\n state: row.state,\n evidenceArtifactId: row.evidence_artifact_id,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/**\n * Repository for the `services` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class ServiceRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Service and return the full entity. */\n create(input: CreateServiceInput): Service {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string, number, string, string, string | null, string | null, string | null, string, string, string, string]\n >(\n `INSERT INTO services (id, host_id, transport, port, app_proto, proto_confidence, banner, product, version, state, evidence_artifact_id, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.hostId,\n input.transport,\n input.port,\n input.appProto,\n input.protoConfidence,\n input.banner ?? null,\n input.product ?? null,\n input.version ?? null,\n input.state,\n input.evidenceArtifactId,\n now,\n now,\n );\n\n return {\n id,\n hostId: input.hostId,\n transport: input.transport,\n port: input.port,\n appProto: input.appProto,\n protoConfidence: input.protoConfidence,\n banner: input.banner,\n product: input.product,\n version: input.version,\n state: input.state,\n evidenceArtifactId: input.evidenceArtifactId,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /** Find a Service by its primary key. Returns undefined if not found. */\n findById(id: string): Service | undefined {\n const stmt = this.db.prepare<[string], ServiceRow>(\n `SELECT id, host_id, transport, port, app_proto, proto_confidence, banner, product, version, state, evidence_artifact_id, created_at, updated_at\n FROM services\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToService(row) : undefined;\n }\n\n /** Find all Services belonging to a given host. */\n findByHostId(hostId: string): Service[] {\n const stmt = this.db.prepare<[string], ServiceRow>(\n `SELECT id, host_id, transport, port, app_proto, proto_confidence, banner, product, version, state, evidence_artifact_id, created_at, updated_at\n FROM services\n WHERE host_id = ?`,\n );\n\n const rows = stmt.all(hostId);\n return rows.map(rowToService);\n }\n\n /**\n * Update an existing Service with the provided fields.\n * Always bumps `updated_at`. Returns the updated entity, or undefined if\n * the Service was not found.\n */\n update(id: string, input: UpdateServiceInput): Service | undefined {\n const setClauses: string[] = [];\n const params: unknown[] = [];\n\n if (input.appProto !== undefined) {\n setClauses.push('app_proto = ?');\n params.push(input.appProto);\n }\n\n if (input.protoConfidence !== undefined) {\n setClauses.push('proto_confidence = ?');\n params.push(input.protoConfidence);\n }\n\n if (input.banner !== undefined) {\n setClauses.push('banner = ?');\n params.push(input.banner);\n }\n\n if (input.product !== undefined) {\n setClauses.push('product = ?');\n params.push(input.product);\n }\n\n if (input.version !== undefined) {\n setClauses.push('version = ?');\n params.push(input.version);\n }\n\n if (input.state !== undefined) {\n setClauses.push('state = ?');\n params.push(input.state);\n }\n\n const now = new Date().toISOString();\n setClauses.push('updated_at = ?');\n params.push(now);\n\n // Always include the WHERE id\n params.push(id);\n\n const sql = `UPDATE services SET ${setClauses.join(', ')} WHERE id = ?`;\n const stmt = this.db.prepare(sql);\n const result = stmt.run(...params);\n\n if (result.changes === 0) {\n return undefined;\n }\n\n return this.findById(id);\n }\n\n /** Delete a Service by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM services WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Vhost } from '../../types/entities.js';\nimport type { CreateVhostInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `vhosts` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface VhostRow {\n id: string;\n host_id: string;\n hostname: string;\n source: string | null;\n evidence_artifact_id: string;\n created_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Vhost entity. */\nfunction rowToVhost(row: VhostRow): Vhost {\n return {\n id: row.id,\n hostId: row.host_id,\n hostname: row.hostname,\n source: row.source ?? undefined,\n evidenceArtifactId: row.evidence_artifact_id,\n createdAt: row.created_at,\n };\n}\n\n/**\n * Repository for the `vhosts` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class VhostRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Vhost and return the full entity. */\n create(input: CreateVhostInput): Vhost {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string, string | null, string, string]\n >(\n `INSERT INTO vhosts (id, host_id, hostname, source, evidence_artifact_id, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.hostId,\n input.hostname,\n input.source ?? null,\n input.evidenceArtifactId,\n now,\n );\n\n return {\n id,\n hostId: input.hostId,\n hostname: input.hostname,\n source: input.source,\n evidenceArtifactId: input.evidenceArtifactId,\n createdAt: now,\n };\n }\n\n /** Find a Vhost by its primary key. Returns undefined if not found. */\n findById(id: string): Vhost | undefined {\n const stmt = this.db.prepare<[string], VhostRow>(\n `SELECT id, host_id, hostname, source, evidence_artifact_id, created_at\n FROM vhosts\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToVhost(row) : undefined;\n }\n\n /** Return all Vhosts belonging to a given host. */\n findByHostId(hostId: string): Vhost[] {\n const stmt = this.db.prepare<[string], VhostRow>(\n `SELECT id, host_id, hostname, source, evidence_artifact_id, created_at\n FROM vhosts\n WHERE host_id = ?`,\n );\n\n const rows = stmt.all(hostId);\n return rows.map(rowToVhost);\n }\n\n /** Delete a Vhost by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM vhosts WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { HttpEndpoint } from '../../types/entities.js';\nimport type { CreateHttpEndpointInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `http_endpoints` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface HttpEndpointRow {\n id: string;\n service_id: string;\n vhost_id: string | null;\n base_uri: string;\n method: string;\n path: string;\n status_code: number | null;\n content_length: number | null;\n words: number | null;\n lines: number | null;\n evidence_artifact_id: string;\n created_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase HttpEndpoint entity. */\nfunction rowToHttpEndpoint(row: HttpEndpointRow): HttpEndpoint {\n return {\n id: row.id,\n serviceId: row.service_id,\n vhostId: row.vhost_id ?? undefined,\n baseUri: row.base_uri,\n method: row.method,\n path: row.path,\n statusCode: row.status_code ?? undefined,\n contentLength: row.content_length ?? undefined,\n words: row.words ?? undefined,\n lines: row.lines ?? undefined,\n evidenceArtifactId: row.evidence_artifact_id,\n createdAt: row.created_at,\n };\n}\n\n/**\n * Repository for the `http_endpoints` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class HttpEndpointRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new HttpEndpoint and return the full entity. */\n create(input: CreateHttpEndpointInput): HttpEndpoint {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare(\n `INSERT INTO http_endpoints (id, service_id, vhost_id, base_uri, method, path, status_code, content_length, words, lines, evidence_artifact_id, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.serviceId,\n input.vhostId ?? null,\n input.baseUri,\n input.method,\n input.path,\n input.statusCode ?? null,\n input.contentLength ?? null,\n input.words ?? null,\n input.lines ?? null,\n input.evidenceArtifactId,\n now,\n );\n\n return {\n id,\n serviceId: input.serviceId,\n vhostId: input.vhostId,\n baseUri: input.baseUri,\n method: input.method,\n path: input.path,\n statusCode: input.statusCode,\n contentLength: input.contentLength,\n words: input.words,\n lines: input.lines,\n evidenceArtifactId: input.evidenceArtifactId,\n createdAt: now,\n };\n }\n\n /** Find an HttpEndpoint by its primary key. Returns undefined if not found. */\n findById(id: string): HttpEndpoint | undefined {\n const stmt = this.db.prepare<[string], HttpEndpointRow>(\n `SELECT id, service_id, vhost_id, base_uri, method, path, status_code, content_length, words, lines, evidence_artifact_id, created_at\n FROM http_endpoints\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToHttpEndpoint(row) : undefined;\n }\n\n /** Return all HttpEndpoints for a given service. */\n findByServiceId(serviceId: string): HttpEndpoint[] {\n const stmt = this.db.prepare<[string], HttpEndpointRow>(\n `SELECT id, service_id, vhost_id, base_uri, method, path, status_code, content_length, words, lines, evidence_artifact_id, created_at\n FROM http_endpoints\n WHERE service_id = ?`,\n );\n\n const rows = stmt.all(serviceId);\n return rows.map(rowToHttpEndpoint);\n }\n\n /** Delete an HttpEndpoint by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>(\n 'DELETE FROM http_endpoints WHERE id = ?',\n );\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Input } from '../../types/entities.js';\nimport type { CreateInputInput, UpdateInputInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `inputs` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface InputRow {\n id: string;\n service_id: string;\n location: string;\n name: string;\n type_hint: string | null;\n created_at: string;\n updated_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Input entity. */\nfunction rowToInput(row: InputRow): Input {\n return {\n id: row.id,\n serviceId: row.service_id,\n location: row.location,\n name: row.name,\n typeHint: row.type_hint ?? undefined,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/**\n * Repository for the `inputs` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class InputRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Input and return the full entity. */\n create(input: CreateInputInput): Input {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string, string, string | null, string, string]\n >(\n `INSERT INTO inputs (id, service_id, location, name, type_hint, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.serviceId,\n input.location,\n input.name,\n input.typeHint ?? null,\n now,\n now,\n );\n\n return {\n id,\n serviceId: input.serviceId,\n location: input.location,\n name: input.name,\n typeHint: input.typeHint,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /** Find an Input by its primary key. Returns undefined if not found. */\n findById(id: string): Input | undefined {\n const stmt = this.db.prepare<[string], InputRow>(\n `SELECT id, service_id, location, name, type_hint, created_at, updated_at\n FROM inputs\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToInput(row) : undefined;\n }\n\n /**\n * Find all Inputs for a given service.\n * If location is provided, further filter by location.\n */\n findByServiceId(serviceId: string, location?: string): Input[] {\n if (location !== undefined) {\n const stmt = this.db.prepare<[string, string], InputRow>(\n `SELECT id, service_id, location, name, type_hint, created_at, updated_at\n FROM inputs\n WHERE service_id = ? AND location = ?`,\n );\n\n const rows = stmt.all(serviceId, location);\n return rows.map(rowToInput);\n }\n\n const stmt = this.db.prepare<[string], InputRow>(\n `SELECT id, service_id, location, name, type_hint, created_at, updated_at\n FROM inputs\n WHERE service_id = ?`,\n );\n\n const rows = stmt.all(serviceId);\n return rows.map(rowToInput);\n }\n\n /**\n * Update an existing Input with the provided fields.\n * Always bumps `updated_at`. Returns the updated entity, or undefined if\n * the Input was not found.\n */\n update(id: string, input: UpdateInputInput): Input | undefined {\n const setClauses: string[] = [];\n const params: unknown[] = [];\n\n if (input.typeHint !== undefined) {\n setClauses.push('type_hint = ?');\n params.push(input.typeHint);\n }\n\n const now = new Date().toISOString();\n setClauses.push('updated_at = ?');\n params.push(now);\n\n // Always include the WHERE id\n params.push(id);\n\n const sql = `UPDATE inputs SET ${setClauses.join(', ')} WHERE id = ?`;\n const stmt = this.db.prepare(sql);\n const result = stmt.run(...params);\n\n if (result.changes === 0) {\n return undefined;\n }\n\n return this.findById(id);\n }\n\n /** Delete an Input by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM inputs WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Observation } from '../../types/entities.js';\nimport type { CreateObservationInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `observations` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface ObservationRow {\n id: string;\n input_id: string;\n raw_value: string;\n norm_value: string;\n body_path: string | null;\n source: string;\n confidence: string;\n evidence_artifact_id: string;\n observed_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Observation entity. */\nfunction rowToObservation(row: ObservationRow): Observation {\n return {\n id: row.id,\n inputId: row.input_id,\n rawValue: row.raw_value,\n normValue: row.norm_value,\n bodyPath: row.body_path ?? undefined,\n source: row.source,\n confidence: row.confidence,\n evidenceArtifactId: row.evidence_artifact_id,\n observedAt: row.observed_at,\n };\n}\n\n/**\n * Repository for the `observations` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class ObservationRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Observation and return the full entity. */\n create(input: CreateObservationInput): Observation {\n const id = crypto.randomUUID();\n\n const stmt = this.db.prepare<\n [string, string, string, string, string | null, string, string, string, string]\n >(\n `INSERT INTO observations (id, input_id, raw_value, norm_value, body_path, source, confidence, evidence_artifact_id, observed_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.inputId,\n input.rawValue,\n input.normValue,\n input.bodyPath ?? null,\n input.source,\n input.confidence,\n input.evidenceArtifactId,\n input.observedAt,\n );\n\n return {\n id,\n inputId: input.inputId,\n rawValue: input.rawValue,\n normValue: input.normValue,\n bodyPath: input.bodyPath,\n source: input.source,\n confidence: input.confidence,\n evidenceArtifactId: input.evidenceArtifactId,\n observedAt: input.observedAt,\n };\n }\n\n /** Find an Observation by its primary key. Returns undefined if not found. */\n findById(id: string): Observation | undefined {\n const stmt = this.db.prepare<[string], ObservationRow>(\n `SELECT id, input_id, raw_value, norm_value, body_path, source, confidence, evidence_artifact_id, observed_at\n FROM observations\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToObservation(row) : undefined;\n }\n\n /** Return all Observations for a given input ID. */\n findByInputId(inputId: string): Observation[] {\n const stmt = this.db.prepare<[string], ObservationRow>(\n `SELECT id, input_id, raw_value, norm_value, body_path, source, confidence, evidence_artifact_id, observed_at\n FROM observations\n WHERE input_id = ?`,\n );\n\n const rows = stmt.all(inputId);\n return rows.map(rowToObservation);\n }\n\n /** Delete an Observation by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM observations WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Credential } from '../../types/entities.js';\nimport type { CreateCredentialInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `credentials` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface CredentialRow {\n id: string;\n service_id: string;\n endpoint_id: string | null;\n username: string;\n secret: string;\n secret_type: string;\n source: string;\n confidence: string;\n evidence_artifact_id: string;\n created_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Credential entity. */\nfunction rowToCredential(row: CredentialRow): Credential {\n return {\n id: row.id,\n serviceId: row.service_id,\n endpointId: row.endpoint_id ?? undefined,\n username: row.username,\n secret: row.secret,\n secretType: row.secret_type,\n source: row.source,\n confidence: row.confidence,\n evidenceArtifactId: row.evidence_artifact_id,\n createdAt: row.created_at,\n };\n}\n\n/**\n * Repository for the `credentials` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class CredentialRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Credential and return the full entity. */\n create(input: CreateCredentialInput): Credential {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string | null, string, string, string, string, string, string, string]\n >(\n `INSERT INTO credentials (id, service_id, endpoint_id, username, secret, secret_type, source, confidence, evidence_artifact_id, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.serviceId,\n input.endpointId ?? null,\n input.username,\n input.secret,\n input.secretType,\n input.source,\n input.confidence,\n input.evidenceArtifactId,\n now,\n );\n\n return {\n id,\n serviceId: input.serviceId,\n endpointId: input.endpointId ?? undefined,\n username: input.username,\n secret: input.secret,\n secretType: input.secretType,\n source: input.source,\n confidence: input.confidence,\n evidenceArtifactId: input.evidenceArtifactId,\n createdAt: now,\n };\n }\n\n /** Find a Credential by its primary key. Returns undefined if not found. */\n findById(id: string): Credential | undefined {\n const stmt = this.db.prepare<[string], CredentialRow>(\n `SELECT id, service_id, endpoint_id, username, secret, secret_type, source, confidence, evidence_artifact_id, created_at\n FROM credentials\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToCredential(row) : undefined;\n }\n\n /** Return all Credentials for a given service. */\n findByServiceId(serviceId: string): Credential[] {\n const stmt = this.db.prepare<[string], CredentialRow>(\n `SELECT id, service_id, endpoint_id, username, secret, secret_type, source, confidence, evidence_artifact_id, created_at\n FROM credentials\n WHERE service_id = ?`,\n );\n\n const rows = stmt.all(serviceId);\n return rows.map(rowToCredential);\n }\n\n /** Return all Credentials across all services. */\n findAll(): Credential[] {\n const stmt = this.db.prepare<[], CredentialRow>(\n `SELECT id, service_id, endpoint_id, username, secret, secret_type, source, confidence, evidence_artifact_id, created_at\n FROM credentials`,\n );\n\n const rows = stmt.all();\n return rows.map(rowToCredential);\n }\n\n /** Delete a Credential by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM credentials WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Vulnerability } from '../../types/entities.js';\nimport type { CreateVulnerabilityInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `vulnerabilities` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface VulnerabilityRow {\n id: string;\n service_id: string;\n endpoint_id: string | null;\n vuln_type: string;\n title: string;\n description: string | null;\n severity: string;\n confidence: string;\n evidence_artifact_id: string;\n created_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Vulnerability entity. */\nfunction rowToVulnerability(row: VulnerabilityRow): Vulnerability {\n return {\n id: row.id,\n serviceId: row.service_id,\n endpointId: row.endpoint_id ?? undefined,\n vulnType: row.vuln_type,\n title: row.title,\n description: row.description ?? undefined,\n severity: row.severity,\n confidence: row.confidence,\n evidenceArtifactId: row.evidence_artifact_id,\n createdAt: row.created_at,\n };\n}\n\n/**\n * Repository for the `vulnerabilities` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class VulnerabilityRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Vulnerability and return the full entity. */\n create(input: CreateVulnerabilityInput): Vulnerability {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare(\n `INSERT INTO vulnerabilities (id, service_id, endpoint_id, vuln_type, title, description, severity, confidence, evidence_artifact_id, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.serviceId,\n input.endpointId ?? null,\n input.vulnType,\n input.title,\n input.description ?? null,\n input.severity,\n input.confidence,\n input.evidenceArtifactId,\n now,\n );\n\n return {\n id,\n serviceId: input.serviceId,\n endpointId: input.endpointId,\n vulnType: input.vulnType,\n title: input.title,\n description: input.description,\n severity: input.severity,\n confidence: input.confidence,\n evidenceArtifactId: input.evidenceArtifactId,\n createdAt: now,\n };\n }\n\n /** Find a Vulnerability by its primary key. Returns undefined if not found. */\n findById(id: string): Vulnerability | undefined {\n const stmt = this.db.prepare<[string], VulnerabilityRow>(\n `SELECT id, service_id, endpoint_id, vuln_type, title, description, severity, confidence, evidence_artifact_id, created_at\n FROM vulnerabilities\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToVulnerability(row) : undefined;\n }\n\n /**\n * Return all Vulnerabilities for a given service.\n * Optionally filter by severity when the parameter is provided.\n */\n findByServiceId(serviceId: string, severity?: string): Vulnerability[] {\n if (severity !== undefined) {\n const stmt = this.db.prepare<[string, string], VulnerabilityRow>(\n `SELECT id, service_id, endpoint_id, vuln_type, title, description, severity, confidence, evidence_artifact_id, created_at\n FROM vulnerabilities\n WHERE service_id = ? AND severity = ?`,\n );\n\n const rows = stmt.all(serviceId, severity);\n return rows.map(rowToVulnerability);\n }\n\n const stmt = this.db.prepare<[string], VulnerabilityRow>(\n `SELECT id, service_id, endpoint_id, vuln_type, title, description, severity, confidence, evidence_artifact_id, created_at\n FROM vulnerabilities\n WHERE service_id = ?`,\n );\n\n const rows = stmt.all(serviceId);\n return rows.map(rowToVulnerability);\n }\n\n /**\n * Return all Vulnerabilities across all services.\n * Optionally filter by severity when the parameter is provided.\n */\n findAll(severity?: string): Vulnerability[] {\n if (severity !== undefined) {\n const stmt = this.db.prepare<[string], VulnerabilityRow>(\n `SELECT id, service_id, endpoint_id, vuln_type, title, description, severity, confidence, evidence_artifact_id, created_at\n FROM vulnerabilities\n WHERE severity = ?`,\n );\n\n const rows = stmt.all(severity);\n return rows.map(rowToVulnerability);\n }\n\n const stmt = this.db.prepare<[], VulnerabilityRow>(\n `SELECT id, service_id, endpoint_id, vuln_type, title, description, severity, confidence, evidence_artifact_id, created_at\n FROM vulnerabilities`,\n );\n\n const rows = stmt.all();\n return rows.map(rowToVulnerability);\n }\n\n /** Delete a Vulnerability by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM vulnerabilities WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","/**\n * sonobat — MCP Ingest Tool\n *\n * Tool for ingesting tool output files into the AttackDataGraph.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { ingest } from '../../engine/ingest.js';\n\nexport function registerIngestTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'ingest_file',\n 'Ingest a tool output file (nmap XML, ffuf JSON, nuclei JSONL) into the AttackDataGraph',\n {\n path: z.string().describe('Absolute path to the tool output file'),\n tool: z.enum(['nmap', 'ffuf', 'nuclei']).describe('Tool that produced the output'),\n },\n async ({ path, tool }) => {\n try {\n const result = ingest(db, { path, tool });\n const nr = result.normalizeResult;\n const summary = [\n `Ingested ${tool} output from ${path}`,\n `Artifact ID: ${result.artifactId}`,\n `Created: ${nr.hostsCreated} hosts, ${nr.servicesCreated} services, ${nr.httpEndpointsCreated} endpoints, ${nr.inputsCreated} inputs, ${nr.observationsCreated} observations, ${nr.vulnerabilitiesCreated} vulnerabilities, ${nr.cvesCreated} CVEs`,\n ].join('\\n');\n return { content: [{ type: 'text', text: summary }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: 'text', text: `Ingest failed: ${message}` }], isError: true };\n }\n },\n );\n}\n","/**\n * sonobat — Ingest Engine\n *\n * ツール出力ファイルを読み込み、パース・正規化してDBに格納する。\n * ingestContent() はコアロジック(ファイルシステム非依存・テスト可能)。\n * ingest() はファイル読み込みの薄いラッパー。\n */\n\nimport type Database from 'better-sqlite3';\nimport fs from 'node:fs';\nimport crypto from 'node:crypto';\nimport path from 'node:path';\nimport type { IngestInput, IngestResult } from '../types/engine.js';\nimport { ArtifactRepository } from '../db/repository/artifact-repository.js';\nimport { parseNmapXml } from '../parser/nmap-parser.js';\nimport { parseFfufJson } from '../parser/ffuf-parser.js';\nimport { parseNucleiJsonl } from '../parser/nuclei-parser.js';\nimport { normalize } from './normalizer.js';\n\n/**\n * ツール出力の文字列を直接受け取り、パース・正規化してDBに格納する。\n * ファイルシステムに依存しないため、テストから直接呼び出せる。\n *\n * @param db better-sqlite3 の Database インスタンス\n * @param tool ツール種別('nmap' | 'ffuf' | 'nuclei')\n * @param content ツール出力の文字列\n * @param filePath Artifact に記録するファイルパス\n * @returns IngestResult(artifactId + normalizeResult)\n */\nexport function ingestContent(\n db: Database.Database,\n tool: 'nmap' | 'ffuf' | 'nuclei',\n content: string,\n filePath: string,\n): IngestResult {\n // 1. SHA-256 ハッシュを計算\n const sha256 = crypto.createHash('sha256').update(content).digest('hex');\n\n // 2. Artifact を作成\n const artifactRepo = new ArtifactRepository(db);\n const artifact = artifactRepo.create({\n tool,\n kind: 'tool_output',\n path: filePath,\n sha256,\n capturedAt: new Date().toISOString(),\n });\n\n // 3. ツール種別に応じてパース\n let parseResult;\n switch (tool) {\n case 'nmap':\n parseResult = parseNmapXml(content);\n break;\n case 'ffuf':\n parseResult = parseFfufJson(content);\n break;\n case 'nuclei':\n parseResult = parseNucleiJsonl(content);\n break;\n default: {\n // never 型による網羅性チェック — 未知の tool が渡された場合はコンパイルエラー\n const _exhaustive: never = tool;\n throw new Error(`Unknown tool: ${String(_exhaustive)}`);\n }\n }\n\n // 4. 正規化してDBに書き込む\n const normalizeResult = normalize(db, artifact.id, parseResult);\n\n // 5. 結果を返す\n return {\n artifactId: artifact.id,\n normalizeResult,\n };\n}\n\n/**\n * ファイルパスからツール出力を読み込み、インジェストする。\n * ingestContent() の薄いラッパー。\n *\n * @param db better-sqlite3 の Database インスタンス\n * @param input IngestInput(path + tool)\n * @returns IngestResult\n */\nexport function ingest(db: Database.Database, input: IngestInput): IngestResult {\n const resolved = path.resolve(input.path);\n const content = fs.readFileSync(resolved, 'utf-8');\n return ingestContent(db, input.tool, content, resolved);\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Artifact } from '../../types/entities.js';\nimport type { CreateArtifactInput } from '../../types/repository.js';\n\n/** Row shape returned by better-sqlite3 for the artifacts table. */\ninterface ArtifactRow {\n id: string;\n scan_id: string | null;\n tool: string;\n kind: string;\n path: string;\n sha256: string | null;\n captured_at: string;\n attrs_json: string | null;\n}\n\n/** Maps a snake_case DB row to a camelCase Artifact entity. */\nfunction rowToArtifact(row: ArtifactRow): Artifact {\n return {\n id: row.id,\n ...(row.scan_id !== null ? { scanId: row.scan_id } : {}),\n tool: row.tool,\n kind: row.kind,\n path: row.path,\n ...(row.sha256 !== null ? { sha256: row.sha256 } : {}),\n capturedAt: row.captured_at,\n ...(row.attrs_json !== null ? { attrsJson: row.attrs_json } : {}),\n };\n}\n\n/**\n * Repository for the `artifacts` table.\n *\n * Provides CRUD operations with camelCase ↔ snake_case mapping\n * between the TypeScript entity layer and the SQLite storage layer.\n */\nexport class ArtifactRepository {\n private readonly db: Database.Database;\n\n private readonly insertStmt: Database.Statement;\n private readonly selectByIdStmt: Database.Statement;\n private readonly selectAllStmt: Database.Statement;\n private readonly selectByToolStmt: Database.Statement;\n\n constructor(db: Database.Database) {\n this.db = db;\n\n this.insertStmt = this.db.prepare(\n 'INSERT INTO artifacts (id, scan_id, tool, kind, path, sha256, captured_at, attrs_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',\n );\n\n this.selectByIdStmt = this.db.prepare(\n 'SELECT id, scan_id, tool, kind, path, sha256, captured_at, attrs_json FROM artifacts WHERE id = ?',\n );\n\n this.selectAllStmt = this.db.prepare(\n 'SELECT id, scan_id, tool, kind, path, sha256, captured_at, attrs_json FROM artifacts',\n );\n\n this.selectByToolStmt = this.db.prepare(\n 'SELECT id, scan_id, tool, kind, path, sha256, captured_at, attrs_json FROM artifacts WHERE tool = ?',\n );\n }\n\n /** Create a new Artifact record and return the full entity. */\n create(input: CreateArtifactInput): Artifact {\n const id = crypto.randomUUID();\n\n this.insertStmt.run(\n id,\n input.scanId ?? null,\n input.tool,\n input.kind,\n input.path,\n input.sha256 ?? null,\n input.capturedAt,\n input.attrsJson ?? null,\n );\n\n return {\n id,\n ...(input.scanId !== undefined ? { scanId: input.scanId } : {}),\n tool: input.tool,\n kind: input.kind,\n path: input.path,\n ...(input.sha256 !== undefined ? { sha256: input.sha256 } : {}),\n capturedAt: input.capturedAt,\n ...(input.attrsJson !== undefined ? { attrsJson: input.attrsJson } : {}),\n };\n }\n\n /** Find an Artifact by its UUID. Returns undefined if not found. */\n findById(id: string): Artifact | undefined {\n const row = this.selectByIdStmt.get(id) as ArtifactRow | undefined;\n if (row === undefined) {\n return undefined;\n }\n return rowToArtifact(row);\n }\n\n /** Return all Artifact records. */\n findAll(): Artifact[] {\n const rows = this.selectAllStmt.all() as ArtifactRow[];\n return rows.map(rowToArtifact);\n }\n\n /** Return all Artifact records for the given tool name. */\n findByTool(tool: string): Artifact[] {\n const rows = this.selectByToolStmt.all(tool) as ArtifactRow[];\n return rows.map(rowToArtifact);\n }\n}\n","/**\n * sonobat — Nmap XML パーサー\n *\n * nmap の XML 出力を解析し、ParseResult 中間表現を返す。\n * fast-xml-parser を使用して XML をパースする。\n */\n\nimport { XMLParser } from 'fast-xml-parser';\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedServiceObservation,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ============================================================\n// XML パース後の型定義(unknown から安全に取り出すための構造)\n// ============================================================\n\ninterface NmapAddress {\n '@_addr': string;\n '@_addrtype': string;\n}\n\ninterface NmapHostname {\n '@_name': string;\n '@_type': string;\n}\n\ninterface NmapPortState {\n '@_state': string;\n '@_reason'?: string;\n}\n\ninterface NmapServiceAttr {\n '@_name'?: string;\n '@_product'?: string;\n '@_version'?: string;\n '@_extrainfo'?: string;\n '@_tunnel'?: string;\n '@_conf'?: string;\n}\n\ninterface NmapPort {\n '@_protocol': string;\n '@_portid': string;\n state: NmapPortState;\n service?: NmapServiceAttr;\n}\n\ninterface NmapOsMatch {\n '@_name': string;\n '@_accuracy': string;\n}\n\ninterface NmapHost {\n address: NmapAddress | NmapAddress[];\n hostnames?: {\n hostname?: NmapHostname | NmapHostname[];\n };\n ports?: {\n port?: NmapPort | NmapPort[];\n };\n os?: {\n osmatch?: NmapOsMatch | NmapOsMatch[];\n };\n}\n\ninterface NmapRun {\n nmaprun: {\n host?: NmapHost | NmapHost[];\n };\n}\n\n// ============================================================\n// ユーティリティ\n// ============================================================\n\n/** 値を配列に正規化する。undefined/null は空配列を返す。 */\nfunction ensureArray<T>(value: T | T[] | undefined | null): T[] {\n if (value === undefined || value === null) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\n/** nmap の conf 属性から protoConfidence を決定する */\nfunction toProtoConfidence(conf: string | undefined): string {\n const n = conf !== undefined ? Number(conf) : 0;\n if (n === 10) return 'high';\n if (n >= 7) return 'medium';\n return 'low';\n}\n\n/** OS accuracy から confidence を決定する */\nfunction toOsConfidence(accuracy: string): string {\n const n = Number(accuracy);\n if (n >= 90) return 'high';\n if (n >= 50) return 'medium';\n return 'low';\n}\n\n/** サービス名が HTTPS を示すかどうかを判定する */\nfunction isHttps(service: NmapServiceAttr): boolean {\n return service['@_name'] === 'https' || service['@_tunnel'] === 'ssl';\n}\n\n/** product, version, extrainfo からバナー文字列を合成する */\nfunction buildBanner(service: NmapServiceAttr): string | undefined {\n const parts: string[] = [];\n if (service['@_product']) parts.push(service['@_product']);\n if (service['@_version']) parts.push(service['@_version']);\n if (service['@_extrainfo']) parts.push(service['@_extrainfo']);\n return parts.length > 0 ? parts.join(' ') : undefined;\n}\n\n/** IPv4 アドレスを address 配列から取得する */\nfunction getIpv4Address(addresses: NmapAddress[]): string | undefined {\n const ipv4 = addresses.find((a) => a['@_addrtype'] === 'ipv4');\n return ipv4?.['@_addr'];\n}\n\n// ============================================================\n// メインパーサー\n// ============================================================\n\n/**\n * nmap XML 出力をパースし、ParseResult を返す。\n *\n * @param xml - nmap の XML 出力文字列\n * @returns ParseResult 中間表現\n */\nexport function parseNmapXml(xml: string): ParseResult {\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n allowBooleanAttributes: true,\n });\n\n const parsed: unknown = parser.parse(xml);\n const nmapRun = parsed as NmapRun;\n\n const result = emptyParseResult();\n\n const hosts = ensureArray(nmapRun.nmaprun?.host);\n\n for (const host of hosts) {\n processHost(host, result);\n }\n\n return result;\n}\n\n/**\n * 単一の host 要素を処理し、結果に追加する。\n */\nfunction processHost(host: NmapHost, result: ParseResult): void {\n // --- ホスト情報 ---\n const addresses = ensureArray(host.address);\n const authority = getIpv4Address(addresses);\n if (authority === undefined) {\n return; // IPv4 アドレスがないホストはスキップ\n }\n\n const parsedHost: ParsedHost = {\n authority,\n authorityKind: 'IP',\n };\n result.hosts.push(parsedHost);\n\n // --- サービス情報 ---\n const ports = ensureArray(host.ports?.port);\n const services: ParsedService[] = [];\n\n for (const port of ports) {\n const service = processPort(port, authority);\n services.push(service);\n result.services.push(service);\n }\n\n // --- OS 情報 ---\n const osMatches = ensureArray(host.os?.osmatch);\n if (osMatches.length > 0) {\n processOsMatches(osMatches, authority, services, result);\n }\n}\n\n/**\n * 単一の port 要素を処理し、ParsedService を返す。\n */\nfunction processPort(port: NmapPort, hostAuthority: string): ParsedService {\n const service = port.service;\n const serviceName = service?.['@_name'] ?? '';\n\n // appProto の決定: https or tunnel=ssl -> 'https'\n const appProto =\n service !== undefined && isHttps(service) ? 'https' : serviceName;\n\n const banner = service !== undefined ? buildBanner(service) : undefined;\n const protoConfidence = toProtoConfidence(service?.['@_conf']);\n\n return {\n hostAuthority,\n transport: port['@_protocol'],\n port: Number(port['@_portid']),\n appProto,\n protoConfidence,\n banner,\n product: service?.['@_product'],\n version: service?.['@_version'],\n state: port.state['@_state'],\n };\n}\n\n/**\n * OS マッチ情報を serviceObservations に追加する。\n * 最初のサービスの transport/port を使う。サービスがない場合は port=0, transport='tcp' を使う。\n */\nfunction processOsMatches(\n osMatches: NmapOsMatch[],\n hostAuthority: string,\n services: ParsedService[],\n result: ParseResult,\n): void {\n const firstService = services.length > 0 ? services[0] : undefined;\n const transport = firstService?.transport ?? 'tcp';\n const port = firstService?.port ?? 0;\n\n for (const osMatch of osMatches) {\n const observation: ParsedServiceObservation = {\n hostAuthority,\n transport,\n port,\n key: 'os',\n value: osMatch['@_name'],\n confidence: toOsConfidence(osMatch['@_accuracy']),\n };\n result.serviceObservations.push(observation);\n }\n}\n","/**\n * sonobat — Parser intermediate types\n *\n * パーサーは DB の ID を持たない中間表現を返す。\n * Normalizer が自然キー(authority, port 等)で DB を検索・upsert する。\n */\n\n// ============================================================\n// 中間表現(DB ID を持たない)\n// ============================================================\n\n/** ホストの中間表現 */\nexport interface ParsedHost {\n authority: string;\n authorityKind: 'IP' | 'DOMAIN';\n resolvedIps?: string[];\n}\n\n/** サービスの中間表現 */\nexport interface ParsedService {\n hostAuthority: string;\n transport: string;\n port: number;\n appProto: string;\n protoConfidence: string;\n banner?: string;\n product?: string;\n version?: string;\n state: string;\n}\n\n/** サービス観測の中間表現 */\nexport interface ParsedServiceObservation {\n hostAuthority: string;\n transport: string;\n port: number;\n key: string;\n value: string;\n confidence: string;\n}\n\n/** HTTP エンドポイントの中間表現 */\nexport interface ParsedHttpEndpoint {\n hostAuthority: string;\n port: number;\n baseUri: string;\n method: string;\n path: string;\n statusCode?: number;\n contentLength?: number;\n words?: number;\n lines?: number;\n}\n\n/** 入力パラメータの中間表現 */\nexport interface ParsedInput {\n hostAuthority: string;\n port: number;\n location: string;\n name: string;\n typeHint?: string;\n}\n\n/** エンドポイント ↔ 入力の紐づけ中間表現 */\nexport interface ParsedEndpointInput {\n hostAuthority: string;\n port: number;\n method: string;\n path: string;\n inputLocation: string;\n inputName: string;\n}\n\n/** 観測値の中間表現 */\nexport interface ParsedObservation {\n hostAuthority: string;\n port: number;\n inputLocation: string;\n inputName: string;\n rawValue: string;\n normValue: string;\n source: string;\n confidence: string;\n}\n\n/** 脆弱性の中間表現 */\nexport interface ParsedVulnerability {\n hostAuthority: string;\n port: number;\n method?: string;\n path?: string;\n vulnType: string;\n title: string;\n description?: string;\n severity: string;\n confidence: string;\n}\n\n/** CVE の中間表現 */\nexport interface ParsedCve {\n /** 対応する脆弱性の title(紐づけ用) */\n vulnerabilityTitle: string;\n cveId: string;\n description?: string;\n cvssScore?: number;\n cvssVector?: string;\n referenceUrl?: string;\n}\n\n// ============================================================\n// パース結果\n// ============================================================\n\n/** パーサーが返す統一的な結果型 */\nexport interface ParseResult {\n hosts: ParsedHost[];\n services: ParsedService[];\n serviceObservations: ParsedServiceObservation[];\n httpEndpoints: ParsedHttpEndpoint[];\n inputs: ParsedInput[];\n endpointInputs: ParsedEndpointInput[];\n observations: ParsedObservation[];\n vulnerabilities: ParsedVulnerability[];\n cves: ParsedCve[];\n}\n\n/** 空の ParseResult を生成するユーティリティ */\nexport function emptyParseResult(): ParseResult {\n return {\n hosts: [],\n services: [],\n serviceObservations: [],\n httpEndpoints: [],\n inputs: [],\n endpointInputs: [],\n observations: [],\n vulnerabilities: [],\n cves: [],\n };\n}\n","/**\n * sonobat — ffuf JSON output parser\n *\n * ffuf の JSON 出力をパースし、ParseResult に変換する。\n * パスディスカバリ、パラメータファジングの両方に対応。\n */\n\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedHttpEndpoint,\n ParsedInput,\n ParsedEndpointInput,\n ParsedObservation,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ---------------------------------------------------------------------------\n// 内部型: ffuf JSON 構造のバリデーション用\n// ---------------------------------------------------------------------------\n\ninterface FfufConfig {\n url: string;\n method: string;\n}\n\ninterface FfufResult {\n input: Record<string, string>;\n status: number;\n length: number;\n words: number;\n lines: number;\n url: string;\n host: string;\n}\n\ninterface FfufJson {\n commandline: string;\n config: FfufConfig;\n results: FfufResult[];\n}\n\n// ---------------------------------------------------------------------------\n// バリデーション\n// ---------------------------------------------------------------------------\n\nconst IP_REGEX = /^\\d{1,3}(\\.\\d{1,3}){3}$/;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction validateFfufJson(raw: unknown): FfufJson {\n if (!isRecord(raw)) {\n throw new Error('ffuf JSON: root must be an object');\n }\n\n if (typeof raw['commandline'] !== 'string') {\n throw new Error('ffuf JSON: commandline must be a string');\n }\n\n const config = raw['config'];\n if (!isRecord(config)) {\n throw new Error('ffuf JSON: config must be an object');\n }\n if (typeof config['url'] !== 'string' || typeof config['method'] !== 'string') {\n throw new Error('ffuf JSON: config.url and config.method must be strings');\n }\n\n if (!Array.isArray(raw['results'])) {\n throw new Error('ffuf JSON: results must be an array');\n }\n\n const results: FfufResult[] = [];\n for (const item of raw['results'] as unknown[]) {\n if (!isRecord(item)) {\n throw new Error('ffuf JSON: each result must be an object');\n }\n results.push({\n input: isRecord(item['input'])\n ? Object.fromEntries(\n Object.entries(item['input'] as Record<string, unknown>).map(([k, v]) => [\n k,\n String(v),\n ]),\n )\n : {},\n status: typeof item['status'] === 'number' ? item['status'] : 0,\n length: typeof item['length'] === 'number' ? item['length'] : 0,\n words: typeof item['words'] === 'number' ? item['words'] : 0,\n lines: typeof item['lines'] === 'number' ? item['lines'] : 0,\n url: typeof item['url'] === 'string' ? item['url'] : '',\n host: typeof item['host'] === 'string' ? item['host'] : '',\n });\n }\n\n return {\n commandline: raw['commandline'] as string,\n config: {\n url: config['url'] as string,\n method: config['method'] as string,\n },\n results,\n };\n}\n\n// ---------------------------------------------------------------------------\n// URL ユーティリティ\n// ---------------------------------------------------------------------------\n\ninterface ParsedUrl {\n scheme: string;\n hostname: string;\n port: number;\n pathname: string;\n searchParams: URLSearchParams;\n}\n\nfunction parseUrl(urlStr: string): ParsedUrl {\n const parsed = new URL(urlStr);\n const scheme = parsed.protocol.replace(':', '');\n\n let port: number;\n if (parsed.port !== '') {\n port = Number(parsed.port);\n } else {\n port = scheme === 'https' ? 443 : 80;\n }\n\n return {\n scheme,\n hostname: parsed.hostname,\n port,\n pathname: parsed.pathname,\n searchParams: parsed.searchParams,\n };\n}\n\nfunction determineAuthorityKind(hostname: string): 'IP' | 'DOMAIN' {\n return IP_REGEX.test(hostname) ? 'IP' : 'DOMAIN';\n}\n\n// ---------------------------------------------------------------------------\n// メインパーサー\n// ---------------------------------------------------------------------------\n\n/**\n * ffuf の JSON 出力文字列をパースし、ParseResult を返す。\n *\n * @param jsonContent - ffuf が `-of json` で出力した JSON 文字列\n * @returns ParseResult\n */\nexport function parseFfufJson(jsonContent: string): ParseResult {\n const raw: unknown = JSON.parse(jsonContent);\n const ffuf = validateFfufJson(raw);\n const method = ffuf.config.method;\n\n // results が空なら全て空配列\n if (ffuf.results.length === 0) {\n return emptyParseResult();\n }\n\n // ----- 集約用 Map -----\n // hosts: authority -> ParsedHost\n const hostsMap = new Map<string, ParsedHost>();\n // services: \"authority:port\" -> ParsedService\n const servicesMap = new Map<string, ParsedService>();\n // httpEndpoints: \"method:path\" -> ParsedHttpEndpoint\n const endpointsMap = new Map<string, ParsedHttpEndpoint>();\n // inputs: \"name\" -> ParsedInput (クエリパラメータ名で重複排除)\n const inputsMap = new Map<string, ParsedInput>();\n // endpointInputs: \"method:path:location:name\" -> ParsedEndpointInput\n const endpointInputsMap = new Map<string, ParsedEndpointInput>();\n // observations: \"location:name:rawValue\" -> ParsedObservation\n const observationsMap = new Map<string, ParsedObservation>();\n\n for (const result of ffuf.results) {\n if (result.url === '') {\n continue;\n }\n\n const parsed = parseUrl(result.url);\n const { scheme, hostname, port, pathname, searchParams } = parsed;\n const authorityKind = determineAuthorityKind(hostname);\n const baseUri = `${scheme}://${hostname}:${port}`;\n\n // --- Host ---\n if (!hostsMap.has(hostname)) {\n hostsMap.set(hostname, {\n authority: hostname,\n authorityKind,\n });\n }\n\n // --- Service ---\n const serviceKey = `${hostname}:${port}`;\n if (!servicesMap.has(serviceKey)) {\n servicesMap.set(serviceKey, {\n hostAuthority: hostname,\n transport: 'tcp',\n port,\n appProto: scheme,\n protoConfidence: 'high',\n state: 'open',\n });\n }\n\n // --- HTTP Endpoint (method + path で重複排除) ---\n const endpointKey = `${method}:${pathname}`;\n if (!endpointsMap.has(endpointKey)) {\n endpointsMap.set(endpointKey, {\n hostAuthority: hostname,\n port,\n baseUri,\n method,\n path: pathname,\n statusCode: result.status,\n contentLength: result.length,\n words: result.words,\n lines: result.lines,\n });\n }\n\n // --- Query Parameters -> inputs, observations, endpointInputs ---\n for (const [paramName, paramValue] of searchParams.entries()) {\n // Input (パラメータ名で重複排除)\n if (!inputsMap.has(paramName)) {\n inputsMap.set(paramName, {\n hostAuthority: hostname,\n port,\n location: 'query',\n name: paramName,\n });\n }\n\n // EndpointInput (endpoint + input の組み合わせで重複排除)\n const eiKey = `${method}:${pathname}:query:${paramName}`;\n if (!endpointInputsMap.has(eiKey)) {\n endpointInputsMap.set(eiKey, {\n hostAuthority: hostname,\n port,\n method,\n path: pathname,\n inputLocation: 'query',\n inputName: paramName,\n });\n }\n\n // Observation (パラメータ名 + 値で重複排除)\n const obsKey = `query:${paramName}:${paramValue}`;\n if (!observationsMap.has(obsKey)) {\n observationsMap.set(obsKey, {\n hostAuthority: hostname,\n port,\n inputLocation: 'query',\n inputName: paramName,\n rawValue: paramValue,\n normValue: paramValue,\n source: 'ffuf_url',\n confidence: 'high',\n });\n }\n }\n }\n\n return {\n hosts: [...hostsMap.values()],\n services: [...servicesMap.values()],\n serviceObservations: [],\n httpEndpoints: [...endpointsMap.values()],\n inputs: [...inputsMap.values()],\n endpointInputs: [...endpointInputsMap.values()],\n observations: [...observationsMap.values()],\n vulnerabilities: [],\n cves: [],\n };\n}\n","/**\r\n * sonobat — Nuclei JSONL パーサー\r\n *\r\n * nuclei の JSONL 出力を解析し、ParseResult 中間表現を返す。\r\n * 各行は独立した JSON オブジェクト(nuclei finding)として処理する。\r\n */\r\n\r\nimport type {\r\n ParseResult,\r\n ParsedHost,\r\n ParsedService,\r\n ParsedHttpEndpoint,\r\n ParsedVulnerability,\r\n ParsedCve,\r\n} from '../types/parser.js';\r\nimport { emptyParseResult } from '../types/parser.js';\r\n\r\n// ============================================================\r\n// nuclei finding の型定義(unknown から安全に取り出すための構造)\r\n// ============================================================\r\n\r\ninterface NucleiClassification {\r\n 'cve-id'?: string[];\r\n 'cvss-metrics'?: string;\r\n 'cvss-score'?: number;\r\n}\r\n\r\ninterface NucleiInfo {\r\n name: string;\r\n severity: string;\r\n tags: string[];\r\n classification?: NucleiClassification;\r\n}\r\n\r\ninterface NucleiFinding {\r\n 'template-id': string;\r\n info: NucleiInfo;\r\n type: string;\r\n host: string;\r\n 'matched-at': string;\r\n ip: string;\r\n port: string;\r\n scheme: string;\r\n url: string;\r\n}\r\n\r\n// ============================================================\r\n// 型ガード\r\n// ============================================================\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction isStringArray(value: unknown): value is string[] {\r\n return (\r\n Array.isArray(value) && value.every((item) => typeof item === 'string')\r\n );\r\n}\r\n\r\nfunction isNucleiClassification(\r\n value: unknown,\r\n): value is NucleiClassification {\r\n if (!isRecord(value)) return false;\r\n // classification は空オブジェクトでも許容する\r\n if ('cve-id' in value && !isStringArray(value['cve-id'])) return false;\r\n if (\r\n 'cvss-metrics' in value &&\r\n typeof value['cvss-metrics'] !== 'string' &&\r\n value['cvss-metrics'] !== undefined\r\n )\r\n return false;\r\n if (\r\n 'cvss-score' in value &&\r\n typeof value['cvss-score'] !== 'number' &&\r\n value['cvss-score'] !== undefined\r\n )\r\n return false;\r\n return true;\r\n}\r\n\r\nfunction isNucleiInfo(value: unknown): value is NucleiInfo {\r\n if (!isRecord(value)) return false;\r\n if (typeof value.name !== 'string') return false;\r\n if (typeof value.severity !== 'string') return false;\r\n if (!isStringArray(value.tags)) return false;\r\n if (\r\n 'classification' in value &&\r\n value.classification !== undefined &&\r\n !isNucleiClassification(value.classification)\r\n )\r\n return false;\r\n return true;\r\n}\r\n\r\nfunction isNucleiFinding(value: unknown): value is NucleiFinding {\r\n if (!isRecord(value)) return false;\r\n if (typeof value['template-id'] !== 'string') return false;\r\n if (!isNucleiInfo(value.info)) return false;\r\n if (typeof value.type !== 'string') return false;\r\n if (typeof value.host !== 'string') return false;\r\n if (typeof value['matched-at'] !== 'string') return false;\r\n if (typeof value.ip !== 'string') return false;\r\n if (typeof value.port !== 'string') return false;\r\n if (typeof value.scheme !== 'string') return false;\r\n if (typeof value.url !== 'string') return false;\r\n return true;\r\n}\r\n\r\n// ============================================================\r\n// URL パス抽出(生文字列から、デコードせずに抽出する)\r\n// ============================================================\r\n\r\n/**\r\n * URL 文字列から pathname 部分を生文字列のまま抽出する。\r\n * Node.js の URL クラスは %2e を . にデコードしてパスを正規化してしまうため、\r\n * パストラバーサル系のペイロードを保存するには raw なパスが必要。\r\n */\r\nfunction extractRawPathname(urlStr: string): string {\r\n // scheme://authority の後のパス部分を取り出す\r\n // authority の終わり = 3つ目の / の位置(scheme://host:port/path...)\r\n const schemeEnd = urlStr.indexOf('://');\r\n if (schemeEnd === -1) {\r\n return '/';\r\n }\r\n const afterAuthority = urlStr.indexOf('/', schemeEnd + 3);\r\n if (afterAuthority === -1) {\r\n return '/';\r\n }\r\n // パスの終わり = ? または # の最初の出現位置\r\n const queryStart = urlStr.indexOf('?', afterAuthority);\r\n const fragmentStart = urlStr.indexOf('#', afterAuthority);\r\n let pathEnd = urlStr.length;\r\n if (queryStart !== -1 && queryStart < pathEnd) {\r\n pathEnd = queryStart;\r\n }\r\n if (fragmentStart !== -1 && fragmentStart < pathEnd) {\r\n pathEnd = fragmentStart;\r\n }\r\n return urlStr.substring(afterAuthority, pathEnd);\r\n}\r\n\r\n// ============================================================\r\n// vulnType 推定\r\n// ============================================================\r\n\r\n/** タグ配列から vulnType を推定する。優先度順に判定する。 */\r\nfunction inferVulnType(tags: string[]): string {\r\n const priorityTags = ['sqli', 'xss', 'rce', 'lfi', 'ssrf'] as const;\r\n for (const tag of priorityTags) {\r\n if (tags.includes(tag)) {\r\n return tag;\r\n }\r\n }\r\n return 'other';\r\n}\r\n\r\n// ============================================================\r\n// メインパーサー\r\n// ============================================================\r\n\r\n/**\r\n * nuclei JSONL 出力をパースし、ParseResult を返す。\r\n *\r\n * @param jsonl - nuclei の JSONL 出力文字列(1行1JSON)\r\n * @returns ParseResult 中間表現\r\n */\r\nexport function parseNucleiJsonl(jsonl: string): ParseResult {\r\n const result = emptyParseResult();\r\n\r\n if (jsonl.trim() === '') {\r\n return result;\r\n }\r\n\r\n const lines = jsonl.split('\\n');\r\n const seenHosts = new Set<string>();\r\n const seenServices = new Set<string>();\r\n\r\n for (const line of lines) {\r\n const trimmed = line.trim();\r\n if (trimmed === '') {\r\n continue;\r\n }\r\n\r\n const parsed: unknown = JSON.parse(trimmed);\r\n if (!isNucleiFinding(parsed)) {\r\n continue;\r\n }\r\n\r\n processFinding(parsed, result, seenHosts, seenServices);\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * 単一の nuclei finding を処理し、結果に追加する。\r\n */\r\nfunction processFinding(\r\n finding: NucleiFinding,\r\n result: ParseResult,\r\n seenHosts: Set<string>,\r\n seenServices: Set<string>,\r\n): void {\r\n const ip = finding.ip;\r\n const port = Number(finding.port);\r\n const scheme = finding.scheme;\r\n const matchedAt = finding['matched-at'];\r\n\r\n // --- ホスト(重複排除) ---\r\n if (!seenHosts.has(ip)) {\r\n seenHosts.add(ip);\r\n const host: ParsedHost = {\r\n authority: ip,\r\n authorityKind: 'IP',\r\n };\r\n result.hosts.push(host);\r\n }\r\n\r\n // --- サービス(authority+port で重複排除) ---\r\n const serviceKey = `${ip}:${port}`;\r\n if (!seenServices.has(serviceKey)) {\r\n seenServices.add(serviceKey);\r\n const service: ParsedService = {\r\n hostAuthority: ip,\r\n transport: 'tcp',\r\n port,\r\n appProto: scheme,\r\n protoConfidence: 'high',\r\n state: 'open',\r\n };\r\n result.services.push(service);\r\n }\r\n\r\n // --- HTTP エンドポイント ---\r\n const rawPath = extractRawPathname(matchedAt);\r\n const baseUri = `${scheme}://${ip}:${port}`;\r\n\r\n const endpoint: ParsedHttpEndpoint = {\r\n hostAuthority: ip,\r\n port,\r\n baseUri,\r\n method: 'GET',\r\n path: rawPath,\r\n };\r\n result.httpEndpoints.push(endpoint);\r\n\r\n // --- 脆弱性 ---\r\n const info = finding.info;\r\n const vulnerability: ParsedVulnerability = {\r\n hostAuthority: ip,\r\n port,\r\n method: 'GET',\r\n path: rawPath,\r\n vulnType: inferVulnType(info.tags),\r\n title: info.name,\r\n severity: info.severity,\r\n confidence: 'high',\r\n };\r\n result.vulnerabilities.push(vulnerability);\r\n\r\n // --- CVE ---\r\n const classification = info.classification;\r\n if (\r\n classification !== undefined &&\r\n isRecord(classification) &&\r\n 'cve-id' in classification &&\r\n isStringArray(classification['cve-id']) &&\r\n classification['cve-id'].length > 0\r\n ) {\r\n for (const cveId of classification['cve-id']) {\r\n const cve: ParsedCve = {\r\n vulnerabilityTitle: info.name,\r\n cveId,\r\n cvssScore: typeof classification['cvss-score'] === 'number' ? classification['cvss-score'] : undefined,\r\n cvssVector: typeof classification['cvss-metrics'] === 'string' ? classification['cvss-metrics'] : undefined,\r\n };\r\n result.cves.push(cve);\r\n }\r\n }\r\n}\r\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { ServiceObservation } from '../../types/entities.js';\nimport type { CreateServiceObservationInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `service_observations` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface ServiceObservationRow {\n id: string;\n service_id: string;\n key: string;\n value: string;\n confidence: string;\n evidence_artifact_id: string;\n created_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase ServiceObservation entity. */\nfunction rowToServiceObservation(row: ServiceObservationRow): ServiceObservation {\n return {\n id: row.id,\n serviceId: row.service_id,\n key: row.key,\n value: row.value,\n confidence: row.confidence,\n evidenceArtifactId: row.evidence_artifact_id,\n createdAt: row.created_at,\n };\n}\n\n/**\n * Repository for the `service_observations` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class ServiceObservationRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new ServiceObservation and return the full entity. */\n create(input: CreateServiceObservationInput): ServiceObservation {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string, string, string, string, string]\n >(\n `INSERT INTO service_observations (id, service_id, key, value, confidence, evidence_artifact_id, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.serviceId,\n input.key,\n input.value,\n input.confidence,\n input.evidenceArtifactId,\n now,\n );\n\n return {\n id,\n serviceId: input.serviceId,\n key: input.key,\n value: input.value,\n confidence: input.confidence,\n evidenceArtifactId: input.evidenceArtifactId,\n createdAt: now,\n };\n }\n\n /** Find a ServiceObservation by its primary key. Returns undefined if not found. */\n findById(id: string): ServiceObservation | undefined {\n const stmt = this.db.prepare<[string], ServiceObservationRow>(\n `SELECT id, service_id, key, value, confidence, evidence_artifact_id, created_at\n FROM service_observations\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToServiceObservation(row) : undefined;\n }\n\n /** Return all ServiceObservations for a given service. */\n findByServiceId(serviceId: string): ServiceObservation[] {\n const stmt = this.db.prepare<[string], ServiceObservationRow>(\n `SELECT id, service_id, key, value, confidence, evidence_artifact_id, created_at\n FROM service_observations\n WHERE service_id = ?`,\n );\n\n const rows = stmt.all(serviceId);\n return rows.map(rowToServiceObservation);\n }\n\n /** Delete a ServiceObservation by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM service_observations WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { EndpointInput } from '../../types/entities.js';\nimport type { CreateEndpointInputInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `endpoint_inputs` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface EndpointInputRow {\n id: string;\n endpoint_id: string;\n input_id: string;\n evidence_artifact_id: string;\n created_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase EndpointInput entity. */\nfunction rowToEndpointInput(row: EndpointInputRow): EndpointInput {\n return {\n id: row.id,\n endpointId: row.endpoint_id,\n inputId: row.input_id,\n evidenceArtifactId: row.evidence_artifact_id,\n createdAt: row.created_at,\n };\n}\n\n/**\n * Repository for the `endpoint_inputs` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class EndpointInputRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new EndpointInput and return the full entity. */\n create(input: CreateEndpointInputInput): EndpointInput {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string, string, string]\n >(\n `INSERT INTO endpoint_inputs (id, endpoint_id, input_id, evidence_artifact_id, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.endpointId,\n input.inputId,\n input.evidenceArtifactId,\n now,\n );\n\n return {\n id,\n endpointId: input.endpointId,\n inputId: input.inputId,\n evidenceArtifactId: input.evidenceArtifactId,\n createdAt: now,\n };\n }\n\n /** Find an EndpointInput by its primary key. Returns undefined if not found. */\n findById(id: string): EndpointInput | undefined {\n const stmt = this.db.prepare<[string], EndpointInputRow>(\n `SELECT id, endpoint_id, input_id, evidence_artifact_id, created_at\n FROM endpoint_inputs\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToEndpointInput(row) : undefined;\n }\n\n /** Find all EndpointInputs for a given endpoint. */\n findByEndpointId(endpointId: string): EndpointInput[] {\n const stmt = this.db.prepare<[string], EndpointInputRow>(\n `SELECT id, endpoint_id, input_id, evidence_artifact_id, created_at\n FROM endpoint_inputs\n WHERE endpoint_id = ?`,\n );\n\n const rows = stmt.all(endpointId);\n return rows.map(rowToEndpointInput);\n }\n\n /** Find all EndpointInputs for a given input. */\n findByInputId(inputId: string): EndpointInput[] {\n const stmt = this.db.prepare<[string], EndpointInputRow>(\n `SELECT id, endpoint_id, input_id, evidence_artifact_id, created_at\n FROM endpoint_inputs\n WHERE input_id = ?`,\n );\n\n const rows = stmt.all(inputId);\n return rows.map(rowToEndpointInput);\n }\n\n /** Delete an EndpointInput by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM endpoint_inputs WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { Cve } from '../../types/entities.js';\nimport type { CreateCveInput } from '../../types/repository.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `cves` table.\n * Column names are snake_case as defined in the schema.\n */\ninterface CveRow {\n id: string;\n vulnerability_id: string;\n cve_id: string;\n description: string | null;\n cvss_score: number | null;\n cvss_vector: string | null;\n reference_url: string | null;\n created_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase Cve entity. */\nfunction rowToCve(row: CveRow): Cve {\n return {\n id: row.id,\n vulnerabilityId: row.vulnerability_id,\n cveId: row.cve_id,\n description: row.description ?? undefined,\n cvssScore: row.cvss_score ?? undefined,\n cvssVector: row.cvss_vector ?? undefined,\n referenceUrl: row.reference_url ?? undefined,\n createdAt: row.created_at,\n };\n}\n\n/**\n * Repository for the `cves` table.\n *\n * All queries use prepared statements to prevent SQL injection.\n */\nexport class CveRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new Cve and return the full entity. */\n create(input: CreateCveInput): Cve {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string, string | null, number | null, string | null, string | null, string]\n >(\n `INSERT INTO cves (id, vulnerability_id, cve_id, description, cvss_score, cvss_vector, reference_url, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.vulnerabilityId,\n input.cveId,\n input.description ?? null,\n input.cvssScore ?? null,\n input.cvssVector ?? null,\n input.referenceUrl ?? null,\n now,\n );\n\n return {\n id,\n vulnerabilityId: input.vulnerabilityId,\n cveId: input.cveId,\n description: input.description,\n cvssScore: input.cvssScore,\n cvssVector: input.cvssVector,\n referenceUrl: input.referenceUrl,\n createdAt: now,\n };\n }\n\n /** Find a Cve by its primary key. Returns undefined if not found. */\n findById(id: string): Cve | undefined {\n const stmt = this.db.prepare<[string], CveRow>(\n `SELECT id, vulnerability_id, cve_id, description, cvss_score, cvss_vector, reference_url, created_at\n FROM cves\n WHERE id = ?`,\n );\n\n const row = stmt.get(id);\n return row ? rowToCve(row) : undefined;\n }\n\n /** Return all Cves associated with a given vulnerability. */\n findByVulnerabilityId(vulnerabilityId: string): Cve[] {\n const stmt = this.db.prepare<[string], CveRow>(\n `SELECT id, vulnerability_id, cve_id, description, cvss_score, cvss_vector, reference_url, created_at\n FROM cves\n WHERE vulnerability_id = ?`,\n );\n\n const rows = stmt.all(vulnerabilityId);\n return rows.map(rowToCve);\n }\n\n /** Delete a Cve by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM cves WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","/**\n * sonobat — Normalizer\n *\n * ParseResult(パーサーの中間表現)を受け取り、\n * 自然キーで既存レコードを検索(upsert)しながら DB に書き込む。\n * 全操作はトランザクションでラップし、アトミックに実行する。\n */\n\nimport type Database from 'better-sqlite3';\nimport type { ParseResult } from '../types/parser.js';\nimport { HostRepository } from '../db/repository/host-repository.js';\nimport { ServiceRepository } from '../db/repository/service-repository.js';\nimport { ServiceObservationRepository } from '../db/repository/service-observation-repository.js';\nimport { HttpEndpointRepository } from '../db/repository/http-endpoint-repository.js';\nimport { InputRepository } from '../db/repository/input-repository.js';\nimport { EndpointInputRepository } from '../db/repository/endpoint-input-repository.js';\nimport { ObservationRepository } from '../db/repository/observation-repository.js';\nimport { VulnerabilityRepository } from '../db/repository/vulnerability-repository.js';\nimport { CveRepository } from '../db/repository/cve-repository.js';\n\n// ============================================================\n// 結果型\n// ============================================================\n\n/** normalize() の実行結果。各エンティティの新規作成数を返す。 */\nexport interface NormalizeResult {\n hostsCreated: number;\n servicesCreated: number;\n serviceObservationsCreated: number;\n httpEndpointsCreated: number;\n inputsCreated: number;\n endpointInputsCreated: number;\n observationsCreated: number;\n vulnerabilitiesCreated: number;\n cvesCreated: number;\n}\n\n// ============================================================\n// normalize\n// ============================================================\n\n/**\n * ParseResult を DB に正規化して書き込む。\n *\n * - 自然キー(authority, host_id+transport+port 等)で既存レコードを検索し、\n * 存在すれば再利用、なければ新規作成する(upsert パターン)。\n * - 全操作は 1 トランザクション内で実行される。\n *\n * @param db better-sqlite3 の Database インスタンス\n * @param artifactId 根拠となる Artifact の ID(evidence_artifact_id に設定)\n * @param parseResult パーサーが返した中間表現\n * @returns 各エンティティの新規作成数\n */\nexport function normalize(\n db: Database.Database,\n artifactId: string,\n parseResult: ParseResult,\n): NormalizeResult {\n const hostRepo = new HostRepository(db);\n const serviceRepo = new ServiceRepository(db);\n const serviceObsRepo = new ServiceObservationRepository(db);\n const httpEndpointRepo = new HttpEndpointRepository(db);\n const inputRepo = new InputRepository(db);\n const endpointInputRepo = new EndpointInputRepository(db);\n const observationRepo = new ObservationRepository(db);\n const vulnRepo = new VulnerabilityRepository(db);\n const cveRepo = new CveRepository(db);\n\n const run = db.transaction((): NormalizeResult => {\n const result: NormalizeResult = {\n hostsCreated: 0,\n servicesCreated: 0,\n serviceObservationsCreated: 0,\n httpEndpointsCreated: 0,\n inputsCreated: 0,\n endpointInputsCreated: 0,\n observationsCreated: 0,\n vulnerabilitiesCreated: 0,\n cvesCreated: 0,\n };\n\n // ---------------------------------------------------------\n // 自然キー → DB ID のマッピング\n // ---------------------------------------------------------\n const hostIdByAuthority = new Map<string, string>();\n // key: \"hostId:transport:port\"\n const serviceIdByKey = new Map<string, string>();\n // key: \"serviceId:method:path\"\n const endpointIdByKey = new Map<string, string>();\n // key: \"serviceId:location:name\"\n const inputIdByKey = new Map<string, string>();\n // key: vulnerability title → DB ID\n const vulnIdByTitle = new Map<string, string>();\n\n // ---------------------------------------------------------\n // 1. Upsert hosts\n // ---------------------------------------------------------\n for (const parsed of parseResult.hosts) {\n const existing = hostRepo.findByAuthority(parsed.authority);\n if (existing) {\n hostIdByAuthority.set(parsed.authority, existing.id);\n } else {\n const host = hostRepo.create({\n authorityKind: parsed.authorityKind,\n authority: parsed.authority,\n resolvedIpsJson: JSON.stringify(parsed.resolvedIps ?? []),\n });\n hostIdByAuthority.set(parsed.authority, host.id);\n result.hostsCreated++;\n }\n }\n\n // ---------------------------------------------------------\n // 2. Upsert services\n // ---------------------------------------------------------\n for (const parsed of parseResult.services) {\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\n if (!hostId) continue;\n\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\n if (serviceIdByKey.has(svcKey)) continue;\n\n // DB から既存サービスを検索\n const existingServices = serviceRepo.findByHostId(hostId);\n const existing = existingServices.find(\n (s) => s.transport === parsed.transport && s.port === parsed.port,\n );\n\n if (existing) {\n serviceIdByKey.set(svcKey, existing.id);\n } else {\n const service = serviceRepo.create({\n hostId,\n transport: parsed.transport,\n port: parsed.port,\n appProto: parsed.appProto,\n protoConfidence: parsed.protoConfidence,\n banner: parsed.banner,\n product: parsed.product,\n version: parsed.version,\n state: parsed.state,\n evidenceArtifactId: artifactId,\n });\n serviceIdByKey.set(svcKey, service.id);\n result.servicesCreated++;\n }\n }\n\n // ---------------------------------------------------------\n // ヘルパー: hostAuthority + port → serviceId を解決\n // HTTP 系エンティティは transport が常に tcp\n // ---------------------------------------------------------\n function resolveServiceId(hostAuthority: string, port: number): string | undefined {\n const hostId = hostIdByAuthority.get(hostAuthority);\n if (!hostId) return undefined;\n return serviceIdByKey.get(`${hostId}:tcp:${port}`);\n }\n\n // ---------------------------------------------------------\n // 3. Service observations\n // ---------------------------------------------------------\n for (const parsed of parseResult.serviceObservations) {\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\n if (!hostId) continue;\n\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\n const serviceId = serviceIdByKey.get(svcKey);\n if (!serviceId) continue;\n\n serviceObsRepo.create({\n serviceId,\n key: parsed.key,\n value: parsed.value,\n confidence: parsed.confidence,\n evidenceArtifactId: artifactId,\n });\n result.serviceObservationsCreated++;\n }\n\n // ---------------------------------------------------------\n // 4. Upsert HTTP endpoints\n // ---------------------------------------------------------\n for (const parsed of parseResult.httpEndpoints) {\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\n if (!serviceId) continue;\n\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\n if (endpointIdByKey.has(epKey)) continue;\n\n const existingEndpoints = httpEndpointRepo.findByServiceId(serviceId);\n const existing = existingEndpoints.find(\n (e) => e.method === parsed.method && e.path === parsed.path,\n );\n\n if (existing) {\n endpointIdByKey.set(epKey, existing.id);\n } else {\n const endpoint = httpEndpointRepo.create({\n serviceId,\n baseUri: parsed.baseUri,\n method: parsed.method,\n path: parsed.path,\n statusCode: parsed.statusCode,\n contentLength: parsed.contentLength,\n words: parsed.words,\n lines: parsed.lines,\n evidenceArtifactId: artifactId,\n });\n endpointIdByKey.set(epKey, endpoint.id);\n result.httpEndpointsCreated++;\n }\n }\n\n // ---------------------------------------------------------\n // 5. Upsert inputs\n // ---------------------------------------------------------\n for (const parsed of parseResult.inputs) {\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\n if (!serviceId) continue;\n\n const inKey = `${serviceId}:${parsed.location}:${parsed.name}`;\n if (inputIdByKey.has(inKey)) continue;\n\n const existingInputs = inputRepo.findByServiceId(serviceId);\n const existing = existingInputs.find(\n (i) => i.location === parsed.location && i.name === parsed.name,\n );\n\n if (existing) {\n inputIdByKey.set(inKey, existing.id);\n } else {\n const input = inputRepo.create({\n serviceId,\n location: parsed.location,\n name: parsed.name,\n typeHint: parsed.typeHint,\n });\n inputIdByKey.set(inKey, input.id);\n result.inputsCreated++;\n }\n }\n\n // ---------------------------------------------------------\n // 6. Upsert endpoint_inputs\n // ---------------------------------------------------------\n for (const parsed of parseResult.endpointInputs) {\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\n if (!serviceId) continue;\n\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\n const endpointId = endpointIdByKey.get(epKey);\n if (!endpointId) continue;\n\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\n const inputId = inputIdByKey.get(inKey);\n if (!inputId) continue;\n\n // 既存リンクの確認\n const existingLinks = endpointInputRepo.findByEndpointId(endpointId);\n const alreadyLinked = existingLinks.some((l) => l.inputId === inputId);\n if (alreadyLinked) continue;\n\n endpointInputRepo.create({\n endpointId,\n inputId,\n evidenceArtifactId: artifactId,\n });\n result.endpointInputsCreated++;\n }\n\n // ---------------------------------------------------------\n // 7. Observations(常に新規作成)\n // ---------------------------------------------------------\n for (const parsed of parseResult.observations) {\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\n if (!serviceId) continue;\n\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\n const inputId = inputIdByKey.get(inKey);\n if (!inputId) continue;\n\n observationRepo.create({\n inputId,\n rawValue: parsed.rawValue,\n normValue: parsed.normValue,\n source: parsed.source,\n confidence: parsed.confidence,\n evidenceArtifactId: artifactId,\n observedAt: new Date().toISOString(),\n });\n result.observationsCreated++;\n }\n\n // ---------------------------------------------------------\n // 8. Vulnerabilities\n // ---------------------------------------------------------\n for (const parsed of parseResult.vulnerabilities) {\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\n if (!serviceId) continue;\n\n // endpoint への紐づけ(任意)\n let endpointId: string | undefined;\n if (parsed.method && parsed.path) {\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\n endpointId = endpointIdByKey.get(epKey);\n }\n\n const vuln = vulnRepo.create({\n serviceId,\n endpointId,\n vulnType: parsed.vulnType,\n title: parsed.title,\n description: parsed.description,\n severity: parsed.severity,\n confidence: parsed.confidence,\n evidenceArtifactId: artifactId,\n });\n vulnIdByTitle.set(parsed.title, vuln.id);\n result.vulnerabilitiesCreated++;\n }\n\n // ---------------------------------------------------------\n // 9. CVEs\n // ---------------------------------------------------------\n for (const parsed of parseResult.cves) {\n const vulnId = vulnIdByTitle.get(parsed.vulnerabilityTitle);\n if (!vulnId) continue;\n\n cveRepo.create({\n vulnerabilityId: vulnId,\n cveId: parsed.cveId,\n description: parsed.description,\n cvssScore: parsed.cvssScore,\n cvssVector: parsed.cvssVector,\n referenceUrl: parsed.referenceUrl,\n });\n result.cvesCreated++;\n }\n\n return result;\n });\n\n return run();\n}\n","/**\n * sonobat — MCP Propose Tool\n *\n * Tool for generating next-step action proposals based on missing data.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { propose } from '../../engine/proposer.js';\n\nexport function registerProposeTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'propose',\n 'Analyze the AttackDataGraph for missing data and propose next-step actions',\n {\n hostId: z.string().optional().describe('Limit proposals to a specific host (optional)'),\n },\n async ({ hostId }) => {\n const actions = propose(db, hostId);\n if (actions.length === 0) {\n return {\n content: [{ type: 'text', text: 'No actions proposed. All discovered data appears complete.' }],\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(actions, null, 2) }] };\n },\n );\n}\n","/**\r\n * sonobat — Proposer engine\r\n *\r\n * Analyzes the AttackDataGraph stored in SQLite and proposes\r\n * next-step actions (scans, discovery, etc.) based on missing data.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Action } from '../types/engine.js';\r\nimport { HostRepository } from '../db/repository/host-repository.js';\r\nimport { ServiceRepository } from '../db/repository/service-repository.js';\r\nimport { HttpEndpointRepository } from '../db/repository/http-endpoint-repository.js';\r\nimport { InputRepository } from '../db/repository/input-repository.js';\r\nimport { EndpointInputRepository } from '../db/repository/endpoint-input-repository.js';\r\nimport { ObservationRepository } from '../db/repository/observation-repository.js';\r\nimport { VhostRepository } from '../db/repository/vhost-repository.js';\r\nimport { VulnerabilityRepository } from '../db/repository/vulnerability-repository.js';\r\nimport type { Host, Service } from '../types/entities.js';\r\n\r\n/**\r\n * Analyze the database for missing reconnaissance data and return\r\n * a list of proposed actions to fill the gaps.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @param hostId - Optional: limit analysis to a single host\r\n * @returns Array of proposed actions\r\n */\r\nexport function propose(db: Database.Database, hostId?: string): Action[] {\r\n const hostRepo = new HostRepository(db);\r\n const serviceRepo = new ServiceRepository(db);\r\n const httpEndpointRepo = new HttpEndpointRepository(db);\r\n const inputRepo = new InputRepository(db);\r\n const endpointInputRepo = new EndpointInputRepository(db);\r\n const observationRepo = new ObservationRepository(db);\r\n const vhostRepo = new VhostRepository(db);\r\n const vulnRepo = new VulnerabilityRepository(db);\r\n\r\n const actions: Action[] = [];\r\n\r\n // Determine target hosts\r\n let hosts: Host[];\r\n if (hostId !== undefined) {\r\n const host = hostRepo.findById(hostId);\r\n if (host === undefined) {\r\n return [];\r\n }\r\n hosts = [host];\r\n } else {\r\n hosts = hostRepo.findAll();\r\n }\r\n\r\n for (const host of hosts) {\r\n const services = serviceRepo.findByHostId(host.id);\r\n\r\n // (a) No services at all -> suggest nmap scan\r\n if (services.length === 0) {\r\n actions.push({\r\n kind: 'nmap_scan',\r\n description: `Port scan ${host.authority} to discover services`,\r\n command: `nmap -p- -sV ${host.authority}`,\r\n params: { hostId: host.id },\r\n });\r\n continue;\r\n }\r\n\r\n // (b) For each HTTP/HTTPS service, check for missing data\r\n for (const service of services) {\r\n if (service.appProto !== 'http' && service.appProto !== 'https') {\r\n continue;\r\n }\r\n\r\n const baseUri = `${service.appProto}://${host.authority}:${service.port}`;\r\n\r\n proposeForHttpService(\r\n actions,\r\n host,\r\n service,\r\n baseUri,\r\n httpEndpointRepo,\r\n inputRepo,\r\n endpointInputRepo,\r\n observationRepo,\r\n vhostRepo,\r\n vulnRepo,\r\n );\r\n }\r\n }\r\n\r\n return actions;\r\n}\r\n\r\n/**\r\n * Check a single HTTP/HTTPS service for missing data and push\r\n * proposed actions into the actions array.\r\n */\r\nfunction proposeForHttpService(\r\n actions: Action[],\r\n host: Host,\r\n service: Service,\r\n baseUri: string,\r\n httpEndpointRepo: HttpEndpointRepository,\r\n inputRepo: InputRepository,\r\n endpointInputRepo: EndpointInputRepository,\r\n observationRepo: ObservationRepository,\r\n vhostRepo: VhostRepository,\r\n vulnRepo: VulnerabilityRepository,\r\n): void {\r\n const endpoints = httpEndpointRepo.findByServiceId(service.id);\r\n\r\n // No endpoints -> suggest directory/file discovery\r\n if (endpoints.length === 0) {\r\n actions.push({\r\n kind: 'ffuf_discovery',\r\n description: `Discover endpoints on ${baseUri}`,\r\n command: `ffuf -u ${baseUri}/FUZZ -w /usr/share/wordlists/dirb/common.txt`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities at service level (used for value_fuzz decision)\r\n const vulns = vulnRepo.findByServiceId(service.id);\r\n\r\n // For each endpoint: check inputs via endpoint_inputs (per-endpoint)\r\n for (const endpoint of endpoints) {\r\n const endpointInputs = endpointInputRepo.findByEndpointId(endpoint.id);\r\n\r\n if (endpointInputs.length === 0) {\r\n actions.push({\r\n kind: 'parameter_discovery',\r\n description: `Discover input parameters for ${baseUri}${endpoint.path}`,\r\n params: { hostId: host.id, serviceId: service.id, endpointId: endpoint.id },\r\n });\r\n }\r\n\r\n // For each linked input: check observations\r\n for (const ei of endpointInputs) {\r\n const input = inputRepo.findById(ei.inputId);\r\n if (input === undefined) {\r\n continue;\r\n }\r\n\r\n const observations = observationRepo.findByInputId(input.id);\r\n\r\n if (observations.length === 0) {\r\n actions.push({\r\n kind: 'value_collection',\r\n description: `Collect observed values for input \"${input.name}\" (${input.location})`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpoint.id,\r\n inputId: input.id,\r\n },\r\n });\r\n } else if (vulns.length === 0) {\r\n // Has input + observations, but no vulnerabilities → suggest fuzzing\r\n actions.push({\r\n kind: 'value_fuzz',\r\n description: `Fuzz input \"${input.name}\" (${input.location}) on ${baseUri}${endpoint.path}`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpoint.id,\r\n inputId: input.id,\r\n },\r\n });\r\n }\r\n }\r\n }\r\n\r\n // Check vhosts\r\n const vhosts = vhostRepo.findByHostId(host.id);\r\n if (vhosts.length === 0) {\r\n actions.push({\r\n kind: 'vhost_discovery',\r\n description: `Discover virtual hosts for ${host.authority}`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities → suggest nuclei scan if none\r\n if (vulns.length === 0) {\r\n actions.push({\r\n kind: 'nuclei_scan',\r\n description: `Scan ${baseUri} for known vulnerabilities`,\r\n command: `nuclei -u ${baseUri} -jsonl`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n}\r\n","/**\n * sonobat — MCP Mutation Tools\n *\n * Tools for manually adding data to the AttackDataGraph.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { HostRepository } from '../../db/repository/host-repository.js';\nimport { ArtifactRepository } from '../../db/repository/artifact-repository.js';\nimport { CredentialRepository } from '../../db/repository/credential-repository.js';\nimport { VulnerabilityRepository } from '../../db/repository/vulnerability-repository.js';\nimport { CveRepository } from '../../db/repository/cve-repository.js';\n\n/**\n * Get or create a singleton \"manual\" artifact for manual data entry.\n * Reused across all manual mutations to avoid artifact proliferation.\n */\nfunction getOrCreateManualArtifact(db: Database.Database): string {\n const artifactRepo = new ArtifactRepository(db);\n const existing = artifactRepo.findByTool('manual');\n if (existing.length > 0) {\n return existing[0].id;\n }\n const artifact = artifactRepo.create({\n tool: 'manual',\n kind: 'manual_entry',\n path: 'manual',\n capturedAt: new Date().toISOString(),\n });\n return artifact.id;\n}\n\nexport function registerMutationTools(server: McpServer, db: Database.Database): void {\n // 1. add_host\n server.tool(\n 'add_host',\n 'Manually add a host to the AttackDataGraph',\n {\n authority: z.string().describe('IP address or domain name'),\n authorityKind: z.enum(['IP', 'DOMAIN']).describe('Type of authority'),\n },\n async ({ authority, authorityKind }) => {\n const hostRepo = new HostRepository(db);\n const existing = hostRepo.findByAuthority(authority);\n if (existing) {\n return {\n content: [{ type: 'text', text: `Host already exists: ${JSON.stringify(existing, null, 2)}` }],\n };\n }\n const host = hostRepo.create({\n authorityKind,\n authority,\n resolvedIpsJson: '[]',\n });\n return { content: [{ type: 'text', text: JSON.stringify(host, null, 2) }] };\n },\n );\n\n // 2. add_credential\n server.tool(\n 'add_credential',\n 'Manually add a credential for a service',\n {\n serviceId: z.string().describe('Service UUID'),\n username: z.string().describe('Username'),\n secret: z.string().describe('Secret value (password, token, etc.)'),\n secretType: z.enum(['password', 'token', 'api_key', 'ssh_key']).describe('Type of secret'),\n source: z.enum(['brute_force', 'default', 'leaked', 'manual']).describe('How the credential was obtained'),\n confidence: z.enum(['high', 'medium', 'low']).describe('Confidence level').default('medium'),\n },\n async ({ serviceId, username, secret, secretType, source, confidence }) => {\n const artifactId = getOrCreateManualArtifact(db);\n const credentialRepo = new CredentialRepository(db);\n const credential = credentialRepo.create({\n serviceId,\n username,\n secret,\n secretType,\n source,\n confidence,\n evidenceArtifactId: artifactId,\n });\n return { content: [{ type: 'text', text: JSON.stringify(credential, null, 2) }] };\n },\n );\n\n // 3. add_vulnerability\n server.tool(\n 'add_vulnerability',\n 'Manually add a vulnerability for a service',\n {\n serviceId: z.string().describe('Service UUID'),\n vulnType: z.string().describe('Vulnerability type (sqli, xss, rce, lfi, ssrf, etc.)'),\n title: z.string().describe('Vulnerability title'),\n severity: z.enum(['critical', 'high', 'medium', 'low', 'info']).describe('Severity level'),\n confidence: z.enum(['high', 'medium', 'low']).describe('Confidence level').default('medium'),\n endpointId: z.string().optional().describe('HTTP endpoint UUID (optional)'),\n description: z.string().optional().describe('Detailed description (optional)'),\n },\n async ({ serviceId, vulnType, title, severity, confidence, endpointId, description }) => {\n const artifactId = getOrCreateManualArtifact(db);\n const vulnRepo = new VulnerabilityRepository(db);\n const vuln = vulnRepo.create({\n serviceId,\n endpointId,\n vulnType,\n title,\n description,\n severity,\n confidence,\n evidenceArtifactId: artifactId,\n });\n return { content: [{ type: 'text', text: JSON.stringify(vuln, null, 2) }] };\n },\n );\n\n // 4. link_cve\n server.tool(\n 'link_cve',\n 'Link a CVE record to an existing vulnerability',\n {\n vulnerabilityId: z.string().describe('Vulnerability UUID'),\n cveId: z.string().describe('CVE identifier (e.g. CVE-2021-44228)'),\n description: z.string().optional().describe('CVE description'),\n cvssScore: z.number().optional().describe('CVSS score (0.0 - 10.0)'),\n cvssVector: z.string().optional().describe('CVSS vector string'),\n referenceUrl: z.string().optional().describe('Reference URL'),\n },\n async ({ vulnerabilityId, cveId, description, cvssScore, cvssVector, referenceUrl }) => {\n const cveRepo = new CveRepository(db);\n const cve = cveRepo.create({\n vulnerabilityId,\n cveId,\n description,\n cvssScore,\n cvssVector,\n referenceUrl,\n });\n return { content: [{ type: 'text', text: JSON.stringify(cve, null, 2) }] };\n },\n );\n}\n","/**\n * sonobat — MCP Datalog Tools\n *\n * Tools for querying and analyzing the AttackDataGraph using\n * the Datalog inference engine.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { listFacts, runDatalog, queryAttackPaths, listPatterns } from '../../engine/datalog/index.js';\nimport type { Fact, EvalResult } from '../../engine/datalog/types.js';\n\n/**\n * Format a single Fact as Datalog-style text.\n *\n * Example: host(\"abc-123\", \"10.0.0.1\", \"IP\").\n */\nfunction formatFact(fact: Fact): string {\n const args = fact.values\n .map((v) => (typeof v === 'number' ? String(v) : `\"${v}\"`))\n .join(', ');\n return `${fact.predicate}(${args}).`;\n}\n\n/**\n * Format an EvalResult as human-readable text.\n */\nfunction formatEvalResult(result: EvalResult): string {\n if (result.answers.length === 0) {\n return `No query results.\\n\\nStats: ${result.stats.iterations} iterations, ${result.stats.totalDerived} derived facts, ${result.stats.elapsedMs}ms`;\n }\n\n const sections: string[] = [];\n\n for (const answer of result.answers) {\n const queryArgs = answer.query.args\n .map((a) => (a.kind === 'variable' ? a.name : a.kind === 'constant' ? String(a.value) : '_'))\n .join(', ');\n const header = `Query: ${answer.query.predicate}(${queryArgs})`;\n\n if (answer.tuples.length === 0) {\n sections.push(`${header}\\nResults: (empty)`);\n continue;\n }\n\n const columns = answer.columns;\n\n // Calculate column widths\n const widths = columns.map((col, i) =>\n Math.max(\n col.length,\n ...answer.tuples.map((t) => String(t[i]).length),\n ),\n );\n\n // Header row\n const headerRow = columns.map((col, i) => col.padEnd(widths[i])).join(' | ');\n\n // Data rows\n const dataRows = answer.tuples.map(\n (tuple) =>\n ' ' +\n tuple\n .map((val, i) => String(val).padEnd(widths[i]))\n .join(' | '),\n );\n\n sections.push(\n `${header}\\nResults (${answer.tuples.length} rows):\\n ${headerRow}\\n${dataRows.join('\\n')}`,\n );\n }\n\n sections.push(\n `\\nStats: ${result.stats.iterations} iterations, ${result.stats.totalDerived} derived facts, ${result.stats.elapsedMs}ms`,\n );\n\n return sections.join('\\n\\n');\n}\n\n/**\n * Register Datalog-related MCP tools on the server.\n */\nexport function registerDatalogTools(server: McpServer, db: Database.Database): void {\n // 1. list_facts\n server.tool(\n 'list_facts',\n 'List database contents as Datalog facts. Optionally filter by predicate name and limit the number of results.',\n {\n predicate: z\n .string()\n .optional()\n .describe(\n 'Filter by predicate name (host, service, http_endpoint, input, endpoint_input, observation, credential, vulnerability, vulnerability_endpoint, cve, vhost)',\n ),\n limit: z.number().optional().describe('Maximum number of facts to return'),\n },\n async ({ predicate, limit }) => {\n const facts = listFacts(db, predicate, limit);\n if (facts.length === 0) {\n return {\n content: [{ type: 'text', text: 'No facts found.' }],\n };\n }\n const text = facts.map(formatFact).join('\\n');\n return { content: [{ type: 'text', text }] };\n },\n );\n\n // 2. run_datalog\n server.tool(\n 'run_datalog',\n 'Execute a custom Datalog program against the AttackDataGraph. Supports rules with :- and queries with ?-. Optionally save the program as a named rule for future reuse.',\n {\n program: z.string().describe('Datalog program text (rules and queries)'),\n save_name: z.string().optional().describe('Save the program as a named rule for future reuse'),\n save_description: z\n .string()\n .optional()\n .describe('Description of the saved rule'),\n generated_by: z\n .string()\n .optional()\n .describe('Who generated this rule: \"human\" or \"ai\" (default: \"ai\")'),\n },\n async ({ program, save_name, save_description, generated_by }) => {\n try {\n const generatedBy =\n generated_by === 'human' ? 'human' : 'ai';\n const result = runDatalog(db, program, {\n saveName: save_name,\n saveDescription: save_description,\n generatedBy,\n });\n const text = formatEvalResult(result);\n return { content: [{ type: 'text', text }] };\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `Datalog error: ${message}` }],\n isError: true,\n };\n }\n },\n );\n\n // 3. query_attack_paths\n server.tool(\n 'query_attack_paths',\n 'Run a preset or saved attack pattern query. Use pattern \"list\" to see all available patterns.',\n {\n pattern: z\n .string()\n .describe(\n 'Pattern name (e.g. \"reachable_services\", \"critical_vulns\") or \"list\" to see available patterns',\n ),\n },\n async ({ pattern }) => {\n if (pattern === 'list') {\n const patterns = listPatterns(db);\n if (patterns.length === 0) {\n return {\n content: [{ type: 'text', text: 'No patterns available.' }],\n };\n }\n const lines = patterns.map(\n (p) =>\n `- ${p.name} [${p.source}]${p.description ? `: ${p.description}` : ''}${p.generatedBy ? ` (by ${p.generatedBy})` : ''}`,\n );\n return {\n content: [\n {\n type: 'text',\n text: `Available patterns:\\n${lines.join('\\n')}`,\n },\n ],\n };\n }\n\n try {\n const result = queryAttackPaths(db, pattern);\n const text = formatEvalResult(result);\n return { content: [{ type: 'text', text }] };\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n content: [{ type: 'text', text: `Datalog error: ${message}` }],\n isError: true,\n };\n }\n },\n );\n}\n","/**\n * sonobat — Datalog engine type definitions\n *\n * AST types, runtime types, configuration, and error types\n * for the Datalog inference engine.\n */\n\n// ============================================================\n// Token types\n// ============================================================\n\nexport type TokenKind =\n | 'IDENT'\n | 'VARIABLE'\n | 'STRING'\n | 'NUMBER'\n | 'LPAREN'\n | 'RPAREN'\n | 'COMMA'\n | 'DOT'\n | 'COLON_DASH'\n | 'NOT'\n | 'NEQ'\n | 'EQ'\n | 'LT'\n | 'GT'\n | 'LTE'\n | 'GTE'\n | 'QUERY'\n | 'UNDERSCORE'\n | 'EOF';\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n line: number;\n col: number;\n}\n\n// ============================================================\n// AST types\n// ============================================================\n\nexport type Term =\n | { kind: 'variable'; name: string }\n | { kind: 'constant'; value: string | number };\n\nexport interface Atom {\n predicate: string;\n args: Term[];\n}\n\nexport type BodyLiteral =\n | { kind: 'positive'; atom: Atom }\n | { kind: 'negated'; atom: Atom }\n | { kind: 'comparison'; op: ComparisonOp; left: Term; right: Term };\n\nexport type ComparisonOp = '=' | '!=' | '<' | '>' | '<=' | '>=';\n\nexport interface Rule {\n head: Atom;\n body: BodyLiteral[];\n}\n\nexport interface Query {\n atom: Atom;\n}\n\nexport interface Program {\n rules: Rule[];\n queries: Query[];\n}\n\n// ============================================================\n// Runtime types\n// ============================================================\n\nexport type Tuple = ReadonlyArray<string | number>;\n\nexport interface EvalConfig {\n /** Maximum fixed-point iterations (default: 1000) */\n maxIterations: number;\n /** Maximum total derived tuples (default: 100_000) */\n maxTuples: number;\n /** Maximum rules allowed (default: 200) */\n maxRules: number;\n /** Timeout in milliseconds (default: 5000) */\n timeoutMs: number;\n}\n\nexport const DEFAULT_EVAL_CONFIG: EvalConfig = {\n maxIterations: 1000,\n maxTuples: 100_000,\n maxRules: 200,\n timeoutMs: 5000,\n};\n\nexport interface QueryAnswer {\n query: Atom;\n tuples: Tuple[];\n columns: string[];\n}\n\nexport interface EvalStats {\n iterations: number;\n totalDerived: number;\n elapsedMs: number;\n}\n\nexport interface EvalResult {\n answers: QueryAnswer[];\n stats: EvalStats;\n}\n\n// ============================================================\n// Fact types (for fact-extractor)\n// ============================================================\n\nexport interface Fact {\n predicate: string;\n values: ReadonlyArray<string | number>;\n}\n\n// ============================================================\n// Rule storage types\n// ============================================================\n\nexport interface DatalogRule {\n id: string;\n name: string;\n description?: string;\n ruleText: string;\n generatedBy: 'human' | 'ai' | 'preset';\n isPreset: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface CreateDatalogRuleInput {\n name: string;\n description?: string;\n ruleText: string;\n generatedBy: 'human' | 'ai' | 'preset';\n isPreset?: boolean;\n}\n\n// ============================================================\n// Error types\n// ============================================================\n\nexport class DatalogError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DatalogError';\n }\n}\n\nexport class DatalogSyntaxError extends DatalogError {\n readonly line: number;\n readonly col: number;\n\n constructor(message: string, line: number, col: number) {\n super(`Syntax error at ${line}:${col}: ${message}`);\n this.name = 'DatalogSyntaxError';\n this.line = line;\n this.col = col;\n }\n}\n\nexport class DatalogSafetyError extends DatalogError {\n constructor(message: string) {\n super(message);\n this.name = 'DatalogSafetyError';\n }\n}\n\nexport class DatalogResourceError extends DatalogError {\n constructor(message: string) {\n super(message);\n this.name = 'DatalogResourceError';\n }\n}\n","/**\n * sonobat — Datalog tokenizer\n *\n * Single-pass character scanner that converts Datalog source text\n * into a token array. Tracks line/column numbers and supports % comments.\n */\n\nimport { DatalogSyntaxError } from './types.js';\nimport type { Token } from './types.js';\n\n/**\n * Tokenize a Datalog source string into an array of tokens.\n * The last token is always EOF.\n */\nexport function tokenize(source: string): Token[] {\n const tokens: Token[] = [];\n let pos = 0;\n let line = 1;\n let col = 1;\n\n while (pos < source.length) {\n const ch = source[pos];\n\n // Whitespace\n if (ch === ' ' || ch === '\\t' || ch === '\\r') {\n pos++;\n col++;\n continue;\n }\n\n // Newline\n if (ch === '\\n') {\n pos++;\n line++;\n col = 1;\n continue;\n }\n\n // Comment (% to end of line)\n if (ch === '%') {\n while (pos < source.length && source[pos] !== '\\n') {\n pos++;\n }\n continue;\n }\n\n // String literal\n if (ch === '\"') {\n const startCol = col;\n pos++;\n col++;\n let value = '';\n while (pos < source.length && source[pos] !== '\"') {\n if (source[pos] === '\\n') {\n throw new DatalogSyntaxError('Unterminated string literal', line, startCol);\n }\n if (source[pos] === '\\\\' && pos + 1 < source.length) {\n pos++;\n col++;\n const escaped = source[pos];\n if (escaped === '\"') value += '\"';\n else if (escaped === '\\\\') value += '\\\\';\n else if (escaped === 'n') value += '\\n';\n else if (escaped === 't') value += '\\t';\n else value += escaped;\n } else {\n value += source[pos];\n }\n pos++;\n col++;\n }\n if (pos >= source.length) {\n throw new DatalogSyntaxError('Unterminated string literal', line, startCol);\n }\n pos++; // skip closing quote\n col++;\n tokens.push({ kind: 'STRING', value, line, col: startCol });\n continue;\n }\n\n // Number literal\n if (ch >= '0' && ch <= '9') {\n const startCol = col;\n let value = '';\n while (pos < source.length && source[pos] >= '0' && source[pos] <= '9') {\n value += source[pos];\n pos++;\n col++;\n }\n // Decimal part\n if (pos < source.length && source[pos] === '.' && pos + 1 < source.length && source[pos + 1] >= '0' && source[pos + 1] <= '9') {\n value += '.';\n pos++;\n col++;\n while (pos < source.length && source[pos] >= '0' && source[pos] <= '9') {\n value += source[pos];\n pos++;\n col++;\n }\n }\n tokens.push({ kind: 'NUMBER', value, line, col: startCol });\n continue;\n }\n\n // Identifier, variable, keyword, or underscore\n if (isIdentStart(ch)) {\n const startCol = col;\n let value = '';\n while (pos < source.length && isIdentPart(source[pos])) {\n value += source[pos];\n pos++;\n col++;\n }\n\n // Keywords\n if (value === 'not') {\n tokens.push({ kind: 'NOT', value, line, col: startCol });\n } else if (value === '_') {\n tokens.push({ kind: 'UNDERSCORE', value, line, col: startCol });\n } else if (ch >= 'A' && ch <= 'Z') {\n tokens.push({ kind: 'VARIABLE', value, line, col: startCol });\n } else {\n tokens.push({ kind: 'IDENT', value, line, col: startCol });\n }\n continue;\n }\n\n // Underscore alone\n if (ch === '_') {\n const startCol = col;\n let value = '_';\n pos++;\n col++;\n while (pos < source.length && isIdentPart(source[pos])) {\n value += source[pos];\n pos++;\n col++;\n }\n if (value === '_') {\n tokens.push({ kind: 'UNDERSCORE', value, line, col: startCol });\n } else {\n // _something is treated as a variable\n tokens.push({ kind: 'VARIABLE', value, line, col: startCol });\n }\n continue;\n }\n\n // Single/multi-character symbols\n const startCol = col;\n\n if (ch === '(') { tokens.push({ kind: 'LPAREN', value: '(', line, col: startCol }); pos++; col++; continue; }\n if (ch === ')') { tokens.push({ kind: 'RPAREN', value: ')', line, col: startCol }); pos++; col++; continue; }\n if (ch === ',') { tokens.push({ kind: 'COMMA', value: ',', line, col: startCol }); pos++; col++; continue; }\n if (ch === '.') { tokens.push({ kind: 'DOT', value: '.', line, col: startCol }); pos++; col++; continue; }\n\n // :- (COLON_DASH)\n if (ch === ':' && pos + 1 < source.length && source[pos + 1] === '-') {\n tokens.push({ kind: 'COLON_DASH', value: ':-', line, col: startCol });\n pos += 2;\n col += 2;\n continue;\n }\n\n // ?- (QUERY)\n if (ch === '?' && pos + 1 < source.length && source[pos + 1] === '-') {\n tokens.push({ kind: 'QUERY', value: '?-', line, col: startCol });\n pos += 2;\n col += 2;\n continue;\n }\n\n // Comparison operators\n if (ch === '!' && pos + 1 < source.length && source[pos + 1] === '=') {\n tokens.push({ kind: 'NEQ', value: '!=', line, col: startCol });\n pos += 2;\n col += 2;\n continue;\n }\n if (ch === '<' && pos + 1 < source.length && source[pos + 1] === '=') {\n tokens.push({ kind: 'LTE', value: '<=', line, col: startCol });\n pos += 2;\n col += 2;\n continue;\n }\n if (ch === '>' && pos + 1 < source.length && source[pos + 1] === '=') {\n tokens.push({ kind: 'GTE', value: '>=', line, col: startCol });\n pos += 2;\n col += 2;\n continue;\n }\n if (ch === '<') { tokens.push({ kind: 'LT', value: '<', line, col: startCol }); pos++; col++; continue; }\n if (ch === '>') { tokens.push({ kind: 'GT', value: '>', line, col: startCol }); pos++; col++; continue; }\n if (ch === '=') { tokens.push({ kind: 'EQ', value: '=', line, col: startCol }); pos++; col++; continue; }\n\n throw new DatalogSyntaxError(`Unexpected character '${ch}'`, line, col);\n }\n\n tokens.push({ kind: 'EOF', value: '', line, col });\n return tokens;\n}\n\nfunction isIdentStart(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');\n}\n\nfunction isIdentPart(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch === '_';\n}\n","/**\n * sonobat — Datalog recursive descent parser\n *\n * Converts a Datalog source string into a Program AST.\n * Calls the tokenizer to produce a token stream, then parses\n * rules, facts, and queries using a recursive descent approach.\n *\n * Grammar:\n * program = (rule | query)*\n * rule = atom \":-\" body \".\" | atom \".\"\n * query = \"?-\" atom \".\"\n * body = bodyLiteral (\",\" bodyLiteral)*\n * bodyLiteral = \"not\" atom | comparison | atom\n * comparison = term compOp term\n * atom = IDENT \"(\" termList \")\"\n * termList = term (\",\" term)*\n * term = VARIABLE | STRING | NUMBER | UNDERSCORE\n * compOp = \"=\" | \"!=\" | \"<\" | \">\" | \"<=\" | \">=\"\n */\n\nimport { tokenize } from './tokenizer.js';\nimport {\n DatalogSyntaxError,\n DatalogSafetyError,\n} from './types.js';\nimport type {\n Token,\n TokenKind,\n Program,\n Rule,\n Query,\n Atom,\n BodyLiteral,\n Term,\n ComparisonOp,\n} from './types.js';\n\n/** Comparison operator token kinds */\nconst COMPARISON_OPS: ReadonlySet<TokenKind> = new Set([\n 'EQ',\n 'NEQ',\n 'LT',\n 'GT',\n 'LTE',\n 'GTE',\n]);\n\n/** Map from token kind to ComparisonOp string */\nconst TOKEN_TO_COMP_OP: Readonly<Record<string, ComparisonOp>> = {\n EQ: '=',\n NEQ: '!=',\n LT: '<',\n GT: '>',\n LTE: '<=',\n GTE: '>=',\n};\n\n/**\n * Parse a Datalog source string into a Program AST.\n *\n * @param source - Datalog source code\n * @returns Parsed program containing rules and queries\n * @throws DatalogSyntaxError on parse failure\n * @throws DatalogSafetyError when a rule head contains unsafe variables\n */\nexport function parse(source: string): Program {\n const tokens = tokenize(source);\n const parser = new Parser(tokens);\n return parser.parseProgram();\n}\n\n/**\n * Recursive descent parser for Datalog.\n *\n * Maintains a cursor position into the token stream and provides\n * helper methods for peeking, advancing, and expecting tokens.\n */\nclass Parser {\n private readonly tokens: Token[];\n private pos: number;\n private anonCounter: number;\n\n constructor(tokens: Token[]) {\n this.tokens = tokens;\n this.pos = 0;\n this.anonCounter = 0;\n }\n\n // ===========================================================\n // Token stream helpers\n // ===========================================================\n\n /** Return the current token without advancing. */\n private peek(): Token {\n return this.tokens[this.pos];\n }\n\n /** Advance and return the consumed token. */\n private advance(): Token {\n const token = this.tokens[this.pos];\n this.pos++;\n return token;\n }\n\n /** If the current token matches `kind`, consume and return it; otherwise return null. */\n private match(kind: TokenKind): Token | null {\n if (this.peek().kind === kind) {\n return this.advance();\n }\n return null;\n }\n\n /** Consume the current token if it matches `kind`; throw if it does not. */\n private expect(kind: TokenKind): Token {\n const token = this.peek();\n if (token.kind !== kind) {\n throw new DatalogSyntaxError(\n `Expected ${kind}, got ${token.kind} ('${token.value}')`,\n token.line,\n token.col,\n );\n }\n return this.advance();\n }\n\n /** Generate a unique name for an anonymous variable. */\n private freshAnon(): string {\n const name = `_anon_${this.anonCounter}`;\n this.anonCounter++;\n return name;\n }\n\n // ===========================================================\n // Grammar productions\n // ===========================================================\n\n /** program = (rule | query)* EOF */\n parseProgram(): Program {\n const rules: Rule[] = [];\n const queries: Query[] = [];\n\n while (this.peek().kind !== 'EOF') {\n if (this.peek().kind === 'QUERY') {\n queries.push(this.parseQuery());\n } else {\n rules.push(this.parseRule());\n }\n }\n\n return { rules, queries };\n }\n\n /** query = \"?-\" atom \".\" */\n private parseQuery(): Query {\n this.expect('QUERY');\n const atom = this.parseAtom();\n this.expect('DOT');\n return { atom };\n }\n\n /**\n * rule = atom \":-\" body \".\" | atom \".\"\n *\n * A fact (atom followed by \".\") is represented as a rule with an empty body.\n * After parsing, safety validation is performed for non-fact rules.\n */\n private parseRule(): Rule {\n const head = this.parseAtom();\n\n let body: BodyLiteral[] = [];\n if (this.match('COLON_DASH')) {\n body = this.parseBody();\n }\n\n this.expect('DOT');\n\n const rule: Rule = { head, body };\n this.validateSafety(rule);\n return rule;\n }\n\n /** body = bodyLiteral (\",\" bodyLiteral)* */\n private parseBody(): BodyLiteral[] {\n const literals: BodyLiteral[] = [];\n literals.push(this.parseBodyLiteral());\n\n while (this.match('COMMA')) {\n literals.push(this.parseBodyLiteral());\n }\n\n return literals;\n }\n\n /**\n * bodyLiteral = \"not\" atom | comparison | atom\n *\n * Disambiguation logic:\n * - If the next token is NOT, parse a negated atom.\n * - If the next token is a VARIABLE or constant that could start a comparison\n * (i.e. the token after it is a comparison operator), parse a comparison.\n * - Otherwise, parse a positive atom.\n */\n private parseBodyLiteral(): BodyLiteral {\n // Negation\n if (this.peek().kind === 'NOT') {\n this.advance();\n const atom = this.parseAtom();\n return { kind: 'negated', atom };\n }\n\n // Try to detect comparison: term compOp term\n if (this.isComparisonStart()) {\n return this.parseComparison();\n }\n\n // Positive atom\n const atom = this.parseAtom();\n return { kind: 'positive', atom };\n }\n\n /**\n * Detect whether the current position starts a comparison.\n *\n * A comparison starts with a term (VARIABLE, STRING, NUMBER, UNDERSCORE)\n * followed by a comparison operator. We look ahead to distinguish this\n * from an atom (which starts with IDENT followed by LPAREN).\n */\n private isComparisonStart(): boolean {\n const current = this.peek();\n // A comparison starts with a term, not an IDENT (atoms start with IDENT)\n if (\n current.kind === 'VARIABLE' ||\n current.kind === 'STRING' ||\n current.kind === 'NUMBER' ||\n current.kind === 'UNDERSCORE'\n ) {\n // Check if the next token after the term is a comparison op\n if (this.pos + 1 < this.tokens.length) {\n const next = this.tokens[this.pos + 1];\n return COMPARISON_OPS.has(next.kind);\n }\n }\n return false;\n }\n\n /** comparison = term compOp term */\n private parseComparison(): BodyLiteral {\n const left = this.parseTerm();\n const opToken = this.advance();\n if (!COMPARISON_OPS.has(opToken.kind)) {\n throw new DatalogSyntaxError(\n `Expected comparison operator, got ${opToken.kind}`,\n opToken.line,\n opToken.col,\n );\n }\n const op = TOKEN_TO_COMP_OP[opToken.kind];\n const right = this.parseTerm();\n return { kind: 'comparison', op, left, right };\n }\n\n /** atom = IDENT \"(\" termList \")\" */\n private parseAtom(): Atom {\n const identToken = this.expect('IDENT');\n const predicate = identToken.value;\n\n this.expect('LPAREN');\n const args = this.parseTermList();\n this.expect('RPAREN');\n\n return { predicate, args };\n }\n\n /** termList = term (\",\" term)* */\n private parseTermList(): Term[] {\n const terms: Term[] = [];\n terms.push(this.parseTerm());\n\n while (this.match('COMMA')) {\n terms.push(this.parseTerm());\n }\n\n return terms;\n }\n\n /** term = VARIABLE | STRING | NUMBER | UNDERSCORE */\n private parseTerm(): Term {\n const token = this.peek();\n\n if (token.kind === 'VARIABLE') {\n this.advance();\n return { kind: 'variable', name: token.value };\n }\n\n if (token.kind === 'STRING') {\n this.advance();\n return { kind: 'constant', value: token.value };\n }\n\n if (token.kind === 'NUMBER') {\n this.advance();\n return { kind: 'constant', value: Number(token.value) };\n }\n\n if (token.kind === 'UNDERSCORE') {\n this.advance();\n return { kind: 'variable', name: this.freshAnon() };\n }\n\n throw new DatalogSyntaxError(\n `Expected term (variable, string, number, or _), got ${token.kind} ('${token.value}')`,\n token.line,\n token.col,\n );\n }\n\n // ===========================================================\n // Safety validation\n // ===========================================================\n\n /**\n * Validate that every variable in the rule head appears in at least\n * one positive body literal.\n *\n * Facts (rules with empty body) are exempt: their head may only\n * contain constants, or variables that are trivially safe because\n * there is no body at all. In standard Datalog, facts should only\n * have constants, but we allow variable-free heads to pass.\n *\n * @throws DatalogSafetyError if a head variable is not grounded\n */\n private validateSafety(rule: Rule): void {\n // Facts (empty body) are exempt from safety checks\n if (rule.body.length === 0) {\n return;\n }\n\n // Collect variables from positive body literals\n const positiveVars = new Set<string>();\n for (const literal of rule.body) {\n if (literal.kind === 'positive') {\n for (const arg of literal.atom.args) {\n if (arg.kind === 'variable') {\n positiveVars.add(arg.name);\n }\n }\n }\n }\n\n // Check that every head variable appears in positiveVars\n for (const arg of rule.head.args) {\n if (arg.kind === 'variable') {\n if (!positiveVars.has(arg.name)) {\n throw new DatalogSafetyError(\n `Unsafe variable '${arg.name}' in head of rule '${rule.head.predicate}': ` +\n `it does not appear in any positive body literal`,\n );\n }\n }\n }\n }\n}\n","/**\n * sonobat — Datalog naive bottom-up evaluator\n *\n * Takes a parsed Program AST and base facts, then:\n * 1. Initializes a fact database from base facts and inline facts (empty-body rules)\n * 2. Repeatedly applies rules until a fixed point (no new facts derived)\n * 3. Evaluates queries against the final fact set\n *\n * Uses a naive semi-naive-style evaluation with Map-based fact storage.\n * Supports positive literals, negated literals, and comparison operators.\n */\n\nimport {\n DatalogResourceError,\n DEFAULT_EVAL_CONFIG,\n} from './types.js';\nimport type {\n Program,\n Rule,\n Atom,\n BodyLiteral,\n Term,\n ComparisonOp,\n Fact,\n Tuple,\n EvalConfig,\n EvalResult,\n EvalStats,\n QueryAnswer,\n} from './types.js';\n\n// ============================================================\n// Binding type — maps variable names to concrete values\n// ============================================================\n\ntype Binding = Map<string, string | number>;\n\n// ============================================================\n// FactDB — stores facts indexed by predicate name\n// ============================================================\n\nclass FactDB {\n private readonly store: Map<string, Tuple[]> = new Map();\n private readonly seen: Map<string, Set<string>> = new Map();\n private totalCount = 0;\n\n /** Get all tuples for a predicate. */\n get(predicate: string): ReadonlyArray<Tuple> {\n return this.store.get(predicate) ?? [];\n }\n\n /** Total number of stored tuples across all predicates. */\n get size(): number {\n return this.totalCount;\n }\n\n /**\n * Add a tuple for a predicate. Returns true if it was new.\n * Uses a serialized key for deduplication.\n */\n add(predicate: string, tuple: Tuple): boolean {\n const key = serializeTuple(tuple);\n\n let seenSet = this.seen.get(predicate);\n if (!seenSet) {\n seenSet = new Set();\n this.seen.set(predicate, seenSet);\n }\n\n if (seenSet.has(key)) {\n return false;\n }\n\n seenSet.add(key);\n\n let tuples = this.store.get(predicate);\n if (!tuples) {\n tuples = [];\n this.store.set(predicate, tuples);\n }\n tuples.push(tuple);\n this.totalCount++;\n return true;\n }\n}\n\n/** Serialize a tuple into a unique string key for deduplication. */\nfunction serializeTuple(tuple: Tuple): string {\n return tuple\n .map((v) => (typeof v === 'number' ? `n:${v}` : `s:${v}`))\n .join('\\0');\n}\n\n// ============================================================\n// Core evaluation\n// ============================================================\n\n/**\n * Evaluate a Datalog program with base facts.\n *\n * @param program - Parsed Datalog program (rules + queries)\n * @param baseFacts - Base facts from the database\n * @param config - Optional resource limits\n * @returns Evaluation result with query answers and statistics\n * @throws DatalogResourceError when resource limits are exceeded\n */\nexport function evaluate(\n program: Program,\n baseFacts: Fact[],\n config?: Partial<EvalConfig>,\n): EvalResult {\n const cfg: EvalConfig = { ...DEFAULT_EVAL_CONFIG, ...config };\n const startTime = performance.now();\n\n // Separate facts (empty-body rules) from real rules\n const inlineFacts: Rule[] = [];\n const realRules: Rule[] = [];\n for (const rule of program.rules) {\n if (rule.body.length === 0) {\n inlineFacts.push(rule);\n } else {\n realRules.push(rule);\n }\n }\n\n // Check maxRules limit\n if (realRules.length > cfg.maxRules) {\n throw new DatalogResourceError(\n `Number of rules (${realRules.length}) exceeds maxRules limit (${cfg.maxRules})`,\n );\n }\n\n // Initialize fact database\n const db = new FactDB();\n\n // Load base facts\n for (const fact of baseFacts) {\n db.add(fact.predicate, fact.values);\n }\n\n // Load inline facts (rules with empty body)\n for (const rule of inlineFacts) {\n const tuple = rule.head.args.map((arg) => {\n if (arg.kind === 'constant') {\n return arg.value;\n }\n // Variables in facts without body are not meaningful,\n // but we allow them as-is (parser already validates safety)\n return arg.name;\n });\n db.add(rule.head.predicate, tuple);\n }\n\n // Fixed-point evaluation\n const stats: EvalStats = { iterations: 0, totalDerived: 0, elapsedMs: 0 };\n\n if (realRules.length > 0) {\n let changed = true;\n while (changed) {\n // Check timeout\n const elapsed = performance.now() - startTime;\n if (elapsed > cfg.timeoutMs) {\n throw new DatalogResourceError(\n `Evaluation timeout: exceeded ${cfg.timeoutMs}ms`,\n );\n }\n\n // Check iteration limit\n if (stats.iterations >= cfg.maxIterations) {\n throw new DatalogResourceError(\n `Iteration limit exceeded: ${cfg.maxIterations}`,\n );\n }\n\n changed = false;\n stats.iterations++;\n\n for (const rule of realRules) {\n const derivedTuples = evaluateRule(rule, db);\n for (const tuple of derivedTuples) {\n // Check tuple limit\n if (db.size >= cfg.maxTuples) {\n throw new DatalogResourceError(\n `Tuple limit exceeded: ${cfg.maxTuples}`,\n );\n }\n const isNew = db.add(rule.head.predicate, tuple);\n if (isNew) {\n changed = true;\n stats.totalDerived++;\n }\n }\n }\n }\n }\n\n stats.elapsedMs = performance.now() - startTime;\n\n // Evaluate queries\n const answers: QueryAnswer[] = program.queries.map((q) =>\n evaluateQuery(q.atom, db),\n );\n\n return { answers, stats };\n}\n\n// ============================================================\n// Rule evaluation — derive new tuples from a single rule\n// ============================================================\n\n/**\n * Evaluate a single rule against the fact database.\n * Returns all new tuples derivable by the rule's head.\n */\nfunction evaluateRule(rule: Rule, db: FactDB): Tuple[] {\n const bindings = evaluateBody(rule.body, 0, new Map(), db);\n const result: Tuple[] = [];\n const seen = new Set<string>();\n\n for (const binding of bindings) {\n const tuple = instantiateHead(rule.head, binding);\n const key = serializeTuple(tuple);\n if (!seen.has(key)) {\n seen.add(key);\n result.push(tuple);\n }\n }\n\n return result;\n}\n\n/**\n * Recursively evaluate body literals against the database.\n * Returns all valid bindings that satisfy all body literals.\n *\n * @param body - Array of body literals to evaluate\n * @param index - Current literal index being processed\n * @param binding - Current variable bindings\n * @param db - Fact database\n * @returns Array of complete bindings satisfying all literals\n */\nfunction evaluateBody(\n body: BodyLiteral[],\n index: number,\n binding: Binding,\n db: FactDB,\n): Binding[] {\n // Base case: all literals satisfied\n if (index >= body.length) {\n return [binding];\n }\n\n const literal = body[index];\n const results: Binding[] = [];\n\n if (literal.kind === 'positive') {\n // Try unifying the atom against all matching facts\n const facts = db.get(literal.atom.predicate);\n for (const factTuple of facts) {\n const newBinding = unifyAtom(literal.atom, factTuple, binding);\n if (newBinding !== null) {\n const subResults = evaluateBody(body, index + 1, newBinding, db);\n for (const r of subResults) {\n results.push(r);\n }\n }\n }\n } else if (literal.kind === 'negated') {\n // Negation: succeeds if NO facts match under current binding\n const facts = db.get(literal.atom.predicate);\n let anyMatch = false;\n for (const factTuple of facts) {\n const newBinding = unifyAtom(literal.atom, factTuple, binding);\n if (newBinding !== null) {\n anyMatch = true;\n break;\n }\n }\n if (!anyMatch) {\n // Negation succeeds — continue with current binding\n const subResults = evaluateBody(body, index + 1, binding, db);\n for (const r of subResults) {\n results.push(r);\n }\n }\n } else if (literal.kind === 'comparison') {\n // Comparison: evaluate against bound values\n if (evaluateComparison(literal.op, literal.left, literal.right, binding)) {\n const subResults = evaluateBody(body, index + 1, binding, db);\n for (const r of subResults) {\n results.push(r);\n }\n }\n }\n\n return results;\n}\n\n// ============================================================\n// Unification\n// ============================================================\n\n/**\n * Attempt to unify an atom's arguments with a fact tuple,\n * extending the given binding. Returns a new binding on success,\n * or null if unification fails.\n */\nfunction unifyAtom(\n atom: Atom,\n factTuple: Tuple,\n binding: Binding,\n): Binding | null {\n // Arity mismatch\n if (atom.args.length !== factTuple.length) {\n return null;\n }\n\n // Work on a copy so we don't mutate the input\n let current = new Map(binding);\n\n for (let i = 0; i < atom.args.length; i++) {\n const term = atom.args[i];\n const value = factTuple[i];\n\n const result = unifyTerm(term, value, current);\n if (result === null) {\n return null;\n }\n current = result;\n }\n\n return current;\n}\n\n/**\n * Unify a single term against a concrete value.\n * Returns updated binding on success, null on failure.\n */\nfunction unifyTerm(\n term: Term,\n value: string | number,\n binding: Binding,\n): Binding | null {\n if (term.kind === 'constant') {\n // Constant must match exactly\n return term.value === value ? binding : null;\n }\n\n // Variable — check if already bound\n const existing = binding.get(term.name);\n if (existing !== undefined) {\n // Must match the existing binding\n return existing === value ? binding : null;\n }\n\n // Bind the variable\n const newBinding = new Map(binding);\n newBinding.set(term.name, value);\n return newBinding;\n}\n\n// ============================================================\n// Comparison evaluation\n// ============================================================\n\n/**\n * Evaluate a comparison operator against bound term values.\n * Returns false if any term is unbound (which shouldn't happen\n * in safe Datalog, but we handle it defensively).\n */\nfunction evaluateComparison(\n op: ComparisonOp,\n left: Term,\n right: Term,\n binding: Binding,\n): boolean {\n const leftVal = resolveTerm(left, binding);\n const rightVal = resolveTerm(right, binding);\n\n if (leftVal === null || rightVal === null) {\n return false;\n }\n\n switch (op) {\n case '=':\n return leftVal === rightVal;\n case '!=':\n return leftVal !== rightVal;\n case '<':\n return leftVal < rightVal;\n case '>':\n return leftVal > rightVal;\n case '<=':\n return leftVal <= rightVal;\n case '>=':\n return leftVal >= rightVal;\n }\n}\n\n/**\n * Resolve a term to its concrete value using the current binding.\n * Returns null if a variable is unbound.\n */\nfunction resolveTerm(\n term: Term,\n binding: Binding,\n): string | number | null {\n if (term.kind === 'constant') {\n return term.value;\n }\n const val = binding.get(term.name);\n return val !== undefined ? val : null;\n}\n\n// ============================================================\n// Head instantiation\n// ============================================================\n\n/**\n * Instantiate a rule head with the given binding,\n * producing a concrete tuple.\n */\nfunction instantiateHead(head: Atom, binding: Binding): Tuple {\n return head.args.map((term) => {\n if (term.kind === 'constant') {\n return term.value;\n }\n const val = binding.get(term.name);\n if (val === undefined) {\n // This shouldn't happen in a safe Datalog program\n return term.name;\n }\n return val;\n });\n}\n\n// ============================================================\n// Query evaluation\n// ============================================================\n\n/**\n * Evaluate a query atom against the final fact database.\n * Returns matching tuples and the column names (variable names in the query).\n */\nfunction evaluateQuery(atom: Atom, db: FactDB): QueryAnswer {\n // Extract column names (variable names in query)\n const columns: string[] = [];\n for (const arg of atom.args) {\n if (arg.kind === 'variable') {\n columns.push(arg.name);\n }\n }\n\n // Find matching tuples\n const facts = db.get(atom.predicate);\n const tuples: Tuple[] = [];\n const seen = new Set<string>();\n\n for (const factTuple of facts) {\n const binding = unifyAtom(atom, factTuple, new Map());\n if (binding !== null) {\n // For the result, return the full fact tuple (not just bound vars)\n const key = serializeTuple(factTuple);\n if (!seen.has(key)) {\n seen.add(key);\n tuples.push(factTuple);\n }\n }\n }\n\n return {\n query: atom,\n tuples,\n columns,\n };\n}\n","/**\n * sonobat — Datalog fact extractor\n *\n * Extracts Fact[] from the SQLite database for use by the Datalog evaluator.\n * Each database table maps to one or more fact predicates.\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Fact } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Row types for direct SQL queries\n// ---------------------------------------------------------------------------\n\ninterface HostRow {\n id: string;\n authority: string;\n authority_kind: string;\n}\n\ninterface ServiceRow {\n host_id: string;\n id: string;\n transport: string;\n port: number;\n app_proto: string;\n state: string;\n}\n\ninterface HttpEndpointRow {\n service_id: string;\n id: string;\n method: string;\n path: string;\n status_code: number | null;\n}\n\ninterface InputRow {\n service_id: string;\n id: string;\n location: string;\n name: string;\n}\n\ninterface EndpointInputRow {\n endpoint_id: string;\n input_id: string;\n}\n\ninterface ObservationRow {\n input_id: string;\n id: string;\n raw_value: string;\n source: string;\n confidence: string;\n}\n\ninterface CredentialRow {\n service_id: string;\n id: string;\n username: string;\n secret_type: string;\n source: string;\n confidence: string;\n}\n\ninterface VulnerabilityRow {\n service_id: string;\n id: string;\n vuln_type: string;\n title: string;\n severity: string;\n confidence: string;\n endpoint_id: string | null;\n}\n\ninterface CveRow {\n vulnerability_id: string;\n cve_id: string;\n cvss_score: number | null;\n}\n\ninterface VhostRow {\n host_id: string;\n id: string;\n hostname: string;\n source: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Predicate extraction functions\n// ---------------------------------------------------------------------------\n\ntype ExtractorFn = (db: Database.Database, limit?: number) => Fact[];\n\nfunction extractHosts(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT id, authority, authority_kind FROM hosts LIMIT ?'\n : 'SELECT id, authority, authority_kind FROM hosts';\n const rows = limit !== undefined\n ? db.prepare<[number], HostRow>(sql).all(limit)\n : db.prepare<[], HostRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'host',\n values: [r.id, r.authority, r.authority_kind],\n }));\n}\n\nfunction extractServices(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT host_id, id, transport, port, app_proto, state FROM services LIMIT ?'\n : 'SELECT host_id, id, transport, port, app_proto, state FROM services';\n const rows = limit !== undefined\n ? db.prepare<[number], ServiceRow>(sql).all(limit)\n : db.prepare<[], ServiceRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'service',\n values: [r.host_id, r.id, r.transport, r.port, r.app_proto, r.state],\n }));\n}\n\nfunction extractHttpEndpoints(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT service_id, id, method, path, status_code FROM http_endpoints LIMIT ?'\n : 'SELECT service_id, id, method, path, status_code FROM http_endpoints';\n const rows = limit !== undefined\n ? db.prepare<[number], HttpEndpointRow>(sql).all(limit)\n : db.prepare<[], HttpEndpointRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'http_endpoint',\n values: [r.service_id, r.id, r.method, r.path, r.status_code ?? 0],\n }));\n}\n\nfunction extractInputs(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT service_id, id, location, name FROM inputs LIMIT ?'\n : 'SELECT service_id, id, location, name FROM inputs';\n const rows = limit !== undefined\n ? db.prepare<[number], InputRow>(sql).all(limit)\n : db.prepare<[], InputRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'input',\n values: [r.service_id, r.id, r.location, r.name],\n }));\n}\n\nfunction extractEndpointInputs(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT endpoint_id, input_id FROM endpoint_inputs LIMIT ?'\n : 'SELECT endpoint_id, input_id FROM endpoint_inputs';\n const rows = limit !== undefined\n ? db.prepare<[number], EndpointInputRow>(sql).all(limit)\n : db.prepare<[], EndpointInputRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'endpoint_input',\n values: [r.endpoint_id, r.input_id],\n }));\n}\n\nfunction extractObservations(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT input_id, id, raw_value, source, confidence FROM observations LIMIT ?'\n : 'SELECT input_id, id, raw_value, source, confidence FROM observations';\n const rows = limit !== undefined\n ? db.prepare<[number], ObservationRow>(sql).all(limit)\n : db.prepare<[], ObservationRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'observation',\n values: [r.input_id, r.id, r.raw_value, r.source, r.confidence],\n }));\n}\n\nfunction extractCredentials(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT service_id, id, username, secret_type, source, confidence FROM credentials LIMIT ?'\n : 'SELECT service_id, id, username, secret_type, source, confidence FROM credentials';\n const rows = limit !== undefined\n ? db.prepare<[number], CredentialRow>(sql).all(limit)\n : db.prepare<[], CredentialRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'credential',\n values: [r.service_id, r.id, r.username, r.secret_type, r.source, r.confidence],\n }));\n}\n\nfunction extractVulnerabilities(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT service_id, id, vuln_type, title, severity, confidence, endpoint_id FROM vulnerabilities LIMIT ?'\n : 'SELECT service_id, id, vuln_type, title, severity, confidence, endpoint_id FROM vulnerabilities';\n const rows = limit !== undefined\n ? db.prepare<[number], VulnerabilityRow>(sql).all(limit)\n : db.prepare<[], VulnerabilityRow>(sql).all();\n\n const facts: Fact[] = [];\n for (const r of rows) {\n facts.push({\n predicate: 'vulnerability',\n values: [r.service_id, r.id, r.vuln_type, r.title, r.severity, r.confidence],\n });\n }\n return facts;\n}\n\nfunction extractVulnerabilityEndpoints(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT id, endpoint_id FROM vulnerabilities WHERE endpoint_id IS NOT NULL LIMIT ?'\n : 'SELECT id, endpoint_id FROM vulnerabilities WHERE endpoint_id IS NOT NULL';\n const rows = limit !== undefined\n ? db.prepare<[number], { id: string; endpoint_id: string }>(sql).all(limit)\n : db.prepare<[], { id: string; endpoint_id: string }>(sql).all();\n return rows.map((r) => ({\n predicate: 'vulnerability_endpoint',\n values: [r.id, r.endpoint_id],\n }));\n}\n\nfunction extractCves(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT vulnerability_id, cve_id, cvss_score FROM cves LIMIT ?'\n : 'SELECT vulnerability_id, cve_id, cvss_score FROM cves';\n const rows = limit !== undefined\n ? db.prepare<[number], CveRow>(sql).all(limit)\n : db.prepare<[], CveRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'cve',\n values: [r.vulnerability_id, r.cve_id, r.cvss_score ?? 0],\n }));\n}\n\nfunction extractVhosts(db: Database.Database, limit?: number): Fact[] {\n const sql = limit !== undefined\n ? 'SELECT host_id, id, hostname, source FROM vhosts LIMIT ?'\n : 'SELECT host_id, id, hostname, source FROM vhosts';\n const rows = limit !== undefined\n ? db.prepare<[number], VhostRow>(sql).all(limit)\n : db.prepare<[], VhostRow>(sql).all();\n return rows.map((r) => ({\n predicate: 'vhost',\n values: [r.host_id, r.id, r.hostname, r.source ?? ''],\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Predicate → extractor mapping\n// ---------------------------------------------------------------------------\n\nconst EXTRACTORS: ReadonlyMap<string, ExtractorFn> = new Map<string, ExtractorFn>([\n ['host', extractHosts],\n ['service', extractServices],\n ['http_endpoint', extractHttpEndpoints],\n ['input', extractInputs],\n ['endpoint_input', extractEndpointInputs],\n ['observation', extractObservations],\n ['credential', extractCredentials],\n ['vulnerability', extractVulnerabilities],\n ['vulnerability_endpoint', extractVulnerabilityEndpoints],\n ['cve', extractCves],\n ['vhost', extractVhosts],\n]);\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Extract all facts from the SQLite database.\n *\n * Iterates over every supported predicate and extracts facts\n * from the corresponding tables.\n */\nexport function extractFacts(db: Database.Database): Fact[] {\n const facts: Fact[] = [];\n for (const extractor of EXTRACTORS.values()) {\n facts.push(...extractor(db));\n }\n return facts;\n}\n\n/**\n * Extract facts for a specific predicate only.\n *\n * Used by the `list_facts` MCP tool to retrieve facts\n * for a single predicate with optional limit.\n *\n * @param db - SQLite database instance\n * @param predicate - The fact predicate name (e.g. 'host', 'service')\n * @param limit - Optional maximum number of facts to return\n * @returns Array of facts matching the predicate\n */\nexport function extractFactsByPredicate(\n db: Database.Database,\n predicate: string,\n limit?: number,\n): Fact[] {\n const extractor = EXTRACTORS.get(predicate);\n if (!extractor) {\n return [];\n }\n return extractor(db, limit);\n}\n","/**\n * sonobat — Datalog preset rules\n *\n * Predefined Datalog rule patterns for common penetration testing\n * analysis queries. These rules can be loaded by name and executed\n * against the fact base extracted from the database.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface PresetRule {\n name: string;\n description: string;\n ruleText: string;\n}\n\n// ---------------------------------------------------------------------------\n// Preset rule definitions\n// ---------------------------------------------------------------------------\n\nconst PRESET_RULES: readonly PresetRule[] = [\n {\n name: 'reachable_services',\n description: 'Find all open services on each host with their port and application protocol.',\n ruleText: [\n 'reachable(Host, Port, AppProto) :- service(Host, _, _, Port, AppProto, \"open\").',\n '?- reachable(Host, Port, AppProto).',\n ].join('\\n'),\n },\n {\n name: 'authenticated_access',\n description: 'Services with discovered credentials, showing host, port, and username.',\n ruleText: [\n 'auth_access(Host, Port, Username) :- service(Host, Svc, _, Port, _, \"open\"), credential(Svc, _, Username, _, _, _).',\n '?- auth_access(Host, Port, Username).',\n ].join('\\n'),\n },\n {\n name: 'exploitable_endpoints',\n description: 'HTTP endpoints with known vulnerabilities, including vulnerability type and severity.',\n ruleText: [\n 'exploitable(Host, Port, Path, VulnType, Severity) :- service(Host, Svc, _, Port, _, _), http_endpoint(Svc, Ep, _, Path, _), vulnerability_endpoint(Vuln, Ep), vulnerability(Svc, Vuln, VulnType, _, Severity, _).',\n '?- exploitable(Host, Port, Path, VulnType, Severity).',\n ].join('\\n'),\n },\n {\n name: 'critical_vulns',\n description: 'Critical severity vulnerabilities with host, port, title, and type.',\n ruleText: [\n 'critical(Host, Port, Title, VulnType) :- service(Host, Svc, _, Port, _, _), vulnerability(Svc, _, VulnType, Title, \"critical\", _).',\n '?- critical(Host, Port, Title, VulnType).',\n ].join('\\n'),\n },\n {\n name: 'attack_surface',\n description: 'Full attack surface overview combining host, port, endpoint path, input name, and input location.',\n ruleText: [\n 'surface(Host, Port, Path, InputName, Location) :- service(Host, Svc, _, Port, _, \"open\"), http_endpoint(Svc, Ep, _, Path, _), endpoint_input(Ep, Inp), input(Svc, Inp, Location, InputName).',\n '?- surface(Host, Port, Path, InputName, Location).',\n ].join('\\n'),\n },\n {\n name: 'unfuzzed_inputs',\n description: 'Inputs with observations but no associated vulnerability endpoint — candidates for fuzzing.',\n ruleText: [\n 'unfuzzed(Host, Port, Path, InputName) :- service(Host, Svc, _, Port, _, \"open\"), http_endpoint(Svc, Ep, _, Path, _), endpoint_input(Ep, Inp), input(Svc, Inp, _, InputName), observation(Inp, _, _, _, _), not vulnerability_endpoint(_, Ep).',\n '?- unfuzzed(Host, Port, Path, InputName).',\n ].join('\\n'),\n },\n] as const;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Get all preset Datalog rules.\n *\n * @returns Array of all preset rules\n */\nexport function getPresetRules(): PresetRule[] {\n return [...PRESET_RULES];\n}\n\n/**\n * Get a specific preset rule by name.\n *\n * @param name - The preset rule name (e.g. 'reachable_services')\n * @returns The preset rule if found, undefined otherwise\n */\nexport function getPresetRule(name: string): PresetRule | undefined {\n return PRESET_RULES.find((r) => r.name === name);\n}\n","import type Database from 'better-sqlite3';\nimport crypto from 'node:crypto';\nimport type { DatalogRule, CreateDatalogRuleInput } from '../../engine/datalog/types.js';\n\n/**\n * Raw row shape returned by better-sqlite3 for the `datalog_rules` table.\n */\ninterface DatalogRuleRow {\n id: string;\n name: string;\n description: string | null;\n rule_text: string;\n generated_by: string;\n is_preset: number;\n created_at: string;\n updated_at: string;\n}\n\n/** Maps a snake_case DB row to a camelCase DatalogRule entity. */\nfunction rowToDatalogRule(row: DatalogRuleRow): DatalogRule {\n return {\n id: row.id,\n name: row.name,\n description: row.description ?? undefined,\n ruleText: row.rule_text,\n generatedBy: row.generated_by as DatalogRule['generatedBy'],\n isPreset: row.is_preset === 1,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\n/**\n * Repository for the `datalog_rules` table.\n */\nexport class DatalogRuleRepository {\n private readonly db: Database.Database;\n\n constructor(db: Database.Database) {\n this.db = db;\n }\n\n /** Insert a new DatalogRule and return the full entity. */\n create(input: CreateDatalogRuleInput): DatalogRule {\n const id = crypto.randomUUID();\n const now = new Date().toISOString();\n\n const stmt = this.db.prepare<\n [string, string, string | null, string, string, number, string, string]\n >(\n `INSERT INTO datalog_rules (id, name, description, rule_text, generated_by, is_preset, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n );\n\n stmt.run(\n id,\n input.name,\n input.description ?? null,\n input.ruleText,\n input.generatedBy,\n input.isPreset ? 1 : 0,\n now,\n now,\n );\n\n return {\n id,\n name: input.name,\n description: input.description,\n ruleText: input.ruleText,\n generatedBy: input.generatedBy,\n isPreset: input.isPreset ?? false,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /** Find a DatalogRule by its primary key. */\n findById(id: string): DatalogRule | undefined {\n const stmt = this.db.prepare<[string], DatalogRuleRow>(\n `SELECT id, name, description, rule_text, generated_by, is_preset, created_at, updated_at\n FROM datalog_rules WHERE id = ?`,\n );\n const row = stmt.get(id);\n return row ? rowToDatalogRule(row) : undefined;\n }\n\n /** Find a DatalogRule by name. */\n findByName(name: string): DatalogRule | undefined {\n const stmt = this.db.prepare<[string], DatalogRuleRow>(\n `SELECT id, name, description, rule_text, generated_by, is_preset, created_at, updated_at\n FROM datalog_rules WHERE name = ?`,\n );\n const row = stmt.get(name);\n return row ? rowToDatalogRule(row) : undefined;\n }\n\n /** Return all DatalogRules. */\n findAll(): DatalogRule[] {\n const stmt = this.db.prepare<[], DatalogRuleRow>(\n `SELECT id, name, description, rule_text, generated_by, is_preset, created_at, updated_at\n FROM datalog_rules ORDER BY created_at`,\n );\n return stmt.all().map(rowToDatalogRule);\n }\n\n /** Delete a DatalogRule by id. Returns true if a row was deleted. */\n delete(id: string): boolean {\n const stmt = this.db.prepare<[string]>('DELETE FROM datalog_rules WHERE id = ?');\n const result = stmt.run(id);\n return result.changes > 0;\n }\n}\n","/**\n * sonobat — Datalog inference engine public API\n *\n * Provides three main functions:\n * - listFacts: Show database contents as Datalog facts\n * - runDatalog: Execute a custom Datalog program against the database\n * - queryAttackPaths: Run a preset attack pattern analysis\n */\n\nimport type Database from 'better-sqlite3';\nimport type { EvalConfig, EvalResult, Fact } from './types.js';\nimport { parse } from './parser.js';\nimport { evaluate } from './evaluator.js';\nimport { extractFacts, extractFactsByPredicate } from './fact-extractor.js';\nimport { getPresetRule, getPresetRules } from './preset-rules.js';\nimport type { PresetRule } from './preset-rules.js';\nimport { DatalogRuleRepository } from '../../db/repository/datalog-rule-repository.js';\n\nexport type { PresetRule };\n\n/**\n * List facts from the database, optionally filtered by predicate.\n */\nexport function listFacts(\n db: Database.Database,\n predicate?: string,\n limit?: number,\n): Fact[] {\n if (predicate !== undefined) {\n return extractFactsByPredicate(db, predicate, limit);\n }\n const facts = extractFacts(db);\n if (limit !== undefined && limit > 0) {\n return facts.slice(0, limit);\n }\n return facts;\n}\n\n/**\n * Run a custom Datalog program against the database.\n * Optionally save the program as a named rule for future reuse.\n */\nexport function runDatalog(\n db: Database.Database,\n program: string,\n options?: {\n config?: Partial<EvalConfig>;\n saveName?: string;\n saveDescription?: string;\n generatedBy?: 'human' | 'ai';\n },\n): EvalResult {\n const ast = parse(program);\n const facts = extractFacts(db);\n const result = evaluate(ast, facts, options?.config);\n\n // Optionally save the rule\n if (options?.saveName !== undefined) {\n const ruleRepo = new DatalogRuleRepository(db);\n ruleRepo.create({\n name: options.saveName,\n description: options.saveDescription,\n ruleText: program,\n generatedBy: options.generatedBy ?? 'ai',\n });\n }\n\n return result;\n}\n\n/**\n * Run a preset or saved attack pattern query.\n */\nexport function queryAttackPaths(\n db: Database.Database,\n pattern: string,\n config?: Partial<EvalConfig>,\n): EvalResult {\n // Try preset rules first\n const preset = getPresetRule(pattern);\n if (preset !== undefined) {\n const ast = parse(preset.ruleText);\n const facts = extractFacts(db);\n return evaluate(ast, facts, config);\n }\n\n // Then try saved rules from DB\n const ruleRepo = new DatalogRuleRepository(db);\n const savedRule = ruleRepo.findByName(pattern);\n if (savedRule !== undefined) {\n const ast = parse(savedRule.ruleText);\n const facts = extractFacts(db);\n return evaluate(ast, facts, config);\n }\n\n // Return empty result if pattern not found\n return {\n answers: [],\n stats: { iterations: 0, totalDerived: 0, elapsedMs: 0 },\n };\n}\n\n/**\n * List all available patterns (presets + saved rules).\n */\nexport function listPatterns(db: Database.Database): Array<{\n name: string;\n description?: string;\n source: 'preset' | 'saved';\n generatedBy?: string;\n}> {\n const presets = getPresetRules().map((p) => ({\n name: p.name,\n description: p.description,\n source: 'preset' as const,\n }));\n\n const ruleRepo = new DatalogRuleRepository(db);\n const saved = ruleRepo.findAll().map((r) => ({\n name: r.name,\n description: r.description,\n source: 'saved' as const,\n generatedBy: r.generatedBy,\n }));\n\n return [...presets, ...saved];\n}\n","/**\n * sonobat — MCP Resources\n *\n * Read-only resources for browsing the AttackDataGraph.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { HostRepository } from '../db/repository/host-repository.js';\nimport { ServiceRepository } from '../db/repository/service-repository.js';\nimport { VhostRepository } from '../db/repository/vhost-repository.js';\nimport { HttpEndpointRepository } from '../db/repository/http-endpoint-repository.js';\nimport { InputRepository } from '../db/repository/input-repository.js';\nimport { VulnerabilityRepository } from '../db/repository/vulnerability-repository.js';\n\nexport function registerResources(server: McpServer, db: Database.Database): void {\n const hostRepo = new HostRepository(db);\n const serviceRepo = new ServiceRepository(db);\n const vhostRepo = new VhostRepository(db);\n const httpEndpointRepo = new HttpEndpointRepository(db);\n const inputRepo = new InputRepository(db);\n const vulnRepo = new VulnerabilityRepository(db);\n\n // 1. sonobat://hosts — Host list\n server.resource('hosts', 'sonobat://hosts', { description: 'List of all discovered hosts' }, async () => {\n const hosts = hostRepo.findAll();\n return {\n contents: [\n {\n uri: 'sonobat://hosts',\n mimeType: 'application/json',\n text: JSON.stringify(hosts, null, 2),\n },\n ],\n };\n });\n\n // 2. sonobat://hosts/{id} — Host detail tree\n server.resource(\n 'host-detail',\n 'sonobat://hosts/{id}',\n { description: 'Detailed host tree with services, endpoints, inputs, and vulnerabilities' },\n async (uri) => {\n // Extract host ID from the URI\n const hostId = uri.pathname.split('/').pop() ?? '';\n const host = hostRepo.findById(hostId);\n if (!host) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify({ error: `Host not found: ${hostId}` }),\n },\n ],\n };\n }\n\n const services = serviceRepo.findByHostId(hostId);\n const vhosts = vhostRepo.findByHostId(hostId);\n\n const serviceTree = services.map((service) => {\n const endpoints = httpEndpointRepo.findByServiceId(service.id);\n const inputs = inputRepo.findByServiceId(service.id);\n const vulnerabilities = vulnRepo.findByServiceId(service.id);\n return { ...service, endpoints, inputs, vulnerabilities };\n });\n\n const result = { ...host, services: serviceTree, vhosts };\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'application/json',\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n },\n );\n\n // 3. sonobat://summary — Statistics summary\n server.resource(\n 'summary',\n 'sonobat://summary',\n { description: 'Summary statistics of the AttackDataGraph' },\n async () => {\n // Use direct SQL COUNT queries for efficiency\n const counts: Record<string, number> = {};\n const tables = [\n 'hosts',\n 'services',\n 'http_endpoints',\n 'inputs',\n 'observations',\n 'credentials',\n 'vulnerabilities',\n 'cves',\n 'vhosts',\n 'artifacts',\n ];\n\n for (const table of tables) {\n const row = db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get() as { count: number };\n counts[table] = row.count;\n }\n\n return {\n contents: [\n {\n uri: 'sonobat://summary',\n mimeType: 'application/json',\n text: JSON.stringify(counts, null, 2),\n },\n ],\n };\n },\n );\n}\n"],"mappings":";;;AAOA,OAAO,cAAc;AACrB,SAAS,4BAA4B;;;ACD9B,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["db","db","crypto","db","crypto","db","crypto","db","crypto","db","stmt","rows","crypto","db","crypto","db","crypto","db","stmt","rows","server","db","z","crypto","crypto","db","transport","isRecord","crypto","db","crypto","db","crypto","db","db","db","crypto","server","db","z","path","z","db","server","db","z","z","db","server","z","z","startCol","atom","db","db","crypto","db","db","server","db","z","server","db","db","server"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/db/migrations/v0.ts","../src/db/migrations/v1.ts","../src/db/migrations/v2.ts","../src/db/migrations/v3.ts","../src/db/migrations/v4.ts","../src/db/migrations/v5.ts","../src/db/migrations/index.ts","../src/db/migrate.ts","../src/mcp/server.ts","../src/mcp/tools/query.ts","../src/db/repository/node-repository.ts","../src/types/graph.ts","../src/db/repository/edge-repository.ts","../src/db/repository/graph-query-repository.ts","../src/mcp/tools/mutate.ts","../src/mcp/tools/ingest.ts","../src/engine/ingest.ts","../src/parser/nmap-parser.ts","../src/types/parser.ts","../src/parser/ffuf-parser.ts","../src/parser/nuclei-parser.ts","../src/engine/normalizer.ts","../src/mcp/tools/propose.ts","../src/engine/proposer.ts","../src/mcp/tools/kb.ts","../src/db/repository/technique-doc-repository.ts","../src/engine/indexer.ts","../src/engine/data-dir.ts","../src/engine/git-ops.ts","../src/mcp/resources.ts"],"sourcesContent":["/**\r\n * sonobat — AttackDataGraph for autonomous penetration testing\r\n *\r\n * MCP Server エントリポイント。\r\n * stdio トランスポートで LLM Agent と接続する。\r\n */\r\n\r\nimport { readFileSync } from 'node:fs';\r\nimport { dirname, resolve } from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport Database from 'better-sqlite3';\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\r\nimport { migrateDatabase } from './db/migrate.js';\r\nimport { createMcpServer } from './mcp/server.js';\r\n\r\nconst __dirname = dirname(fileURLToPath(import.meta.url));\r\nconst pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8')) as {\r\n version: string;\r\n};\r\n\r\nconst DB_PATH = process.env['SONOBAT_DB_PATH'] ?? 'sonobat.db';\r\nconst db = new Database(DB_PATH);\r\nmigrateDatabase(db);\r\n\r\nconst server = createMcpServer(db, pkg.version);\r\nconst transport = new StdioServerTransport();\r\nawait server.connect(transport);\r\n","/**\r\n * Migration v0: Base schema\r\n *\r\n * 元の sonobat スキーマ(v1/v2/v3 の追加分を除く)。\r\n * fresh DB でマイグレーション順次実行するための基盤。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Migration } from './index.js';\r\n\r\nconst migration: Migration = {\r\n version: 0,\r\n description: 'Base schema (scans, artifacts, hosts, services, endpoints, etc.)',\r\n up(db: Database.Database): void {\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS scans (\r\n id TEXT PRIMARY KEY,\r\n started_at TEXT NOT NULL,\r\n finished_at TEXT,\r\n notes TEXT\r\n );\r\n\r\n CREATE TABLE IF NOT EXISTS artifacts (\r\n id TEXT PRIMARY KEY,\r\n scan_id TEXT,\r\n tool TEXT NOT NULL,\r\n kind TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n sha256 TEXT,\r\n captured_at TEXT NOT NULL,\r\n attrs_json TEXT,\r\n FOREIGN KEY (scan_id) REFERENCES scans(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_tool ON artifacts(tool);\r\n\r\n CREATE TABLE IF NOT EXISTS hosts (\r\n id TEXT PRIMARY KEY,\r\n authority_kind TEXT NOT NULL,\r\n authority TEXT NOT NULL UNIQUE,\r\n resolved_ips_json TEXT NOT NULL DEFAULT '[]',\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n\r\n CREATE TABLE IF NOT EXISTS vhosts (\r\n id TEXT PRIMARY KEY,\r\n host_id TEXT NOT NULL,\r\n hostname TEXT NOT NULL,\r\n source TEXT,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (host_id, hostname),\r\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_vhosts_host ON vhosts(host_id);\r\n\r\n CREATE TABLE IF NOT EXISTS services (\r\n id TEXT PRIMARY KEY,\r\n host_id TEXT NOT NULL,\r\n transport TEXT NOT NULL,\r\n port INTEGER NOT NULL,\r\n app_proto TEXT NOT NULL,\r\n proto_confidence TEXT NOT NULL,\r\n banner TEXT,\r\n product TEXT,\r\n version TEXT,\r\n state TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n UNIQUE (host_id, transport, port),\r\n FOREIGN KEY (host_id) REFERENCES hosts(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_services_host ON services(host_id);\r\n\r\n CREATE TABLE IF NOT EXISTS service_observations (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n key TEXT NOT NULL,\r\n value TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_svc_obs_service ON service_observations(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS http_endpoints (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n vhost_id TEXT,\r\n base_uri TEXT NOT NULL,\r\n method TEXT NOT NULL,\r\n path TEXT NOT NULL,\r\n status_code INTEGER,\r\n content_length INTEGER,\r\n words INTEGER,\r\n lines INTEGER,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (service_id, method, path),\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (vhost_id) REFERENCES vhosts(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_endpoints_service ON http_endpoints(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS inputs (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n location TEXT NOT NULL,\r\n name TEXT NOT NULL,\r\n type_hint TEXT,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n UNIQUE (service_id, location, name),\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_inputs_service ON inputs(service_id);\r\n\r\n CREATE TABLE IF NOT EXISTS endpoint_inputs (\r\n id TEXT PRIMARY KEY,\r\n endpoint_id TEXT NOT NULL,\r\n input_id TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n UNIQUE (endpoint_id, input_id),\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE CASCADE,\r\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_ep_inputs_endpoint ON endpoint_inputs(endpoint_id);\r\n CREATE INDEX IF NOT EXISTS idx_ep_inputs_input ON endpoint_inputs(input_id);\r\n\r\n CREATE TABLE IF NOT EXISTS observations (\r\n id TEXT PRIMARY KEY,\r\n input_id TEXT NOT NULL,\r\n raw_value TEXT NOT NULL,\r\n norm_value TEXT NOT NULL,\r\n body_path TEXT,\r\n source TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n observed_at TEXT NOT NULL,\r\n FOREIGN KEY (input_id) REFERENCES inputs(id) ON DELETE CASCADE,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_obs_input ON observations(input_id);\r\n\r\n CREATE TABLE IF NOT EXISTS credentials (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n endpoint_id TEXT,\r\n username TEXT NOT NULL,\r\n secret TEXT NOT NULL,\r\n secret_type TEXT NOT NULL,\r\n source TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_creds_service ON credentials(service_id);\r\n CREATE INDEX IF NOT EXISTS idx_creds_endpoint ON credentials(endpoint_id);\r\n\r\n CREATE TABLE IF NOT EXISTS vulnerabilities (\r\n id TEXT PRIMARY KEY,\r\n service_id TEXT NOT NULL,\r\n endpoint_id TEXT,\r\n vuln_type TEXT NOT NULL,\r\n title TEXT NOT NULL,\r\n description TEXT,\r\n severity TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n evidence_artifact_id TEXT NOT NULL,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE,\r\n FOREIGN KEY (endpoint_id) REFERENCES http_endpoints(id) ON DELETE SET NULL,\r\n FOREIGN KEY (evidence_artifact_id) REFERENCES artifacts(id) ON DELETE RESTRICT\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_vulns_service ON vulnerabilities(service_id);\r\n CREATE INDEX IF NOT EXISTS idx_vulns_endpoint ON vulnerabilities(endpoint_id);\r\n CREATE INDEX IF NOT EXISTS idx_vulns_severity ON vulnerabilities(severity);\r\n\r\n CREATE TABLE IF NOT EXISTS cves (\r\n id TEXT PRIMARY KEY,\r\n vulnerability_id TEXT NOT NULL,\r\n cve_id TEXT NOT NULL,\r\n description TEXT,\r\n cvss_score REAL,\r\n cvss_vector TEXT,\r\n reference_url TEXT,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_cves_vuln ON cves(vulnerability_id);\r\n CREATE INDEX IF NOT EXISTS idx_cves_cveid ON cves(cve_id);\r\n `);\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\n * Migration v1: Add datalog_rules table\n *\n * This migration adds the datalog_rules table for storing\n * custom Datalog rules created by humans or AI agents.\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 1,\n description: 'Add datalog_rules table',\n up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS datalog_rules (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n description TEXT,\n rule_text TEXT NOT NULL,\n generated_by TEXT NOT NULL, -- \"human\" | \"ai\" | \"preset\"\n is_preset INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_datalog_rules_name ON datalog_rules(name);\n `);\n },\n};\n\nexport default migration;\n","/**\n * Migration v2: Add status column to vulnerabilities table\n *\n * Adds a `status` column to track the verification state of vulnerabilities.\n * Values: 'unverified' (default), 'confirmed', 'false_positive', 'not_exploitable'\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 2,\n description: 'Add status column to vulnerabilities table',\n up(db: Database.Database): void {\n db.exec(`\n ALTER TABLE vulnerabilities ADD COLUMN status TEXT NOT NULL DEFAULT 'unverified';\n `);\n },\n};\n\nexport default migration;\n","/**\n * Migration v3: Add technique_docs table with FTS5 full-text search\n *\n * Creates a technique documentation table for indexing penetration testing\n * knowledge (e.g. HackTricks) and a FTS5 virtual table for efficient search.\n */\n\nimport type Database from 'better-sqlite3';\nimport type { Migration } from './index.js';\n\nconst migration: Migration = {\n version: 3,\n description: 'Add technique_docs table with FTS5 full-text search',\n up(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS technique_docs (\n id TEXT PRIMARY KEY,\n source TEXT NOT NULL,\n file_path TEXT NOT NULL,\n title TEXT NOT NULL,\n category TEXT NOT NULL,\n content TEXT NOT NULL,\n chunk_index INTEGER NOT NULL,\n indexed_at TEXT NOT NULL\n );\n\n CREATE VIRTUAL TABLE IF NOT EXISTS technique_docs_fts USING fts5(\n title, category, content,\n content=technique_docs,\n content_rowid=rowid,\n tokenize='porter unicode61'\n );\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_ai AFTER INSERT ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(rowid, title, category, content)\n VALUES (new.rowid, new.title, new.category, new.content);\n END;\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_ad AFTER DELETE ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(technique_docs_fts, rowid, title, category, content)\n VALUES ('delete', old.rowid, old.title, old.category, old.content);\n END;\n\n CREATE TRIGGER IF NOT EXISTS technique_docs_au AFTER UPDATE ON technique_docs BEGIN\n INSERT INTO technique_docs_fts(technique_docs_fts, rowid, title, category, content)\n VALUES ('delete', old.rowid, old.title, old.category, old.content);\n INSERT INTO technique_docs_fts(rowid, title, category, content)\n VALUES (new.rowid, new.title, new.category, new.content);\n END;\n `);\n },\n};\n\nexport default migration;\n","/**\r\n * Migration v4: Graph-native schema\r\n *\r\n * 12 エンティティテーブル → nodes + edges の 2 テーブルに移行。\r\n * 既存データをマイグレーションし、旧テーブルを DROP する。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Migration } from './index.js';\r\nimport { randomUUID } from 'node:crypto';\r\n\r\nconst migration: Migration = {\r\n version: 4,\r\n description: 'Graph-native schema: nodes + edges tables',\r\n up(db: Database.Database): void {\r\n // --------------------------------------------------\r\n // 1. nodes + edges テーブル作成\r\n // --------------------------------------------------\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS nodes (\r\n id TEXT PRIMARY KEY,\r\n kind TEXT NOT NULL,\r\n natural_key TEXT NOT NULL UNIQUE,\r\n props_json TEXT NOT NULL DEFAULT '{}',\r\n evidence_artifact_id TEXT REFERENCES artifacts(id),\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);\r\n CREATE INDEX IF NOT EXISTS idx_nodes_evidence ON nodes(evidence_artifact_id);\r\n\r\n CREATE TABLE IF NOT EXISTS edges (\r\n id TEXT PRIMARY KEY,\r\n kind TEXT NOT NULL,\r\n source_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\r\n target_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,\r\n props_json TEXT NOT NULL DEFAULT '{}',\r\n evidence_artifact_id TEXT REFERENCES artifacts(id),\r\n created_at TEXT NOT NULL,\r\n UNIQUE(kind, source_id, target_id)\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);\r\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);\r\n CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);\r\n `);\r\n\r\n // --------------------------------------------------\r\n // 2. 既存データのマイグレーション\r\n // --------------------------------------------------\r\n\r\n // ヘルパー: edge 挿入\r\n const insertEdge = db.prepare(`\r\n INSERT INTO edges (id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at)\r\n VALUES (?, ?, ?, ?, '{}', ?, ?)\r\n `);\r\n\r\n const insertNode = db.prepare(`\r\n INSERT INTO nodes (id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `);\r\n\r\n // テーブル存在チェック\r\n const tableExists = (name: string): boolean => {\r\n const row = db\r\n .prepare(\"SELECT COUNT(*) AS cnt FROM sqlite_master WHERE type='table' AND name=?\")\r\n .get(name) as { cnt: number };\r\n return row.cnt > 0;\r\n };\r\n\r\n // 2a. hosts → nodes (kind=\"host\")\r\n if (tableExists('hosts')) {\r\n const hosts = db.prepare('SELECT * FROM hosts').all() as Array<Record<string, unknown>>;\r\n for (const h of hosts) {\r\n const props = JSON.stringify({\r\n authorityKind: h.authority_kind,\r\n authority: h.authority,\r\n resolvedIpsJson: h.resolved_ips_json ?? '[]',\r\n });\r\n insertNode.run(\r\n h.id,\r\n 'host',\r\n `host:${h.authority}`,\r\n props,\r\n null, // hosts didn't have evidence_artifact_id\r\n h.created_at,\r\n h.updated_at,\r\n );\r\n }\r\n }\r\n\r\n // 2b. vhosts → nodes (kind=\"vhost\") + edge (HOST_VHOST)\r\n if (tableExists('vhosts')) {\r\n const vhosts = db.prepare('SELECT * FROM vhosts').all() as Array<Record<string, unknown>>;\r\n for (const v of vhosts) {\r\n const props = JSON.stringify({\r\n hostname: v.hostname,\r\n source: v.source ?? undefined,\r\n });\r\n insertNode.run(\r\n v.id,\r\n 'vhost',\r\n `vhost:${v.host_id}:${v.hostname}`,\r\n props,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n v.created_at, // vhosts had no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'HOST_VHOST',\r\n v.host_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2c. services → nodes (kind=\"service\") + edge (HOST_SERVICE)\r\n if (tableExists('services')) {\r\n const services = db.prepare('SELECT * FROM services').all() as Array<Record<string, unknown>>;\r\n for (const s of services) {\r\n const props = JSON.stringify({\r\n transport: s.transport,\r\n port: s.port,\r\n appProto: s.app_proto,\r\n protoConfidence: s.proto_confidence,\r\n banner: s.banner ?? undefined,\r\n product: s.product ?? undefined,\r\n version: s.version ?? undefined,\r\n state: s.state,\r\n });\r\n insertNode.run(\r\n s.id,\r\n 'service',\r\n `svc:${s.host_id}:${s.transport}:${s.port}`,\r\n props,\r\n s.evidence_artifact_id,\r\n s.created_at,\r\n s.updated_at,\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'HOST_SERVICE',\r\n s.host_id,\r\n s.id,\r\n s.evidence_artifact_id,\r\n s.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2d. service_observations → nodes (kind=\"svc_observation\") + edge (SERVICE_OBSERVATION)\r\n if (tableExists('service_observations')) {\r\n const svcObs = db.prepare('SELECT * FROM service_observations').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const so of svcObs) {\r\n const props = JSON.stringify({\r\n key: so.key,\r\n value: so.value,\r\n confidence: so.confidence,\r\n });\r\n insertNode.run(\r\n so.id,\r\n 'svc_observation',\r\n `svcobs:${so.id}`,\r\n props,\r\n so.evidence_artifact_id,\r\n so.created_at,\r\n so.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_OBSERVATION',\r\n so.service_id,\r\n so.id,\r\n so.evidence_artifact_id,\r\n so.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2e. http_endpoints → nodes (kind=\"endpoint\") + edge (SERVICE_ENDPOINT) + optional (VHOST_ENDPOINT)\r\n if (tableExists('http_endpoints')) {\r\n const endpoints = db.prepare('SELECT * FROM http_endpoints').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const ep of endpoints) {\r\n const props = JSON.stringify({\r\n baseUri: ep.base_uri,\r\n method: ep.method,\r\n path: ep.path,\r\n statusCode: ep.status_code ?? undefined,\r\n contentLength: ep.content_length ?? undefined,\r\n words: ep.words ?? undefined,\r\n lines: ep.lines ?? undefined,\r\n });\r\n insertNode.run(\r\n ep.id,\r\n 'endpoint',\r\n `ep:${ep.service_id}:${ep.method}:${ep.path}`,\r\n props,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n ep.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_ENDPOINT',\r\n ep.service_id,\r\n ep.id,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n );\r\n // Optional: VHOST_ENDPOINT\r\n if (ep.vhost_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'VHOST_ENDPOINT',\r\n ep.vhost_id,\r\n ep.id,\r\n ep.evidence_artifact_id,\r\n ep.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2f. inputs → nodes (kind=\"input\") + edge (SERVICE_INPUT)\r\n if (tableExists('inputs')) {\r\n const inputs = db.prepare('SELECT * FROM inputs').all() as Array<Record<string, unknown>>;\r\n for (const inp of inputs) {\r\n const props = JSON.stringify({\r\n location: inp.location,\r\n name: inp.name,\r\n typeHint: inp.type_hint ?? undefined,\r\n });\r\n insertNode.run(\r\n inp.id,\r\n 'input',\r\n `in:${inp.service_id}:${inp.location}:${inp.name}`,\r\n props,\r\n null, // inputs had no evidence_artifact_id\r\n inp.created_at,\r\n inp.updated_at,\r\n );\r\n insertEdge.run(randomUUID(), 'SERVICE_INPUT', inp.service_id, inp.id, null, inp.created_at);\r\n }\r\n }\r\n\r\n // 2g. endpoint_inputs → edges (ENDPOINT_INPUT)\r\n if (tableExists('endpoint_inputs')) {\r\n const epInputs = db.prepare('SELECT * FROM endpoint_inputs').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const ei of epInputs) {\r\n insertEdge.run(\r\n ei.id,\r\n 'ENDPOINT_INPUT',\r\n ei.endpoint_id,\r\n ei.input_id,\r\n ei.evidence_artifact_id,\r\n ei.created_at,\r\n );\r\n }\r\n }\r\n\r\n // 2h. observations → nodes (kind=\"observation\") + edge (INPUT_OBSERVATION)\r\n if (tableExists('observations')) {\r\n const obs = db.prepare('SELECT * FROM observations').all() as Array<Record<string, unknown>>;\r\n for (const o of obs) {\r\n const props = JSON.stringify({\r\n rawValue: o.raw_value,\r\n normValue: o.norm_value,\r\n bodyPath: o.body_path ?? undefined,\r\n source: o.source,\r\n confidence: o.confidence,\r\n observedAt: o.observed_at,\r\n });\r\n insertNode.run(\r\n o.id,\r\n 'observation',\r\n `obs:${o.id}`,\r\n props,\r\n o.evidence_artifact_id,\r\n o.observed_at,\r\n o.observed_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'INPUT_OBSERVATION',\r\n o.input_id,\r\n o.id,\r\n o.evidence_artifact_id,\r\n o.observed_at,\r\n );\r\n }\r\n }\r\n\r\n // 2i. credentials → nodes (kind=\"credential\") + edge (SERVICE_CREDENTIAL) + optional (ENDPOINT_CREDENTIAL)\r\n if (tableExists('credentials')) {\r\n const creds = db.prepare('SELECT * FROM credentials').all() as Array<Record<string, unknown>>;\r\n for (const c of creds) {\r\n const props = JSON.stringify({\r\n username: c.username,\r\n secret: c.secret,\r\n secretType: c.secret_type,\r\n source: c.source,\r\n confidence: c.confidence,\r\n });\r\n insertNode.run(\r\n c.id,\r\n 'credential',\r\n `cred:${c.id}`,\r\n props,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n c.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_CREDENTIAL',\r\n c.service_id,\r\n c.id,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n );\r\n if (c.endpoint_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'ENDPOINT_CREDENTIAL',\r\n c.endpoint_id,\r\n c.id,\r\n c.evidence_artifact_id,\r\n c.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2j. vulnerabilities → nodes (kind=\"vulnerability\") + edge (SERVICE_VULNERABILITY) + optional (ENDPOINT_VULNERABILITY)\r\n if (tableExists('vulnerabilities')) {\r\n const vulns = db.prepare('SELECT * FROM vulnerabilities').all() as Array<\r\n Record<string, unknown>\r\n >;\r\n for (const v of vulns) {\r\n const props = JSON.stringify({\r\n vulnType: v.vuln_type,\r\n title: v.title,\r\n description: v.description ?? undefined,\r\n severity: v.severity,\r\n confidence: v.confidence,\r\n status: v.status ?? 'unverified',\r\n });\r\n insertNode.run(\r\n v.id,\r\n 'vulnerability',\r\n `vuln:${v.id}`,\r\n props,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n v.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'SERVICE_VULNERABILITY',\r\n v.service_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n if (v.endpoint_id) {\r\n insertEdge.run(\r\n randomUUID(),\r\n 'ENDPOINT_VULNERABILITY',\r\n v.endpoint_id,\r\n v.id,\r\n v.evidence_artifact_id,\r\n v.created_at,\r\n );\r\n }\r\n }\r\n }\r\n\r\n // 2k. cves → nodes (kind=\"cve\") + edge (VULNERABILITY_CVE)\r\n if (tableExists('cves')) {\r\n const cves = db.prepare('SELECT * FROM cves').all() as Array<Record<string, unknown>>;\r\n for (const c of cves) {\r\n const props = JSON.stringify({\r\n cveId: c.cve_id,\r\n description: c.description ?? undefined,\r\n cvssScore: c.cvss_score ?? undefined,\r\n cvssVector: c.cvss_vector ?? undefined,\r\n referenceUrl: c.reference_url ?? undefined,\r\n });\r\n insertNode.run(\r\n c.id,\r\n 'cve',\r\n `cve:${c.vulnerability_id}:${c.cve_id}`,\r\n props,\r\n null, // cves had no evidence_artifact_id\r\n c.created_at,\r\n c.created_at, // no updated_at\r\n );\r\n insertEdge.run(\r\n randomUUID(),\r\n 'VULNERABILITY_CVE',\r\n c.vulnerability_id,\r\n c.id,\r\n null,\r\n c.created_at,\r\n );\r\n }\r\n }\r\n\r\n // --------------------------------------------------\r\n // 3. 旧テーブル DROP\r\n // FK 依存順に削除(子テーブルから親テーブルへ)\r\n // --------------------------------------------------\r\n // FK を一時的に無効化して DROP\r\n db.pragma('foreign_keys = OFF');\r\n\r\n const tablesToDrop = [\r\n 'endpoint_inputs',\r\n 'observations',\r\n 'credentials',\r\n 'cves',\r\n 'vulnerabilities',\r\n 'http_endpoints',\r\n 'service_observations',\r\n 'inputs',\r\n 'services',\r\n 'vhosts',\r\n 'hosts',\r\n 'datalog_rules',\r\n ];\r\n\r\n for (const table of tablesToDrop) {\r\n if (tableExists(table)) {\r\n db.exec(`DROP TABLE ${table}`);\r\n }\r\n }\r\n\r\n // FK を再度有効化\r\n db.pragma('foreign_keys = ON');\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\r\n * Migration v5: file_mtime + 運用テーブル (Continuous STG Pentest)\r\n *\r\n * Part A: technique_docs\r\n * - Adds `file_mtime` TEXT column (NULL-able) for incremental indexing.\r\n * - Adds composite index on (source, file_path) for efficient mtime lookups.\r\n *\r\n * Part B: 7 新テーブル\r\n * - engagements, runs, action_queue, action_executions,\r\n * findings, finding_events, risk_snapshots\r\n *\r\n * Part C: scans/artifacts ALTER TABLE(リネージカラム追加)\r\n * - scans に engagement_id, run_id を追加\r\n * - artifacts に engagement_id, run_id, action_execution_id を追加\r\n *\r\n * Part D: 既存データの backfill\r\n * - 既存 scans がある場合、デフォルト engagement を作成\r\n * - scans.engagement_id と artifacts.engagement_id をデフォルト engagement で埋める\r\n *\r\n * See docs/v5-db-design.md for full design rationale.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport { randomUUID } from 'node:crypto';\r\nimport type { Migration } from './index.js';\r\n\r\nconst migration: Migration = {\r\n version: 5,\r\n description:\r\n 'Add file_mtime, operational tables (engagements, runs, action_queue, etc.), and lineage columns',\r\n up(db: Database.Database): void {\r\n // -------------------------------------------------------\r\n // Part A: technique_docs — file_mtime + 複合インデックス\r\n // -------------------------------------------------------\r\n db.exec(`\r\n ALTER TABLE technique_docs ADD COLUMN file_mtime TEXT;\r\n\r\n CREATE INDEX IF NOT EXISTS idx_technique_docs_source_filepath\r\n ON technique_docs(source, file_path);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part B: 運用テーブル — Continuous STG Pentest\r\n // -------------------------------------------------------\r\n\r\n // 1) engagements\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS engagements (\r\n id TEXT PRIMARY KEY,\r\n name TEXT NOT NULL,\r\n environment TEXT NOT NULL DEFAULT 'stg',\r\n scope_json TEXT NOT NULL DEFAULT '{}',\r\n policy_json TEXT NOT NULL DEFAULT '{}',\r\n schedule_cron TEXT,\r\n status TEXT NOT NULL DEFAULT 'active',\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_engagements_status ON engagements(status);\r\n `);\r\n\r\n // 2) runs\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS runs (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n trigger_kind TEXT NOT NULL,\r\n trigger_ref TEXT,\r\n status TEXT NOT NULL,\r\n started_at TEXT,\r\n finished_at TEXT,\r\n summary_json TEXT NOT NULL DEFAULT '{}',\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_runs_engagement_created ON runs(engagement_id, created_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);\r\n `);\r\n\r\n // 3) action_queue\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS action_queue (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n run_id TEXT,\r\n parent_action_id TEXT,\r\n kind TEXT NOT NULL,\r\n priority INTEGER NOT NULL DEFAULT 100,\r\n dedupe_key TEXT NOT NULL,\r\n params_json TEXT NOT NULL DEFAULT '{}',\r\n state TEXT NOT NULL,\r\n attempt_count INTEGER NOT NULL DEFAULT 0,\r\n max_attempts INTEGER NOT NULL DEFAULT 3,\r\n available_at TEXT NOT NULL,\r\n lease_owner TEXT,\r\n lease_expires_at TEXT,\r\n last_error TEXT,\r\n created_at TEXT NOT NULL,\r\n updated_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (parent_action_id) REFERENCES action_queue(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_action_queue_poll\r\n ON action_queue(state, available_at, priority, created_at);\r\n CREATE INDEX IF NOT EXISTS idx_action_queue_engagement_state\r\n ON action_queue(engagement_id, state, created_at DESC);\r\n CREATE UNIQUE INDEX IF NOT EXISTS uq_action_queue_active_dedupe\r\n ON action_queue(engagement_id, dedupe_key)\r\n WHERE state IN ('queued', 'running');\r\n `);\r\n\r\n // 4) action_executions\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS action_executions (\r\n id TEXT PRIMARY KEY,\r\n action_id TEXT NOT NULL,\r\n run_id TEXT,\r\n executor TEXT NOT NULL,\r\n command TEXT,\r\n input_json TEXT NOT NULL DEFAULT '{}',\r\n output_json TEXT NOT NULL DEFAULT '{}',\r\n stdout_artifact_id TEXT,\r\n stderr_artifact_id TEXT,\r\n exit_code INTEGER,\r\n error_type TEXT,\r\n error_message TEXT,\r\n started_at TEXT NOT NULL,\r\n finished_at TEXT,\r\n duration_ms INTEGER,\r\n FOREIGN KEY (action_id) REFERENCES action_queue(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (stdout_artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL,\r\n FOREIGN KEY (stderr_artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_action_exec_action_started ON action_executions(action_id, started_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_action_exec_run_started ON action_executions(run_id, started_at DESC);\r\n `);\r\n\r\n // 5) findings\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS findings (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n canonical_key TEXT NOT NULL,\r\n node_id TEXT,\r\n title TEXT NOT NULL,\r\n severity TEXT NOT NULL,\r\n confidence TEXT NOT NULL,\r\n state TEXT NOT NULL,\r\n state_reason TEXT,\r\n owner TEXT,\r\n ticket_ref TEXT,\r\n first_seen_run_id TEXT,\r\n last_seen_run_id TEXT,\r\n first_seen_at TEXT NOT NULL,\r\n last_seen_at TEXT NOT NULL,\r\n sla_due_at TEXT,\r\n attrs_json TEXT NOT NULL DEFAULT '{}',\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE SET NULL,\r\n FOREIGN KEY (first_seen_run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (last_seen_run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n UNIQUE (engagement_id, canonical_key)\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_findings_engagement_state_sev\r\n ON findings(engagement_id, state, severity, last_seen_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_findings_node ON findings(node_id);\r\n `);\r\n\r\n // 6) finding_events\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS finding_events (\r\n id TEXT PRIMARY KEY,\r\n finding_id TEXT NOT NULL,\r\n run_id TEXT,\r\n event_type TEXT NOT NULL,\r\n before_json TEXT NOT NULL DEFAULT '{}',\r\n after_json TEXT NOT NULL DEFAULT '{}',\r\n artifact_id TEXT,\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (finding_id) REFERENCES findings(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL,\r\n FOREIGN KEY (artifact_id) REFERENCES artifacts(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_finding_events_finding_created\r\n ON finding_events(finding_id, created_at DESC);\r\n `);\r\n\r\n // 7) risk_snapshots\r\n db.exec(`\r\n CREATE TABLE IF NOT EXISTS risk_snapshots (\r\n id TEXT PRIMARY KEY,\r\n engagement_id TEXT NOT NULL,\r\n run_id TEXT,\r\n score REAL NOT NULL,\r\n open_critical INTEGER NOT NULL DEFAULT 0,\r\n open_high INTEGER NOT NULL DEFAULT 0,\r\n open_medium INTEGER NOT NULL DEFAULT 0,\r\n open_low INTEGER NOT NULL DEFAULT 0,\r\n open_info INTEGER NOT NULL DEFAULT 0,\r\n open_total INTEGER NOT NULL DEFAULT 0,\r\n attack_path_count INTEGER NOT NULL DEFAULT 0,\r\n exposed_cred_count INTEGER NOT NULL DEFAULT 0,\r\n model_version TEXT,\r\n attrs_json TEXT NOT NULL DEFAULT '{}',\r\n created_at TEXT NOT NULL,\r\n FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,\r\n FOREIGN KEY (run_id) REFERENCES runs(id) ON DELETE SET NULL\r\n );\r\n CREATE INDEX IF NOT EXISTS idx_risk_snapshots_engagement_created\r\n ON risk_snapshots(engagement_id, created_at DESC);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part C: 既存テーブルへのリネージカラム追加\r\n // -------------------------------------------------------\r\n\r\n // scans に engagement_id, run_id を追加\r\n // NOTE: SQLite の ALTER TABLE は REFERENCES 句を無視するが、設計意図として記載\r\n db.exec(`\r\n ALTER TABLE scans ADD COLUMN engagement_id TEXT; -- REFERENCES engagements(id) ON DELETE SET NULL\r\n ALTER TABLE scans ADD COLUMN run_id TEXT; -- REFERENCES runs(id) ON DELETE SET NULL\r\n CREATE INDEX IF NOT EXISTS idx_scans_engagement_started ON scans(engagement_id, started_at DESC);\r\n `);\r\n\r\n // artifacts に engagement_id, run_id, action_execution_id を追加\r\n db.exec(`\r\n ALTER TABLE artifacts ADD COLUMN engagement_id TEXT; -- REFERENCES engagements(id) ON DELETE SET NULL\r\n ALTER TABLE artifacts ADD COLUMN run_id TEXT; -- REFERENCES runs(id) ON DELETE SET NULL\r\n ALTER TABLE artifacts ADD COLUMN action_execution_id TEXT; -- REFERENCES action_executions(id) ON DELETE SET NULL\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_engagement_captured ON artifacts(engagement_id, captured_at DESC);\r\n CREATE INDEX IF NOT EXISTS idx_artifacts_run_captured ON artifacts(run_id, captured_at DESC);\r\n `);\r\n\r\n // -------------------------------------------------------\r\n // Part D: 既存データの backfill\r\n // -------------------------------------------------------\r\n const scanCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM scans').get() as { cnt: number }\r\n ).cnt;\r\n\r\n if (scanCount > 0) {\r\n const now = new Date().toISOString();\r\n const defaultEngId = randomUUID();\r\n\r\n // デフォルト engagement を作成\r\n db.prepare(\r\n `INSERT INTO engagements (id, name, environment, scope_json, policy_json, status, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\r\n ).run(defaultEngId, 'default', 'stg', '{}', '{}', 'active', now, now);\r\n\r\n // scans.engagement_id をデフォルト engagement で埋める\r\n db.prepare('UPDATE scans SET engagement_id = ? WHERE engagement_id IS NULL').run(\r\n defaultEngId,\r\n );\r\n\r\n // artifacts.engagement_id をデフォルト engagement で埋める\r\n db.prepare('UPDATE artifacts SET engagement_id = ? WHERE engagement_id IS NULL').run(\r\n defaultEngId,\r\n );\r\n }\r\n },\r\n};\r\n\r\nexport default migration;\r\n","/**\r\n * sonobat — Database migration registry\r\n *\r\n * Manages versioned migrations using SQLite's PRAGMA user_version.\r\n * Each migration has a version number and an up() function.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport v0 from './v0.js';\r\nimport v1 from './v1.js';\r\nimport v2 from './v2.js';\r\nimport v3 from './v3.js';\r\nimport v4 from './v4.js';\r\nimport v5 from './v5.js';\r\n\r\nexport interface Migration {\r\n version: number;\r\n description: string;\r\n up(db: Database.Database): void;\r\n}\r\n\r\n/** All migrations in order. Must be sorted by version ascending. */\r\nconst migrations: Migration[] = [v0, v1, v2, v3, v4, v5];\r\n\r\n/** The latest schema version (after all migrations applied). */\r\nexport const LATEST_VERSION: number =\r\n migrations.length > 0 ? migrations[migrations.length - 1].version : 0;\r\n\r\n/**\r\n * Get the current schema version from the database.\r\n */\r\nexport function getSchemaVersion(db: Database.Database): number {\r\n const row = db.prepare('PRAGMA user_version').get() as {\r\n user_version: number;\r\n };\r\n return row.user_version;\r\n}\r\n\r\n/**\r\n * Set the schema version in the database.\r\n */\r\nexport function setSchemaVersion(db: Database.Database, version: number): void {\r\n db.pragma(`user_version = ${version}`);\r\n}\r\n\r\n/**\r\n * Run all pending migrations from currentVersion to LATEST_VERSION.\r\n * Each migration runs inside a transaction for safety.\r\n * Schema version is updated after each successful migration so that\r\n * a failure mid-sequence leaves the DB at the last completed version.\r\n */\r\nexport function runMigrations(db: Database.Database, currentVersion: number): void {\r\n for (const migration of migrations) {\r\n if (migration.version > currentVersion) {\r\n const runMigration = db.transaction(() => {\r\n migration.up(db);\r\n });\r\n runMigration();\r\n setSchemaVersion(db, migration.version);\r\n }\r\n }\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport { getSchemaVersion, runMigrations, LATEST_VERSION } from './migrations/index.js';\r\n\r\n/**\r\n * Migrate the database to the latest schema version.\r\n *\r\n * - New database (user_version = 0, no tables): runs ALL migrations from v0.\r\n * - Existing database (user_version = 0, has tables): runs incremental migrations from v1.\r\n * - Partially migrated: runs remaining migrations.\r\n * - Already up-to-date (user_version = LATEST_VERSION): no-op.\r\n */\r\nexport function migrateDatabase(db: Database.Database): void {\r\n db.pragma('foreign_keys = ON');\r\n\r\n const currentVersion = getSchemaVersion(db);\r\n\r\n if (currentVersion >= LATEST_VERSION) {\r\n return;\r\n }\r\n\r\n if (currentVersion === 0) {\r\n // Check if this is a truly new DB or an existing v0 DB\r\n const tableCount = (\r\n db\r\n .prepare(\r\n \"SELECT COUNT(*) AS cnt FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\",\r\n )\r\n .get() as { cnt: number }\r\n ).cnt;\r\n\r\n if (tableCount === 0) {\r\n // Brand new database: run ALL migrations including v0 (base schema)\r\n runMigrations(db, -1);\r\n return;\r\n }\r\n\r\n // Existing v0 database: run incremental migrations from v1\r\n runMigrations(db, 0);\r\n return;\r\n }\r\n\r\n // Partially migrated: run remaining migrations\r\n runMigrations(db, currentVersion);\r\n}\r\n","/**\r\n * sonobat — MCP Server\r\n *\r\n * Creates and configures the MCP server with all tools and resources.\r\n */\r\n\r\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { registerQueryTool } from './tools/query.js';\r\nimport { registerMutateTool } from './tools/mutate.js';\r\nimport { registerIngestTool } from './tools/ingest.js';\r\nimport { registerProposeTool } from './tools/propose.js';\r\nimport { registerKbTools } from './tools/kb.js';\r\nimport { registerResources } from './resources.js';\r\n\r\n/**\r\n * Create a fully configured MCP server with all sonobat tools and resources.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @param version - Package version string (read from package.json by caller)\r\n * @returns Configured McpServer instance\r\n */\r\nexport function createMcpServer(db: Database.Database, version?: string): McpServer {\r\n const server = new McpServer({\r\n name: 'sonobat',\r\n version: version ?? '0.0.0',\r\n });\r\n\r\n // Register tools (6 tools total)\r\n registerQueryTool(server, db);\r\n registerMutateTool(server, db);\r\n registerIngestTool(server, db);\r\n registerProposeTool(server, db);\r\n registerKbTools(server, db); // search_kb + index_kb\r\n\r\n // Register resources\r\n registerResources(server, db);\r\n\r\n return server;\r\n}\r\n","/**\r\n * sonobat — MCP Query Tool (unified)\r\n *\r\n * Single 'query' tool with an 'action' parameter that replaces\r\n * all previous read-only tools (list_hosts, get_host, list_services, etc.).\r\n *\r\n * Actions: list_nodes, get_node, traverse, summary, attack_paths\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { z } from 'zod';\r\nimport { NodeRepository } from '../../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../../db/repository/edge-repository.js';\r\nimport { GraphQueryRepository } from '../../db/repository/graph-query-repository.js';\r\nimport { NODE_KINDS, EDGE_KINDS } from '../../types/graph.js';\r\nimport type { NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\nexport function registerQueryTool(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n const graphQueryRepo = new GraphQueryRepository(db);\r\n\r\n server.tool(\r\n 'query',\r\n 'Query the AttackDataGraph. Actions: list_nodes, get_node, traverse, summary, attack_paths',\r\n {\r\n action: z.enum(['list_nodes', 'get_node', 'traverse', 'summary', 'attack_paths']),\r\n // Parameters for list_nodes\r\n kind: z.string().optional().describe('Node kind filter (host, service, endpoint, etc.)'),\r\n // Parameters for get_node\r\n id: z.string().optional().describe('Node ID'),\r\n // Parameters for traverse\r\n startId: z.string().optional().describe('Start node ID for traversal'),\r\n depth: z.number().optional().describe('Max traversal depth'),\r\n edgeKinds: z.array(z.string()).optional().describe('Edge kinds to follow'),\r\n // Parameters for attack_paths\r\n pattern: z.string().optional().describe('Preset pattern name'),\r\n // Common filters\r\n filtersJson: z\r\n .string()\r\n .optional()\r\n .describe('Filters as JSON object for list_nodes (JSON_EXTRACT on props)'),\r\n },\r\n async ({ action, kind, id, startId, depth, edgeKinds, pattern, filtersJson }) => {\r\n switch (action) {\r\n case 'list_nodes': {\r\n if (!kind) {\r\n return {\r\n content: [{ type: 'text', text: 'kind parameter required for list_nodes' }],\r\n isError: true,\r\n };\r\n }\r\n // Validate kind\r\n if (!NODE_KINDS.includes(kind as NodeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid kind: ${kind}. Valid: ${NODE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n const filters = filtersJson\r\n ? (JSON.parse(filtersJson) as Record<string, unknown>)\r\n : undefined;\r\n const nodes = nodeRepo.findByKind(kind as NodeKind, filters);\r\n const result = nodes.map((n) => ({ ...n, props: JSON.parse(n.propsJson) }));\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n case 'get_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter required for get_node' }],\r\n isError: true,\r\n };\r\n }\r\n const node = nodeRepo.findById(id);\r\n if (!node) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n // Get adjacent edges and nodes\r\n const outEdges = edgeRepo.findBySource(node.id);\r\n const inEdges = edgeRepo.findByTarget(node.id);\r\n const adjacentNodeIds = new Set([\r\n ...outEdges.map((e) => e.targetId),\r\n ...inEdges.map((e) => e.sourceId),\r\n ]);\r\n const adjacentNodes = [...adjacentNodeIds]\r\n .map((nid) => nodeRepo.findById(nid))\r\n .filter(Boolean);\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n outEdges,\r\n inEdges,\r\n adjacentNodes: adjacentNodes.map((n) => ({\r\n ...n!,\r\n props: JSON.parse(n!.propsJson),\r\n })),\r\n };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n case 'traverse': {\r\n if (!startId) {\r\n return {\r\n content: [{ type: 'text', text: 'startId parameter required for traverse' }],\r\n isError: true,\r\n };\r\n }\r\n const validEdgeKinds = edgeKinds?.filter((ek) => EDGE_KINDS.includes(ek as EdgeKind)) as\r\n | EdgeKind[]\r\n | undefined;\r\n const results = graphQueryRepo.traverse(startId, depth, validEdgeKinds);\r\n const enriched = results.map((r) => ({\r\n ...r,\r\n node: { ...r.node, props: JSON.parse(r.node.propsJson) },\r\n }));\r\n return { content: [{ type: 'text', text: JSON.stringify(enriched, null, 2) }] };\r\n }\r\n case 'summary': {\r\n // Count nodes by kind and edges by kind\r\n const nodeCounts: Record<string, number> = {};\r\n for (const k of NODE_KINDS) {\r\n nodeCounts[k] = nodeRepo.findByKind(k).length;\r\n }\r\n const edgeCounts: Record<string, number> = {};\r\n for (const ek of EDGE_KINDS) {\r\n edgeCounts[ek] = edgeRepo.findByKind(ek).length;\r\n }\r\n // Also count artifacts\r\n const artifactCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM artifacts').get() as { cnt: number }\r\n ).cnt;\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: JSON.stringify(\r\n { nodes: nodeCounts, edges: edgeCounts, artifacts: artifactCount },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n }\r\n case 'attack_paths': {\r\n if (!pattern) {\r\n return {\r\n content: [{ type: 'text', text: 'pattern parameter required for attack_paths' }],\r\n isError: true,\r\n };\r\n }\r\n try {\r\n const results = graphQueryRepo.runPreset(pattern);\r\n return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n return {\r\n content: [{ type: 'text', text: `attack_paths error: ${message}` }],\r\n isError: true,\r\n };\r\n }\r\n }\r\n }\r\n },\r\n );\r\n}\r\n","/**\r\n * sonobat — NodeRepository\r\n *\r\n * Graph-native スキーマの nodes テーブルに対する CRUD 操作を提供する。\r\n * snake_case (DB) ↔ camelCase (TypeScript) の変換を内部で行う。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\nimport type { GraphNode, NodeKind } from '../../types/graph.js';\r\nimport { validateProps, buildNaturalKey } from '../../types/graph.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// DB row 型\r\n// ---------------------------------------------------------------------------\r\n\r\n/** better-sqlite3 から返る nodes テーブルの行形状 */\r\ninterface NodeRow {\r\n id: string;\r\n kind: string;\r\n natural_key: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n updated_at: string;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Row → GraphNode 変換\r\n// ---------------------------------------------------------------------------\r\n\r\n/** snake_case DB row を camelCase GraphNode にマッピング */\r\nfunction rowToGraphNode(row: NodeRow): GraphNode {\r\n return {\r\n id: row.id,\r\n kind: row.kind as NodeKind,\r\n naturalKey: row.natural_key,\r\n propsJson: row.props_json,\r\n ...(row.evidence_artifact_id !== null ? { evidenceArtifactId: row.evidence_artifact_id } : {}),\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// NodeRepository\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * nodes テーブルの CRUD リポジトリ。\r\n *\r\n * - props は Zod バリデーション後に JSON 文字列としてストア\r\n * - natural_key は buildNaturalKey() で自動生成\r\n * - ID は crypto.randomUUID() で生成\r\n */\r\nexport class NodeRepository {\r\n private readonly db: Database.Database;\r\n\r\n private readonly insertStmt: Database.Statement;\r\n private readonly selectByIdStmt: Database.Statement;\r\n private readonly selectByKindStmt: Database.Statement;\r\n private readonly selectByNaturalKeyStmt: Database.Statement;\r\n private readonly updatePropsStmt: Database.Statement;\r\n private readonly deleteStmt: Database.Statement;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n\r\n this.insertStmt = this.db.prepare(\r\n `INSERT INTO nodes (id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\r\n );\r\n\r\n this.selectByIdStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE id = ?`,\r\n );\r\n\r\n this.selectByKindStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE kind = ?`,\r\n );\r\n\r\n this.selectByNaturalKeyStmt = this.db.prepare(\r\n `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes WHERE natural_key = ?`,\r\n );\r\n\r\n this.updatePropsStmt = this.db.prepare(\r\n `UPDATE nodes SET props_json = ?, updated_at = ? WHERE id = ?`,\r\n );\r\n\r\n this.deleteStmt = this.db.prepare(`DELETE FROM nodes WHERE id = ?`);\r\n }\r\n\r\n /**\r\n * ノードを新規作成して返す。\r\n *\r\n * @param kind ノード種別\r\n * @param props ノードの props(Zod バリデーション対象)\r\n * @param evidenceArtifactId 証拠 artifact の ID(任意)\r\n * @param parentId 親ノード ID(service, endpoint 等で必要)\r\n * @throws props バリデーションエラー、または natural_key 重複時\r\n */\r\n create(\r\n kind: NodeKind,\r\n props: Record<string, unknown>,\r\n evidenceArtifactId?: string,\r\n parentId?: string,\r\n ): GraphNode {\r\n // props バリデーション\r\n const validation = validateProps(kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${kind}\": ${validation.error}`);\r\n }\r\n\r\n const id = crypto.randomUUID();\r\n const naturalKey = buildNaturalKey(kind, validation.data, parentId);\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n\r\n this.insertStmt.run(\r\n id,\r\n kind,\r\n naturalKey,\r\n propsJson,\r\n evidenceArtifactId ?? null,\r\n timestamp,\r\n timestamp,\r\n );\r\n\r\n return {\r\n id,\r\n kind,\r\n naturalKey,\r\n propsJson,\r\n ...(evidenceArtifactId !== undefined ? { evidenceArtifactId } : {}),\r\n createdAt: timestamp,\r\n updatedAt: timestamp,\r\n };\r\n }\r\n\r\n /**\r\n * Upsert: natural_key が存在すれば更新、なければ新規作成。\r\n *\r\n * UUID ベースの natural key (observation, credential, vulnerability, svc_observation) は\r\n * 毎回異なるキーが生成されるため、常に新規作成となる。\r\n *\r\n * @returns { node, created } — created は新規作成時 true、既存更新時 false\r\n */\r\n upsert(\r\n kind: NodeKind,\r\n props: Record<string, unknown>,\r\n evidenceArtifactId?: string,\r\n parentId?: string,\r\n ): { node: GraphNode; created: boolean } {\r\n // props バリデーション\r\n const validation = validateProps(kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${kind}\": ${validation.error}`);\r\n }\r\n\r\n const naturalKey = buildNaturalKey(kind, validation.data, parentId);\r\n\r\n // 既存ノードを natural_key で検索\r\n const existing = this.findByNaturalKey(naturalKey);\r\n\r\n if (existing !== undefined) {\r\n // 既存ノードの props を更新\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n this.updatePropsStmt.run(propsJson, timestamp, existing.id);\r\n\r\n const updated = this.findById(existing.id)!;\r\n return { node: updated, created: false };\r\n }\r\n\r\n // 新規作成\r\n const node = this.create(kind, props, evidenceArtifactId, parentId);\r\n return { node, created: true };\r\n }\r\n\r\n /**\r\n * ID でノードを取得する。存在しなければ undefined。\r\n */\r\n findById(id: string): GraphNode | undefined {\r\n const row = this.selectByIdStmt.get(id) as NodeRow | undefined;\r\n if (row === undefined) {\r\n return undefined;\r\n }\r\n return rowToGraphNode(row);\r\n }\r\n\r\n /**\r\n * kind でノード一覧を取得する。\r\n *\r\n * filters を指定すると、props_json の中身に対して JSON_EXTRACT で絞り込む。\r\n * 例: findByKind('host', { authority: '192.168.1.1' })\r\n * → WHERE kind = 'host' AND JSON_EXTRACT(props_json, '$.authority') = '192.168.1.1'\r\n */\r\n findByKind(kind: NodeKind, filters?: Record<string, unknown>): GraphNode[] {\r\n if (filters === undefined || Object.keys(filters).length === 0) {\r\n const rows = this.selectByKindStmt.all(kind) as NodeRow[];\r\n return rows.map(rowToGraphNode);\r\n }\r\n\r\n // 動的フィルタ構築(プリペアドステートメントで安全に)\r\n const filterKeys = Object.keys(filters);\r\n const whereClauses = filterKeys.map((_key) => `JSON_EXTRACT(props_json, '$.' || ?) = ?`);\r\n const sql = `SELECT id, kind, natural_key, props_json, evidence_artifact_id, created_at, updated_at\r\n FROM nodes\r\n WHERE kind = ? AND ${whereClauses.join(' AND ')}`;\r\n\r\n const params: unknown[] = [kind];\r\n for (const key of filterKeys) {\r\n params.push(key, filters[key]);\r\n }\r\n\r\n const rows = this.db.prepare(sql).all(...params) as NodeRow[];\r\n return rows.map(rowToGraphNode);\r\n }\r\n\r\n /**\r\n * natural_key でノードを取得する。存在しなければ undefined。\r\n */\r\n findByNaturalKey(naturalKey: string): GraphNode | undefined {\r\n const row = this.selectByNaturalKeyStmt.get(naturalKey) as NodeRow | undefined;\r\n if (row === undefined) {\r\n return undefined;\r\n }\r\n return rowToGraphNode(row);\r\n }\r\n\r\n /**\r\n * ノードの props を更新する。updated_at も自動更新される。\r\n *\r\n * @returns 更新後の GraphNode。id が存在しなければ undefined。\r\n * @throws props バリデーションエラー時\r\n */\r\n updateProps(id: string, props: Record<string, unknown>): GraphNode | undefined {\r\n // 既存ノードを取得して kind を確認\r\n const existing = this.findById(id);\r\n if (existing === undefined) {\r\n return undefined;\r\n }\r\n\r\n // props バリデーション\r\n const validation = validateProps(existing.kind, props);\r\n if (!validation.ok) {\r\n throw new Error(`Props validation failed for kind=\"${existing.kind}\": ${validation.error}`);\r\n }\r\n\r\n const propsJson = JSON.stringify(validation.data);\r\n const timestamp = new Date().toISOString();\r\n\r\n this.updatePropsStmt.run(propsJson, timestamp, id);\r\n\r\n return this.findById(id);\r\n }\r\n\r\n /**\r\n * ノードを削除する。\r\n *\r\n * @returns 削除成功時 true、id が存在しない場合 false。\r\n */\r\n delete(id: string): boolean {\r\n const result = this.deleteStmt.run(id);\r\n return result.changes > 0;\r\n }\r\n}\r\n","/**\r\n * sonobat — Graph type system\r\n *\r\n * グラフネイティブスキーマの型定義。\r\n * NodeKind/EdgeKind 列挙、Zod props スキーマ、\r\n * GraphNode/GraphEdge インターフェース、natural key builder。\r\n */\r\n\r\nimport { z } from 'zod';\r\nimport { randomUUID } from 'node:crypto';\r\n\r\n// ============================================================\r\n// NodeKind / EdgeKind 列挙\r\n// ============================================================\r\n\r\nexport const NODE_KINDS = [\r\n 'host',\r\n 'vhost',\r\n 'service',\r\n 'endpoint',\r\n 'input',\r\n 'observation',\r\n 'credential',\r\n 'vulnerability',\r\n 'cve',\r\n 'svc_observation',\r\n] as const;\r\nexport type NodeKind = (typeof NODE_KINDS)[number];\r\n\r\nexport const EDGE_KINDS = [\r\n 'HOST_SERVICE',\r\n 'HOST_VHOST',\r\n 'SERVICE_ENDPOINT',\r\n 'SERVICE_INPUT',\r\n 'SERVICE_CREDENTIAL',\r\n 'SERVICE_VULNERABILITY',\r\n 'SERVICE_OBSERVATION',\r\n 'ENDPOINT_INPUT',\r\n 'ENDPOINT_VULNERABILITY',\r\n 'ENDPOINT_CREDENTIAL',\r\n 'INPUT_OBSERVATION',\r\n 'VULNERABILITY_CVE',\r\n 'VHOST_ENDPOINT',\r\n] as const;\r\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\r\n\r\n// ============================================================\r\n// Zod Props スキーマ\r\n// ============================================================\r\n\r\nexport const HostPropsSchema = z.object({\r\n authorityKind: z.enum(['IP', 'DOMAIN']),\r\n authority: z.string().min(1),\r\n resolvedIpsJson: z.string().default('[]'),\r\n});\r\nexport type HostProps = z.infer<typeof HostPropsSchema>;\r\n\r\nexport const VhostPropsSchema = z.object({\r\n hostname: z.string().min(1),\r\n source: z.string().optional(),\r\n});\r\nexport type VhostProps = z.infer<typeof VhostPropsSchema>;\r\n\r\nexport const ServicePropsSchema = z.object({\r\n transport: z.string().min(1),\r\n port: z.number().int().nonnegative(),\r\n appProto: z.string().min(1),\r\n protoConfidence: z.string().min(1),\r\n banner: z.string().optional(),\r\n product: z.string().optional(),\r\n version: z.string().optional(),\r\n state: z.string().min(1),\r\n});\r\nexport type ServiceProps = z.infer<typeof ServicePropsSchema>;\r\n\r\nexport const EndpointPropsSchema = z.object({\r\n baseUri: z.string().min(1),\r\n method: z.string().min(1),\r\n path: z.string().min(1),\r\n statusCode: z.number().int().optional(),\r\n contentLength: z.number().int().optional(),\r\n words: z.number().int().optional(),\r\n lines: z.number().int().optional(),\r\n});\r\nexport type EndpointProps = z.infer<typeof EndpointPropsSchema>;\r\n\r\nexport const InputPropsSchema = z.object({\r\n location: z.string().min(1),\r\n name: z.string().min(1),\r\n typeHint: z.string().optional(),\r\n});\r\nexport type InputProps = z.infer<typeof InputPropsSchema>;\r\n\r\nexport const ObservationPropsSchema = z.object({\r\n rawValue: z.string(),\r\n normValue: z.string(),\r\n bodyPath: z.string().optional(),\r\n source: z.string().min(1),\r\n confidence: z.string().min(1),\r\n observedAt: z.string().min(1),\r\n});\r\nexport type ObservationProps = z.infer<typeof ObservationPropsSchema>;\r\n\r\nexport const CredentialPropsSchema = z.object({\r\n username: z.string(),\r\n secret: z.string(),\r\n secretType: z.string().min(1),\r\n source: z.string().min(1),\r\n confidence: z.string().min(1),\r\n});\r\nexport type CredentialProps = z.infer<typeof CredentialPropsSchema>;\r\n\r\nexport const VulnerabilityPropsSchema = z.object({\r\n vulnType: z.string().min(1),\r\n title: z.string().min(1),\r\n description: z.string().optional(),\r\n severity: z.string().min(1),\r\n confidence: z.string().min(1),\r\n status: z.string().min(1).default('unverified'),\r\n});\r\nexport type VulnerabilityProps = z.infer<typeof VulnerabilityPropsSchema>;\r\n\r\nexport const CvePropsSchema = z.object({\r\n cveId: z.string().min(1),\r\n description: z.string().optional(),\r\n cvssScore: z.number().optional(),\r\n cvssVector: z.string().optional(),\r\n referenceUrl: z.string().optional(),\r\n});\r\nexport type CveProps = z.infer<typeof CvePropsSchema>;\r\n\r\nexport const SvcObservationPropsSchema = z.object({\r\n key: z.string().min(1),\r\n value: z.string(),\r\n confidence: z.string().min(1),\r\n});\r\nexport type SvcObservationProps = z.infer<typeof SvcObservationPropsSchema>;\r\n\r\n/** NodeKind → Zod スキーマのマッピング */\r\nconst PROPS_SCHEMA_MAP: Record<NodeKind, z.ZodTypeAny> = {\r\n host: HostPropsSchema,\r\n vhost: VhostPropsSchema,\r\n service: ServicePropsSchema,\r\n endpoint: EndpointPropsSchema,\r\n input: InputPropsSchema,\r\n observation: ObservationPropsSchema,\r\n credential: CredentialPropsSchema,\r\n vulnerability: VulnerabilityPropsSchema,\r\n cve: CvePropsSchema,\r\n svc_observation: SvcObservationPropsSchema,\r\n};\r\n\r\n// ============================================================\r\n// GraphNode / GraphEdge インターフェース\r\n// ============================================================\r\n\r\nexport interface GraphNode {\r\n id: string;\r\n kind: NodeKind;\r\n naturalKey: string;\r\n propsJson: string;\r\n evidenceArtifactId?: string;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\nexport interface GraphEdge {\r\n id: string;\r\n kind: EdgeKind;\r\n sourceId: string;\r\n targetId: string;\r\n propsJson: string;\r\n evidenceArtifactId?: string;\r\n createdAt: string;\r\n}\r\n\r\n// ============================================================\r\n// validateProps\r\n// ============================================================\r\n\r\nexport type ValidateResult = { ok: true; data: unknown } | { ok: false; error: string };\r\n\r\n/**\r\n * 指定された NodeKind に対する props のバリデーション。\r\n */\r\nexport function validateProps(kind: NodeKind, props: unknown): ValidateResult {\r\n const schema = PROPS_SCHEMA_MAP[kind];\r\n const result = schema.safeParse(props);\r\n if (result.success) {\r\n return { ok: true, data: result.data };\r\n }\r\n return { ok: false, error: result.error.message };\r\n}\r\n\r\n// ============================================================\r\n// buildNaturalKey\r\n// ============================================================\r\n\r\n/**\r\n * ノード種別と props から自然キーを構築する。\r\n *\r\n * 決定的なキーが生成できるノード (host, vhost, service, endpoint, input, cve) は\r\n * 常に同じ入力に対して同じキーを返す。\r\n *\r\n * 一意性が保証できないノード (observation, credential, vulnerability, svc_observation)\r\n * は UUID ベースのキーを生成する。\r\n *\r\n * @param kind ノード種別\r\n * @param props ノードの props(部分的でも可)\r\n * @param parentId 親ノードの ID(vhost, service, endpoint, input, cve で必須)\r\n */\r\nexport function buildNaturalKey(kind: NodeKind, props: unknown, parentId?: string): string {\r\n const p = props as Record<string, unknown>;\r\n\r\n switch (kind) {\r\n case 'host':\r\n return `host:${p.authority}`;\r\n\r\n case 'vhost':\r\n return `vhost:${parentId}:${p.hostname}`;\r\n\r\n case 'service':\r\n return `svc:${parentId}:${p.transport}:${p.port}`;\r\n\r\n case 'endpoint':\r\n return `ep:${parentId}:${p.method}:${p.path}`;\r\n\r\n case 'input':\r\n return `in:${parentId}:${p.location}:${p.name}`;\r\n\r\n case 'cve':\r\n return `cve:${parentId}:${p.cveId}`;\r\n\r\n case 'observation':\r\n return `obs:${randomUUID()}`;\r\n\r\n case 'credential':\r\n return `cred:${randomUUID()}`;\r\n\r\n case 'vulnerability':\r\n return `vuln:${randomUUID()}`;\r\n\r\n case 'svc_observation':\r\n return `svcobs:${randomUUID()}`;\r\n }\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\nimport type { EdgeKind, GraphEdge } from '../../types/graph.js';\r\n\r\n/** Row shape returned by better-sqlite3 for the edges table. */\r\ninterface EdgeRow {\r\n id: string;\r\n kind: string;\r\n source_id: string;\r\n target_id: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n}\r\n\r\n/** Maps a snake_case DB row to a camelCase GraphEdge entity. */\r\nfunction rowToEdge(row: EdgeRow): GraphEdge {\r\n return {\r\n id: row.id,\r\n kind: row.kind as EdgeKind,\r\n sourceId: row.source_id,\r\n targetId: row.target_id,\r\n propsJson: row.props_json,\r\n ...(row.evidence_artifact_id !== null ? { evidenceArtifactId: row.evidence_artifact_id } : {}),\r\n createdAt: row.created_at,\r\n };\r\n}\r\n\r\n/**\r\n * Repository for the `edges` table.\r\n *\r\n * Provides CRUD operations with camelCase <-> snake_case mapping\r\n * between the TypeScript entity layer and the SQLite storage layer.\r\n */\r\nexport class EdgeRepository {\r\n private readonly db: Database.Database;\r\n\r\n private readonly insertStmt: Database.Statement;\r\n private readonly selectByCompositeKeyStmt: Database.Statement;\r\n private readonly selectBySourceStmt: Database.Statement;\r\n private readonly selectBySourceAndKindStmt: Database.Statement;\r\n private readonly selectByTargetStmt: Database.Statement;\r\n private readonly selectByTargetAndKindStmt: Database.Statement;\r\n private readonly selectByKindStmt: Database.Statement;\r\n private readonly deleteStmt: Database.Statement;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n\r\n this.insertStmt = this.db.prepare(\r\n 'INSERT INTO edges (id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)',\r\n );\r\n\r\n this.selectByCompositeKeyStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE kind = ? AND source_id = ? AND target_id = ?',\r\n );\r\n\r\n this.selectBySourceStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE source_id = ?',\r\n );\r\n\r\n this.selectBySourceAndKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE source_id = ? AND kind = ?',\r\n );\r\n\r\n this.selectByTargetStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE target_id = ?',\r\n );\r\n\r\n this.selectByTargetAndKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE target_id = ? AND kind = ?',\r\n );\r\n\r\n this.selectByKindStmt = this.db.prepare(\r\n 'SELECT id, kind, source_id, target_id, props_json, evidence_artifact_id, created_at FROM edges WHERE kind = ?',\r\n );\r\n\r\n this.deleteStmt = this.db.prepare('DELETE FROM edges WHERE id = ?');\r\n }\r\n\r\n /**\r\n * Create a new edge linking two nodes.\r\n *\r\n * Throws if the (kind, source_id, target_id) combination already exists.\r\n */\r\n create(\r\n kind: EdgeKind,\r\n sourceId: string,\r\n targetId: string,\r\n evidenceArtifactId?: string,\r\n propsJson?: string,\r\n ): GraphEdge {\r\n const id = crypto.randomUUID();\r\n const createdAt = new Date().toISOString();\r\n const props = propsJson ?? '{}';\r\n\r\n this.insertStmt.run(id, kind, sourceId, targetId, props, evidenceArtifactId ?? null, createdAt);\r\n\r\n return {\r\n id,\r\n kind,\r\n sourceId,\r\n targetId,\r\n propsJson: props,\r\n ...(evidenceArtifactId !== undefined ? { evidenceArtifactId } : {}),\r\n createdAt,\r\n };\r\n }\r\n\r\n /**\r\n * Upsert an edge by (kind, source_id, target_id).\r\n *\r\n * If the combination already exists, returns the existing edge with `created: false`.\r\n * Otherwise creates a new edge and returns it with `created: true`.\r\n */\r\n upsert(\r\n kind: EdgeKind,\r\n sourceId: string,\r\n targetId: string,\r\n evidenceArtifactId?: string,\r\n propsJson?: string,\r\n ): { edge: GraphEdge; created: boolean } {\r\n const existing = this.selectByCompositeKeyStmt.get(kind, sourceId, targetId) as\r\n | EdgeRow\r\n | undefined;\r\n\r\n if (existing !== undefined) {\r\n return { edge: rowToEdge(existing), created: false };\r\n }\r\n\r\n const edge = this.create(kind, sourceId, targetId, evidenceArtifactId, propsJson);\r\n return { edge, created: true };\r\n }\r\n\r\n /**\r\n * Find all edges originating from a source node.\r\n *\r\n * Optionally filter by edge kind.\r\n */\r\n findBySource(sourceId: string, edgeKind?: EdgeKind): GraphEdge[] {\r\n if (edgeKind !== undefined) {\r\n const rows = this.selectBySourceAndKindStmt.all(sourceId, edgeKind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n const rows = this.selectBySourceStmt.all(sourceId) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Find all edges targeting a node.\r\n *\r\n * Optionally filter by edge kind.\r\n */\r\n findByTarget(targetId: string, edgeKind?: EdgeKind): GraphEdge[] {\r\n if (edgeKind !== undefined) {\r\n const rows = this.selectByTargetAndKindStmt.all(targetId, edgeKind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n const rows = this.selectByTargetStmt.all(targetId) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Find all edges of a specific kind.\r\n */\r\n findByKind(kind: EdgeKind): GraphEdge[] {\r\n const rows = this.selectByKindStmt.all(kind) as EdgeRow[];\r\n return rows.map(rowToEdge);\r\n }\r\n\r\n /**\r\n * Delete an edge by ID.\r\n *\r\n * Returns true if the edge was found and deleted, false otherwise.\r\n */\r\n delete(id: string): boolean {\r\n const result = this.deleteStmt.run(id);\r\n return result.changes > 0;\r\n }\r\n}\r\n","/**\r\n * sonobat — GraphQueryRepository\r\n *\r\n * グラフ走査・パス探索・プリセットクエリを提供するリポジトリ。\r\n * WITH RECURSIVE CTE を活用した SQLite ネイティブなグラフ操作。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { GraphNode, GraphEdge, NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Row → Entity マッピング\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface NodeRow {\r\n id: string;\r\n kind: string;\r\n natural_key: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n updated_at: string;\r\n}\r\n\r\ninterface EdgeRow {\r\n id: string;\r\n kind: string;\r\n source_id: string;\r\n target_id: string;\r\n props_json: string;\r\n evidence_artifact_id: string | null;\r\n created_at: string;\r\n}\r\n\r\nfunction rowToNode(row: NodeRow): GraphNode {\r\n return {\r\n id: row.id,\r\n kind: row.kind as NodeKind,\r\n naturalKey: row.natural_key,\r\n propsJson: row.props_json,\r\n evidenceArtifactId: row.evidence_artifact_id ?? undefined,\r\n createdAt: row.created_at,\r\n updatedAt: row.updated_at,\r\n };\r\n}\r\n\r\nfunction rowToEdge(row: EdgeRow): GraphEdge {\r\n return {\r\n id: row.id,\r\n kind: row.kind as EdgeKind,\r\n sourceId: row.source_id,\r\n targetId: row.target_id,\r\n propsJson: row.props_json,\r\n evidenceArtifactId: row.evidence_artifact_id ?? undefined,\r\n createdAt: row.created_at,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 公開型\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TraversalResult {\r\n node: GraphNode;\r\n depth: number;\r\n path: string[]; // node IDs from start to this node\r\n}\r\n\r\nexport interface PathResult {\r\n nodes: GraphNode[];\r\n edges: GraphEdge[];\r\n length: number;\r\n}\r\n\r\nexport type PresetResult = Record<string, unknown>[];\r\n\r\n// ---------------------------------------------------------------------------\r\n// GraphQueryRepository\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * グラフ走査・パス探索・プリセットクエリを提供するリポジトリ。\r\n *\r\n * すべてのクエリはプリペアドステートメントを使用し、SQL インジェクションを防止。\r\n */\r\nexport class GraphQueryRepository {\r\n private readonly db: Database.Database;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n }\r\n\r\n /**\r\n * 開始ノードから有向エッジを辿り、到達可能なノードを幅優先で返す。\r\n *\r\n * @param startId 開始ノード ID\r\n * @param maxDepth 最大探索深度(デフォルト: 10)\r\n * @param edgeKinds 辿るエッジ種別のフィルタ(省略時は全種別)\r\n * @returns 到達可能なノードのリスト(開始ノード自身は含まない)\r\n */\r\n traverse(startId: string, maxDepth?: number, edgeKinds?: EdgeKind[]): TraversalResult[] {\r\n const depth = maxDepth ?? 10;\r\n\r\n // 開始ノードの存在確認\r\n const startNode = this.db\r\n .prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?')\r\n .get(startId);\r\n if (!startNode) {\r\n return [];\r\n }\r\n\r\n // edgeKinds フィルタ用の条件構築\r\n let edgeFilter = '';\r\n const params: unknown[] = [startId, startId, depth];\r\n\r\n if (edgeKinds && edgeKinds.length > 0) {\r\n const placeholders = edgeKinds.map(() => '?').join(', ');\r\n edgeFilter = `AND e.kind IN (${placeholders})`;\r\n params.push(...edgeKinds);\r\n }\r\n\r\n const sql = `\r\n WITH RECURSIVE graph_walk(node_id, depth, path) AS (\r\n SELECT ?, 0, ?\r\n UNION ALL\r\n SELECT e.target_id, gw.depth + 1, gw.path || ',' || e.target_id\r\n FROM graph_walk gw\r\n JOIN edges e ON e.source_id = gw.node_id\r\n WHERE gw.depth < ?\r\n ${edgeFilter}\r\n AND instr(',' || gw.path || ',', ',' || e.target_id || ',') = 0\r\n )\r\n SELECT DISTINCT\r\n n.id, n.kind, n.natural_key, n.props_json,\r\n n.evidence_artifact_id, n.created_at, n.updated_at,\r\n gw.depth AS walk_depth,\r\n gw.path AS walk_path\r\n FROM graph_walk gw\r\n JOIN nodes n ON n.id = gw.node_id\r\n WHERE gw.depth > 0\r\n ORDER BY gw.depth ASC\r\n `;\r\n\r\n const stmt = this.db.prepare(sql);\r\n const rows = stmt.all(...params) as Array<NodeRow & { walk_depth: number; walk_path: string }>;\r\n\r\n // 同一ノードが複数パスで到達可能な場合、最短パスのみ保持\r\n const seen = new Map<string, TraversalResult>();\r\n\r\n for (const row of rows) {\r\n if (!seen.has(row.id) || seen.get(row.id)!.depth > row.walk_depth) {\r\n seen.set(row.id, {\r\n node: rowToNode(row),\r\n depth: row.walk_depth,\r\n path: row.walk_path.split(','),\r\n });\r\n }\r\n }\r\n\r\n return Array.from(seen.values()).sort((a, b) => a.depth - b.depth);\r\n }\r\n\r\n /**\r\n * 開始ノードから有向エッジを辿り、到達可能な全ノードを返す。\r\n *\r\n * @param nodeId 開始ノード ID\r\n * @param targetKind フィルタ対象のノード種別(省略時は全種別)\r\n * @returns 到達可能なノードのリスト\r\n */\r\n reachableFrom(nodeId: string, targetKind?: NodeKind): GraphNode[] {\r\n // BFS を TypeScript 側で実装する\r\n const visited = new Set<string>();\r\n const queue: string[] = [nodeId];\r\n visited.add(nodeId);\r\n\r\n const results: GraphNode[] = [];\r\n\r\n const edgeStmt = this.db.prepare<[string], EdgeRow>('SELECT * FROM edges WHERE source_id = ?');\r\n const nodeStmt = this.db.prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?');\r\n\r\n while (queue.length > 0) {\r\n const currentId = queue.shift()!;\r\n const edges = edgeStmt.all(currentId);\r\n\r\n for (const edge of edges) {\r\n if (!visited.has(edge.target_id)) {\r\n visited.add(edge.target_id);\r\n queue.push(edge.target_id);\r\n\r\n const targetNode = nodeStmt.get(edge.target_id);\r\n if (targetNode) {\r\n const graphNode = rowToNode(targetNode);\r\n if (targetKind === undefined || graphNode.kind === targetKind) {\r\n results.push(graphNode);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * 2 ノード間の最短パスを BFS で探索する。\r\n *\r\n * @param sourceId 起点ノード ID\r\n * @param targetId 終点ノード ID\r\n * @returns パス情報。パスが存在しない場合は undefined\r\n */\r\n shortestPath(sourceId: string, targetId: string): PathResult | undefined {\r\n const nodeStmt = this.db.prepare<[string], NodeRow>('SELECT * FROM nodes WHERE id = ?');\r\n\r\n // 同一ノードの場合\r\n if (sourceId === targetId) {\r\n const node = nodeStmt.get(sourceId);\r\n if (!node) {\r\n return undefined;\r\n }\r\n return {\r\n nodes: [rowToNode(node)],\r\n edges: [],\r\n length: 0,\r\n };\r\n }\r\n\r\n // BFS で最短パスを探索\r\n const edgeStmt = this.db.prepare<[string], EdgeRow>('SELECT * FROM edges WHERE source_id = ?');\r\n\r\n // parent[nodeId] = { parentId, edge }\r\n const parent = new Map<string, { parentId: string; edge: EdgeRow }>();\r\n const visited = new Set<string>();\r\n const queue: string[] = [sourceId];\r\n visited.add(sourceId);\r\n\r\n let found = false;\r\n\r\n while (queue.length > 0 && !found) {\r\n const currentId = queue.shift()!;\r\n const edges = edgeStmt.all(currentId);\r\n\r\n for (const edge of edges) {\r\n if (!visited.has(edge.target_id)) {\r\n visited.add(edge.target_id);\r\n parent.set(edge.target_id, { parentId: currentId, edge });\r\n if (edge.target_id === targetId) {\r\n found = true;\r\n break;\r\n }\r\n queue.push(edge.target_id);\r\n }\r\n }\r\n }\r\n\r\n if (!found) {\r\n return undefined;\r\n }\r\n\r\n // パスを逆順に再構築\r\n const pathNodeIds: string[] = [];\r\n const pathEdges: GraphEdge[] = [];\r\n\r\n let currentId = targetId;\r\n while (currentId !== sourceId) {\r\n pathNodeIds.unshift(currentId);\r\n const info = parent.get(currentId)!;\r\n pathEdges.unshift(rowToEdge(info.edge));\r\n currentId = info.parentId;\r\n }\r\n pathNodeIds.unshift(sourceId);\r\n\r\n // ノード情報を取得\r\n const nodes: GraphNode[] = [];\r\n for (const nid of pathNodeIds) {\r\n const row = nodeStmt.get(nid);\r\n if (row) {\r\n nodes.push(rowToNode(row));\r\n }\r\n }\r\n\r\n return {\r\n nodes,\r\n edges: pathEdges,\r\n length: pathEdges.length,\r\n };\r\n }\r\n\r\n /**\r\n * プリセットクエリを実行する。\r\n *\r\n * @param pattern プリセット名\r\n * @param params パラメータ(プリセットによって異なる)\r\n * @returns クエリ結果の配列\r\n */\r\n runPreset(pattern: string, params?: Record<string, unknown>): PresetResult {\r\n switch (pattern) {\r\n case 'attack_surface':\r\n return this.presetAttackSurface();\r\n case 'critical_vulns':\r\n return this.presetCriticalVulns();\r\n case 'credential_exposure':\r\n return this.presetCredentialExposure();\r\n case 'unscanned_services':\r\n return this.presetUnscannedServices();\r\n case 'vuln_by_host':\r\n return this.presetVulnByHost();\r\n case 'reachable_services':\r\n return this.presetReachableServices(params);\r\n default:\r\n throw new Error(`Unknown preset pattern: ${pattern}`);\r\n }\r\n }\r\n\r\n // =========================================================================\r\n // プリセットクエリ実装\r\n // =========================================================================\r\n\r\n /**\r\n * attack_surface: host → service → endpoint → input の完全パスを返す。\r\n * input がないエンドポイントも含む(inputId は null)。\r\n */\r\n private presetAttackSurface(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n ep.id AS endpoint_id,\r\n ep.props_json AS endpoint_props,\r\n inp.id AS input_id,\r\n inp.props_json AS input_props\r\n FROM nodes h\r\n JOIN edges e_hs ON e_hs.source_id = h.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n JOIN edges e_se ON e_se.source_id = s.id AND e_se.kind = 'SERVICE_ENDPOINT'\r\n JOIN nodes ep ON ep.id = e_se.target_id AND ep.kind = 'endpoint'\r\n LEFT JOIN edges e_ei ON e_ei.source_id = ep.id AND e_ei.kind = 'ENDPOINT_INPUT'\r\n LEFT JOIN nodes inp ON inp.id = e_ei.target_id AND inp.kind = 'input'\r\n WHERE h.kind = 'host'\r\n ORDER BY h.id, s.id, ep.id, inp.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n endpointId: row.endpoint_id,\r\n endpointProps: row.endpoint_props,\r\n inputId: row.input_id ?? null,\r\n inputProps: row.input_props ?? null,\r\n }));\r\n }\r\n\r\n /**\r\n * critical_vulns: severity が critical/high の脆弱性をホスト情報付きで返す。\r\n */\r\n private presetCriticalVulns(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n v.id AS vuln_id,\r\n v.props_json AS vuln_props,\r\n json_extract(v.props_json, '$.severity') AS severity,\r\n json_extract(v.props_json, '$.title') AS title\r\n FROM nodes v\r\n JOIN edges e_sv ON e_sv.target_id = v.id AND e_sv.kind = 'SERVICE_VULNERABILITY'\r\n JOIN nodes s ON s.id = e_sv.source_id AND s.kind = 'service'\r\n JOIN edges e_hs ON e_hs.target_id = s.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes h ON h.id = e_hs.source_id AND h.kind = 'host'\r\n WHERE v.kind = 'vulnerability'\r\n AND json_extract(v.props_json, '$.severity') IN ('critical', 'high')\r\n ORDER BY\r\n CASE json_extract(v.props_json, '$.severity')\r\n WHEN 'critical' THEN 0\r\n WHEN 'high' THEN 1\r\n END,\r\n h.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n vulnId: row.vuln_id,\r\n vulnProps: row.vuln_props,\r\n severity: row.severity,\r\n title: row.title,\r\n }));\r\n }\r\n\r\n /**\r\n * credential_exposure: service → credential の全マッピングを返す。\r\n */\r\n private presetCredentialExposure(): PresetResult {\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n c.id AS credential_id,\r\n c.props_json AS credential_props\r\n FROM nodes c\r\n JOIN edges e_sc ON e_sc.target_id = c.id AND e_sc.kind = 'SERVICE_CREDENTIAL'\r\n JOIN nodes s ON s.id = e_sc.source_id AND s.kind = 'service'\r\n WHERE c.kind = 'credential'\r\n ORDER BY s.id, c.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n credentialId: row.credential_id,\r\n credentialProps: row.credential_props,\r\n }));\r\n }\r\n\r\n /**\r\n * unscanned_services: endpoint が 0 件のサービスを返す。\r\n */\r\n private presetUnscannedServices(): PresetResult {\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n h.id AS host_id,\r\n h.props_json AS host_props\r\n FROM nodes s\r\n JOIN edges e_hs ON e_hs.target_id = s.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes h ON h.id = e_hs.source_id AND h.kind = 'host'\r\n LEFT JOIN edges e_se ON e_se.source_id = s.id AND e_se.kind = 'SERVICE_ENDPOINT'\r\n WHERE s.kind = 'service'\r\n AND e_se.id IS NULL\r\n ORDER BY h.id, s.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n }));\r\n }\r\n\r\n /**\r\n * vuln_by_host: ホスト別脆弱性カウントを返す。\r\n */\r\n private presetVulnByHost(): PresetResult {\r\n const sql = `\r\n SELECT\r\n h.id AS host_id,\r\n h.props_json AS host_props,\r\n COUNT(v.id) AS vuln_count\r\n FROM nodes h\r\n JOIN edges e_hs ON e_hs.source_id = h.id AND e_hs.kind = 'HOST_SERVICE'\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n JOIN edges e_sv ON e_sv.source_id = s.id AND e_sv.kind = 'SERVICE_VULNERABILITY'\r\n JOIN nodes v ON v.id = e_sv.target_id AND v.kind = 'vulnerability'\r\n WHERE h.kind = 'host'\r\n GROUP BY h.id\r\n ORDER BY vuln_count DESC\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all() as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n hostId: row.host_id,\r\n hostProps: row.host_props,\r\n vulnCount: row.vuln_count,\r\n }));\r\n }\r\n\r\n /**\r\n * reachable_services: 指定 host から到達可能な全サービスを返す。\r\n */\r\n private presetReachableServices(params?: Record<string, unknown>): PresetResult {\r\n const hostId = params?.hostId as string | undefined;\r\n if (!hostId) {\r\n throw new Error('reachable_services preset requires hostId parameter');\r\n }\r\n\r\n const sql = `\r\n SELECT\r\n s.id AS service_id,\r\n s.props_json AS service_props,\r\n e_hs.kind AS edge_kind\r\n FROM edges e_hs\r\n JOIN nodes s ON s.id = e_hs.target_id AND s.kind = 'service'\r\n WHERE e_hs.source_id = ?\r\n AND e_hs.kind = 'HOST_SERVICE'\r\n ORDER BY s.id\r\n `;\r\n\r\n const rows = this.db.prepare(sql).all(hostId) as Array<Record<string, unknown>>;\r\n\r\n return rows.map((row) => ({\r\n serviceId: row.service_id,\r\n serviceProps: row.service_props,\r\n edgeKind: row.edge_kind,\r\n }));\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Mutate Tool (unified)\r\n *\r\n * Single 'mutate' tool with an 'action' parameter that replaces\r\n * all previous mutation tools (add_host, add_credential, etc.).\r\n *\r\n * Actions: add_node, add_edge, update_node, delete_node\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { z } from 'zod';\r\nimport { NodeRepository } from '../../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../../db/repository/edge-repository.js';\r\nimport { NODE_KINDS, EDGE_KINDS, validateProps } from '../../types/graph.js';\r\nimport type { NodeKind, EdgeKind } from '../../types/graph.js';\r\n\r\n/**\r\n * Get or create a singleton \"manual\" artifact for manual data entry.\r\n * Reused across all manual mutations to avoid artifact proliferation.\r\n * Uses direct SQL since ArtifactRepository is being removed.\r\n */\r\nfunction getOrCreateManualArtifact(db: Database.Database): string {\r\n const row = db.prepare(\"SELECT id FROM artifacts WHERE tool = 'manual' LIMIT 1\").get() as\r\n | { id: string }\r\n | undefined;\r\n if (row) return row.id;\r\n\r\n const id = crypto.randomUUID();\r\n const now = new Date().toISOString();\r\n db.prepare(\r\n \"INSERT INTO artifacts (id, tool, kind, path, captured_at) VALUES (?, 'manual', 'manual_entry', 'manual', ?)\",\r\n ).run(id, now);\r\n return id;\r\n}\r\n\r\nexport function registerMutateTool(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n server.tool(\r\n 'mutate',\r\n 'Mutate the AttackDataGraph. Actions: add_node, add_edge, update_node, delete_node',\r\n {\r\n action: z.enum(['add_node', 'add_edge', 'update_node', 'delete_node']),\r\n // Parameters for add_node\r\n kind: z.string().optional().describe('Node kind (host, service, endpoint, etc.)'),\r\n propsJson: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Node properties as JSON string (e.g. {\"authorityKind\":\"IP\",\"authority\":\"10.0.0.1\",\"resolvedIpsJson\":\"[]\"})',\r\n ),\r\n parentId: z\r\n .string()\r\n .optional()\r\n .describe('Parent node ID (required for service, endpoint, input, vhost, cve)'),\r\n // Parameters for add_edge\r\n edgeKind: z.string().optional().describe('Edge kind (HOST_SERVICE, SERVICE_ENDPOINT, etc.)'),\r\n sourceId: z.string().optional().describe('Source node ID for edge'),\r\n targetId: z.string().optional().describe('Target node ID for edge'),\r\n // Parameters for update_node / delete_node\r\n id: z.string().optional().describe('Node ID for update or delete'),\r\n // Common optional\r\n evidenceArtifactId: z\r\n .string()\r\n .optional()\r\n .describe('Evidence artifact ID. If omitted, a \"manual\" artifact is auto-created/reused.'),\r\n },\r\n async ({\r\n action,\r\n kind,\r\n propsJson: propsJsonStr,\r\n parentId,\r\n edgeKind,\r\n sourceId,\r\n targetId,\r\n id,\r\n evidenceArtifactId,\r\n }) => {\r\n // Parse propsJson string into an object when provided\r\n let props: Record<string, unknown> | undefined;\r\n if (propsJsonStr) {\r\n try {\r\n props = JSON.parse(propsJsonStr) as Record<string, unknown>;\r\n } catch {\r\n return {\r\n content: [{ type: 'text', text: `Invalid JSON in propsJson: ${propsJsonStr}` }],\r\n isError: true,\r\n };\r\n }\r\n }\r\n\r\n switch (action) {\r\n // ----------------------------------------------------------------\r\n // add_node\r\n // ----------------------------------------------------------------\r\n case 'add_node': {\r\n if (!kind) {\r\n return {\r\n content: [{ type: 'text', text: 'kind parameter is required for add_node' }],\r\n isError: true,\r\n };\r\n }\r\n if (!NODE_KINDS.includes(kind as NodeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid kind: ${kind}. Valid kinds: ${NODE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n if (!props) {\r\n return {\r\n content: [{ type: 'text', text: 'propsJson parameter is required for add_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Validate props against the schema for this kind\r\n const validation = validateProps(kind as NodeKind, props);\r\n if (!validation.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Props validation failed for kind=\"${kind}\": ${validation.error}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n const artifactId = evidenceArtifactId ?? getOrCreateManualArtifact(db);\r\n const { node, created } = nodeRepo.upsert(kind as NodeKind, props, artifactId, parentId);\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n created,\r\n };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // add_edge\r\n // ----------------------------------------------------------------\r\n case 'add_edge': {\r\n if (!edgeKind) {\r\n return {\r\n content: [{ type: 'text', text: 'edgeKind parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n if (!EDGE_KINDS.includes(edgeKind as EdgeKind)) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Invalid edgeKind: ${edgeKind}. Valid kinds: ${EDGE_KINDS.join(', ')}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n if (!sourceId) {\r\n return {\r\n content: [{ type: 'text', text: 'sourceId parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n if (!targetId) {\r\n return {\r\n content: [{ type: 'text', text: 'targetId parameter is required for add_edge' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n const artifactId = evidenceArtifactId ?? getOrCreateManualArtifact(db);\r\n const { edge, created } = edgeRepo.upsert(\r\n edgeKind as EdgeKind,\r\n sourceId,\r\n targetId,\r\n artifactId,\r\n );\r\n return {\r\n content: [{ type: 'text', text: JSON.stringify({ ...edge, created }, null, 2) }],\r\n };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // update_node\r\n // ----------------------------------------------------------------\r\n case 'update_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter is required for update_node' }],\r\n isError: true,\r\n };\r\n }\r\n if (!props) {\r\n return {\r\n content: [{ type: 'text', text: 'propsJson parameter is required for update_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Find existing node to get its kind and current props\r\n const existing = nodeRepo.findById(id);\r\n if (!existing) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n\r\n // Merge existing props with the partial update\r\n const existingProps = JSON.parse(existing.propsJson) as Record<string, unknown>;\r\n const mergedProps = { ...existingProps, ...props };\r\n\r\n // Validate merged props\r\n const validation = validateProps(existing.kind, mergedProps);\r\n if (!validation.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Props validation failed for kind=\"${existing.kind}\": ${validation.error}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n const updated = nodeRepo.updateProps(id, mergedProps);\r\n if (!updated) {\r\n return {\r\n content: [{ type: 'text', text: `Failed to update node: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n const result = { ...updated, props: JSON.parse(updated.propsJson) };\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };\r\n }\r\n\r\n // ----------------------------------------------------------------\r\n // delete_node\r\n // ----------------------------------------------------------------\r\n case 'delete_node': {\r\n if (!id) {\r\n return {\r\n content: [{ type: 'text', text: 'id parameter is required for delete_node' }],\r\n isError: true,\r\n };\r\n }\r\n\r\n const deleted = nodeRepo.delete(id);\r\n if (!deleted) {\r\n return {\r\n content: [{ type: 'text', text: `Node not found: ${id}` }],\r\n isError: true,\r\n };\r\n }\r\n return {\r\n content: [{ type: 'text', text: `Node ${id} deleted successfully.` }],\r\n };\r\n }\r\n }\r\n },\r\n );\r\n}\r\n","/**\n * sonobat — MCP Ingest Tool\n *\n * Tool for ingesting tool output files into the AttackDataGraph.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { ingest } from '../../engine/ingest.js';\n\nexport function registerIngestTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'ingest_file',\n 'Ingest a tool output file (nmap XML, ffuf JSON, nuclei JSONL) into the AttackDataGraph',\n {\n path: z.string().describe('Absolute path to the tool output file'),\n tool: z.enum(['nmap', 'ffuf', 'nuclei']).describe('Tool that produced the output'),\n },\n async ({ path, tool }) => {\n try {\n const result = ingest(db, { path, tool });\n const nr = result.normalizeResult;\n const summary = [\n `Ingested ${tool} output from ${path}`,\n `Artifact ID: ${result.artifactId}`,\n `Created: ${nr.hostsCreated} hosts, ${nr.servicesCreated} services, ${nr.httpEndpointsCreated} endpoints, ${nr.inputsCreated} inputs, ${nr.observationsCreated} observations, ${nr.vulnerabilitiesCreated} vulnerabilities, ${nr.cvesCreated} CVEs`,\n ].join('\\n');\n return { content: [{ type: 'text', text: summary }] };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { content: [{ type: 'text', text: `Ingest failed: ${message}` }], isError: true };\n }\n },\n );\n}\n","/**\r\n * sonobat — Ingest Engine\r\n *\r\n * ツール出力ファイルを読み込み、パース・正規化してDBに格納する。\r\n * ingestContent() はコアロジック(ファイルシステム非依存・テスト可能)。\r\n * ingest() はファイル読み込みの薄いラッパー。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport crypto from 'node:crypto';\r\nimport path from 'node:path';\r\nimport type { IngestInput, IngestResult } from '../types/engine.js';\r\nimport { parseNmapXml } from '../parser/nmap-parser.js';\r\nimport { parseFfufJson } from '../parser/ffuf-parser.js';\r\nimport { parseNucleiJsonl } from '../parser/nuclei-parser.js';\r\nimport { normalize } from './normalizer.js';\r\n\r\n/**\r\n * ツール出力の文字列を直接受け取り、パース・正規化してDBに格納する。\r\n * ファイルシステムに依存しないため、テストから直接呼び出せる。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param tool ツール種別('nmap' | 'ffuf' | 'nuclei')\r\n * @param content ツール出力の文字列\r\n * @param filePath Artifact に記録するファイルパス\r\n * @returns IngestResult(artifactId + normalizeResult)\r\n */\r\nexport function ingestContent(\r\n db: Database.Database,\r\n tool: 'nmap' | 'ffuf' | 'nuclei',\r\n content: string,\r\n filePath: string,\r\n): IngestResult {\r\n // 1. SHA-256 ハッシュを計算\r\n const sha256 = crypto.createHash('sha256').update(content).digest('hex');\r\n\r\n // 2. Artifact を作成 (direct SQL — ArtifactRepository は廃止)\r\n const artifactId = crypto.randomUUID();\r\n const capturedAt = new Date().toISOString();\r\n db.prepare(\r\n 'INSERT INTO artifacts (id, tool, kind, path, sha256, captured_at) VALUES (?, ?, ?, ?, ?, ?)',\r\n ).run(artifactId, tool, 'tool_output', filePath, sha256, capturedAt);\r\n\r\n // 3. ツール種別に応じてパース\r\n let parseResult;\r\n switch (tool) {\r\n case 'nmap':\r\n parseResult = parseNmapXml(content);\r\n break;\r\n case 'ffuf':\r\n parseResult = parseFfufJson(content);\r\n break;\r\n case 'nuclei':\r\n parseResult = parseNucleiJsonl(content);\r\n break;\r\n default: {\r\n // never 型による網羅性チェック — 未知の tool が渡された場合はコンパイルエラー\r\n const _exhaustive: never = tool;\r\n throw new Error(`Unknown tool: ${String(_exhaustive)}`);\r\n }\r\n }\r\n\r\n // 4. 正規化してDBに書き込む\r\n const normalizeResult = normalize(db, artifactId, parseResult);\r\n\r\n // 5. 結果を返す\r\n return {\r\n artifactId,\r\n normalizeResult,\r\n };\r\n}\r\n\r\n/**\r\n * ファイルパスからツール出力を読み込み、インジェストする。\r\n * ingestContent() の薄いラッパー。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param input IngestInput(path + tool)\r\n * @returns IngestResult\r\n */\r\nexport function ingest(db: Database.Database, input: IngestInput): IngestResult {\r\n const resolved = path.resolve(input.path);\r\n const content = fs.readFileSync(resolved, 'utf-8');\r\n return ingestContent(db, input.tool, content, resolved);\r\n}\r\n","/**\n * sonobat — Nmap XML パーサー\n *\n * nmap の XML 出力を解析し、ParseResult 中間表現を返す。\n * fast-xml-parser を使用して XML をパースする。\n */\n\nimport { XMLParser } from 'fast-xml-parser';\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedServiceObservation,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ============================================================\n// XML パース後の型定義(unknown から安全に取り出すための構造)\n// ============================================================\n\ninterface NmapAddress {\n '@_addr': string;\n '@_addrtype': string;\n}\n\ninterface NmapHostname {\n '@_name': string;\n '@_type': string;\n}\n\ninterface NmapPortState {\n '@_state': string;\n '@_reason'?: string;\n}\n\ninterface NmapServiceAttr {\n '@_name'?: string;\n '@_product'?: string;\n '@_version'?: string;\n '@_extrainfo'?: string;\n '@_tunnel'?: string;\n '@_conf'?: string;\n}\n\ninterface NmapPort {\n '@_protocol': string;\n '@_portid': string;\n state: NmapPortState;\n service?: NmapServiceAttr;\n}\n\ninterface NmapOsMatch {\n '@_name': string;\n '@_accuracy': string;\n}\n\ninterface NmapHost {\n address: NmapAddress | NmapAddress[];\n hostnames?: {\n hostname?: NmapHostname | NmapHostname[];\n };\n ports?: {\n port?: NmapPort | NmapPort[];\n };\n os?: {\n osmatch?: NmapOsMatch | NmapOsMatch[];\n };\n}\n\ninterface NmapRun {\n nmaprun: {\n host?: NmapHost | NmapHost[];\n };\n}\n\n// ============================================================\n// ユーティリティ\n// ============================================================\n\n/** 値を配列に正規化する。undefined/null は空配列を返す。 */\nfunction ensureArray<T>(value: T | T[] | undefined | null): T[] {\n if (value === undefined || value === null) {\n return [];\n }\n return Array.isArray(value) ? value : [value];\n}\n\n/** nmap の conf 属性から protoConfidence を決定する */\nfunction toProtoConfidence(conf: string | undefined): string {\n const n = conf !== undefined ? Number(conf) : 0;\n if (n === 10) return 'high';\n if (n >= 7) return 'medium';\n return 'low';\n}\n\n/** OS accuracy から confidence を決定する */\nfunction toOsConfidence(accuracy: string): string {\n const n = Number(accuracy);\n if (n >= 90) return 'high';\n if (n >= 50) return 'medium';\n return 'low';\n}\n\n/** サービス名が HTTPS を示すかどうかを判定する */\nfunction isHttps(service: NmapServiceAttr): boolean {\n return service['@_name'] === 'https' || service['@_tunnel'] === 'ssl';\n}\n\n/** product, version, extrainfo からバナー文字列を合成する */\nfunction buildBanner(service: NmapServiceAttr): string | undefined {\n const parts: string[] = [];\n if (service['@_product']) parts.push(service['@_product']);\n if (service['@_version']) parts.push(service['@_version']);\n if (service['@_extrainfo']) parts.push(service['@_extrainfo']);\n return parts.length > 0 ? parts.join(' ') : undefined;\n}\n\n/** IPv4 アドレスを address 配列から取得する */\nfunction getIpv4Address(addresses: NmapAddress[]): string | undefined {\n const ipv4 = addresses.find((a) => a['@_addrtype'] === 'ipv4');\n return ipv4?.['@_addr'];\n}\n\n// ============================================================\n// メインパーサー\n// ============================================================\n\n/**\n * nmap XML 出力をパースし、ParseResult を返す。\n *\n * @param xml - nmap の XML 出力文字列\n * @returns ParseResult 中間表現\n */\nexport function parseNmapXml(xml: string): ParseResult {\n const parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n allowBooleanAttributes: true,\n });\n\n const parsed: unknown = parser.parse(xml);\n const nmapRun = parsed as NmapRun;\n\n const result = emptyParseResult();\n\n const hosts = ensureArray(nmapRun.nmaprun?.host);\n\n for (const host of hosts) {\n processHost(host, result);\n }\n\n return result;\n}\n\n/**\n * 単一の host 要素を処理し、結果に追加する。\n */\nfunction processHost(host: NmapHost, result: ParseResult): void {\n // --- ホスト情報 ---\n const addresses = ensureArray(host.address);\n const authority = getIpv4Address(addresses);\n if (authority === undefined) {\n return; // IPv4 アドレスがないホストはスキップ\n }\n\n const parsedHost: ParsedHost = {\n authority,\n authorityKind: 'IP',\n };\n result.hosts.push(parsedHost);\n\n // --- サービス情報 ---\n const ports = ensureArray(host.ports?.port);\n const services: ParsedService[] = [];\n\n for (const port of ports) {\n const service = processPort(port, authority);\n services.push(service);\n result.services.push(service);\n }\n\n // --- OS 情報 ---\n const osMatches = ensureArray(host.os?.osmatch);\n if (osMatches.length > 0) {\n processOsMatches(osMatches, authority, services, result);\n }\n}\n\n/**\n * 単一の port 要素を処理し、ParsedService を返す。\n */\nfunction processPort(port: NmapPort, hostAuthority: string): ParsedService {\n const service = port.service;\n const serviceName = service?.['@_name'] ?? '';\n\n // appProto の決定: https or tunnel=ssl -> 'https'\n const appProto = service !== undefined && isHttps(service) ? 'https' : serviceName;\n\n const banner = service !== undefined ? buildBanner(service) : undefined;\n const protoConfidence = toProtoConfidence(service?.['@_conf']);\n\n return {\n hostAuthority,\n transport: port['@_protocol'],\n port: Number(port['@_portid']),\n appProto,\n protoConfidence,\n banner,\n product: service?.['@_product'],\n version: service?.['@_version'],\n state: port.state['@_state'],\n };\n}\n\n/**\n * OS マッチ情報を serviceObservations に追加する。\n * 最初のサービスの transport/port を使う。サービスがない場合は port=0, transport='tcp' を使う。\n */\nfunction processOsMatches(\n osMatches: NmapOsMatch[],\n hostAuthority: string,\n services: ParsedService[],\n result: ParseResult,\n): void {\n const firstService = services.length > 0 ? services[0] : undefined;\n const transport = firstService?.transport ?? 'tcp';\n const port = firstService?.port ?? 0;\n\n for (const osMatch of osMatches) {\n const observation: ParsedServiceObservation = {\n hostAuthority,\n transport,\n port,\n key: 'os',\n value: osMatch['@_name'],\n confidence: toOsConfidence(osMatch['@_accuracy']),\n };\n result.serviceObservations.push(observation);\n }\n}\n","/**\n * sonobat — Parser intermediate types\n *\n * パーサーは DB の ID を持たない中間表現を返す。\n * Normalizer が自然キー(authority, port 等)で DB を検索・upsert する。\n */\n\n// ============================================================\n// 中間表現(DB ID を持たない)\n// ============================================================\n\n/** ホストの中間表現 */\nexport interface ParsedHost {\n authority: string;\n authorityKind: 'IP' | 'DOMAIN';\n resolvedIps?: string[];\n}\n\n/** サービスの中間表現 */\nexport interface ParsedService {\n hostAuthority: string;\n transport: string;\n port: number;\n appProto: string;\n protoConfidence: string;\n banner?: string;\n product?: string;\n version?: string;\n state: string;\n}\n\n/** サービス観測の中間表現 */\nexport interface ParsedServiceObservation {\n hostAuthority: string;\n transport: string;\n port: number;\n key: string;\n value: string;\n confidence: string;\n}\n\n/** HTTP エンドポイントの中間表現 */\nexport interface ParsedHttpEndpoint {\n hostAuthority: string;\n port: number;\n baseUri: string;\n method: string;\n path: string;\n statusCode?: number;\n contentLength?: number;\n words?: number;\n lines?: number;\n}\n\n/** 入力パラメータの中間表現 */\nexport interface ParsedInput {\n hostAuthority: string;\n port: number;\n location: string;\n name: string;\n typeHint?: string;\n}\n\n/** エンドポイント ↔ 入力の紐づけ中間表現 */\nexport interface ParsedEndpointInput {\n hostAuthority: string;\n port: number;\n method: string;\n path: string;\n inputLocation: string;\n inputName: string;\n}\n\n/** 観測値の中間表現 */\nexport interface ParsedObservation {\n hostAuthority: string;\n port: number;\n inputLocation: string;\n inputName: string;\n rawValue: string;\n normValue: string;\n source: string;\n confidence: string;\n}\n\n/** 脆弱性の中間表現 */\nexport interface ParsedVulnerability {\n hostAuthority: string;\n port: number;\n method?: string;\n path?: string;\n vulnType: string;\n title: string;\n description?: string;\n severity: string;\n confidence: string;\n}\n\n/** CVE の中間表現 */\nexport interface ParsedCve {\n /** 対応する脆弱性の title(紐づけ用) */\n vulnerabilityTitle: string;\n cveId: string;\n description?: string;\n cvssScore?: number;\n cvssVector?: string;\n referenceUrl?: string;\n}\n\n// ============================================================\n// パース結果\n// ============================================================\n\n/** パーサーが返す統一的な結果型 */\nexport interface ParseResult {\n hosts: ParsedHost[];\n services: ParsedService[];\n serviceObservations: ParsedServiceObservation[];\n httpEndpoints: ParsedHttpEndpoint[];\n inputs: ParsedInput[];\n endpointInputs: ParsedEndpointInput[];\n observations: ParsedObservation[];\n vulnerabilities: ParsedVulnerability[];\n cves: ParsedCve[];\n}\n\n/** 空の ParseResult を生成するユーティリティ */\nexport function emptyParseResult(): ParseResult {\n return {\n hosts: [],\n services: [],\n serviceObservations: [],\n httpEndpoints: [],\n inputs: [],\n endpointInputs: [],\n observations: [],\n vulnerabilities: [],\n cves: [],\n };\n}\n","/**\r\n * sonobat — ffuf JSON output parser\r\n *\r\n * ffuf の JSON 出力をパースし、ParseResult に変換する。\r\n * パスディスカバリ、パラメータファジングの両方に対応。\r\n */\r\n\r\nimport type {\r\n ParseResult,\r\n ParsedHost,\r\n ParsedService,\r\n ParsedHttpEndpoint,\r\n ParsedInput,\r\n ParsedEndpointInput,\r\n ParsedObservation,\r\n} from '../types/parser.js';\r\nimport { emptyParseResult } from '../types/parser.js';\r\n\r\n// ---------------------------------------------------------------------------\r\n// 内部型: ffuf JSON 構造のバリデーション用\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface FfufConfig {\r\n url: string;\r\n method: string;\r\n}\r\n\r\ninterface FfufResult {\r\n input: Record<string, string>;\r\n status: number;\r\n length: number;\r\n words: number;\r\n lines: number;\r\n url: string;\r\n host: string;\r\n}\r\n\r\ninterface FfufJson {\r\n commandline: string;\r\n config: FfufConfig;\r\n results: FfufResult[];\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// バリデーション\r\n// ---------------------------------------------------------------------------\r\n\r\nconst IP_REGEX = /^\\d{1,3}(\\.\\d{1,3}){3}$/;\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction validateFfufJson(raw: unknown): FfufJson {\r\n if (!isRecord(raw)) {\r\n throw new Error('ffuf JSON: root must be an object');\r\n }\r\n\r\n if (typeof raw['commandline'] !== 'string') {\r\n throw new Error('ffuf JSON: commandline must be a string');\r\n }\r\n\r\n const config = raw['config'];\r\n if (!isRecord(config)) {\r\n throw new Error('ffuf JSON: config must be an object');\r\n }\r\n if (typeof config['url'] !== 'string' || typeof config['method'] !== 'string') {\r\n throw new Error('ffuf JSON: config.url and config.method must be strings');\r\n }\r\n\r\n if (!Array.isArray(raw['results'])) {\r\n throw new Error('ffuf JSON: results must be an array');\r\n }\r\n\r\n const results: FfufResult[] = [];\r\n for (const item of raw['results'] as unknown[]) {\r\n if (!isRecord(item)) {\r\n throw new Error('ffuf JSON: each result must be an object');\r\n }\r\n results.push({\r\n input: isRecord(item['input'])\r\n ? Object.fromEntries(\r\n Object.entries(item['input'] as Record<string, unknown>).map(([k, v]) => [\r\n k,\r\n String(v),\r\n ]),\r\n )\r\n : {},\r\n status: typeof item['status'] === 'number' ? item['status'] : 0,\r\n length: typeof item['length'] === 'number' ? item['length'] : 0,\r\n words: typeof item['words'] === 'number' ? item['words'] : 0,\r\n lines: typeof item['lines'] === 'number' ? item['lines'] : 0,\r\n url: typeof item['url'] === 'string' ? item['url'] : '',\r\n host: typeof item['host'] === 'string' ? item['host'] : '',\r\n });\r\n }\r\n\r\n return {\r\n commandline: raw['commandline'] as string,\r\n config: {\r\n url: config['url'] as string,\r\n method: config['method'] as string,\r\n },\r\n results,\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// URL ユーティリティ\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface ParsedUrl {\r\n scheme: string;\r\n hostname: string;\r\n port: number;\r\n pathname: string;\r\n searchParams: URLSearchParams;\r\n}\r\n\r\nfunction parseUrl(urlStr: string): ParsedUrl {\r\n const parsed = new URL(urlStr);\r\n const scheme = parsed.protocol.replace(':', '');\r\n\r\n let port: number;\r\n if (parsed.port !== '') {\r\n port = Number(parsed.port);\r\n } else {\r\n port = scheme === 'https' ? 443 : 80;\r\n }\r\n\r\n return {\r\n scheme,\r\n hostname: parsed.hostname,\r\n port,\r\n pathname: parsed.pathname,\r\n searchParams: parsed.searchParams,\r\n };\r\n}\r\n\r\nfunction determineAuthorityKind(hostname: string): 'IP' | 'DOMAIN' {\r\n return IP_REGEX.test(hostname) ? 'IP' : 'DOMAIN';\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// メインパーサー\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * ffuf の JSON 出力文字列をパースし、ParseResult を返す。\r\n *\r\n * @param jsonContent - ffuf が `-of json` で出力した JSON 文字列\r\n * @returns ParseResult\r\n */\r\nexport function parseFfufJson(jsonContent: string): ParseResult {\r\n const raw: unknown = JSON.parse(jsonContent);\r\n const ffuf = validateFfufJson(raw);\r\n const method = ffuf.config.method;\r\n\r\n // results が空なら全て空配列\r\n if (ffuf.results.length === 0) {\r\n return emptyParseResult();\r\n }\r\n\r\n // ----- 集約用 Map -----\r\n // hosts: authority -> ParsedHost\r\n const hostsMap = new Map<string, ParsedHost>();\r\n // services: \"authority:port\" -> ParsedService\r\n const servicesMap = new Map<string, ParsedService>();\r\n // httpEndpoints: \"method:path\" -> ParsedHttpEndpoint\r\n const endpointsMap = new Map<string, ParsedHttpEndpoint>();\r\n // inputs: \"name\" -> ParsedInput (クエリパラメータ名で重複排除)\r\n const inputsMap = new Map<string, ParsedInput>();\r\n // endpointInputs: \"method:path:location:name\" -> ParsedEndpointInput\r\n const endpointInputsMap = new Map<string, ParsedEndpointInput>();\r\n // observations: \"location:name:rawValue\" -> ParsedObservation\r\n const observationsMap = new Map<string, ParsedObservation>();\r\n\r\n for (const result of ffuf.results) {\r\n if (result.url === '') {\r\n continue;\r\n }\r\n\r\n const parsed = parseUrl(result.url);\r\n const { scheme, hostname, port, pathname, searchParams } = parsed;\r\n const authorityKind = determineAuthorityKind(hostname);\r\n const baseUri = `${scheme}://${hostname}:${port}`;\r\n\r\n // --- Host ---\r\n if (!hostsMap.has(hostname)) {\r\n hostsMap.set(hostname, {\r\n authority: hostname,\r\n authorityKind,\r\n });\r\n }\r\n\r\n // --- Service ---\r\n const serviceKey = `${hostname}:${port}`;\r\n if (!servicesMap.has(serviceKey)) {\r\n servicesMap.set(serviceKey, {\r\n hostAuthority: hostname,\r\n transport: 'tcp',\r\n port,\r\n appProto: scheme,\r\n protoConfidence: 'high',\r\n state: 'open',\r\n });\r\n }\r\n\r\n // --- HTTP Endpoint (method + path で重複排除) ---\r\n const endpointKey = `${hostname}:${port}:${method}:${pathname}`;\r\n if (!endpointsMap.has(endpointKey)) {\r\n endpointsMap.set(endpointKey, {\r\n hostAuthority: hostname,\r\n port,\r\n baseUri,\r\n method,\r\n path: pathname,\r\n statusCode: result.status,\r\n contentLength: result.length,\r\n words: result.words,\r\n lines: result.lines,\r\n });\r\n }\r\n\r\n // --- Query Parameters -> inputs, observations, endpointInputs ---\r\n for (const [paramName, paramValue] of searchParams.entries()) {\r\n // Input (パラメータ名で重複排除)\r\n const inputKey = `${hostname}:${port}:query:${paramName}`;\r\n if (!inputsMap.has(inputKey)) {\r\n inputsMap.set(inputKey, {\r\n hostAuthority: hostname,\r\n port,\r\n location: 'query',\r\n name: paramName,\r\n });\r\n }\r\n\r\n // EndpointInput (endpoint + input の組み合わせで重複排除)\r\n const eiKey = `${hostname}:${port}:${method}:${pathname}:query:${paramName}`;\r\n if (!endpointInputsMap.has(eiKey)) {\r\n endpointInputsMap.set(eiKey, {\r\n hostAuthority: hostname,\r\n port,\r\n method,\r\n path: pathname,\r\n inputLocation: 'query',\r\n inputName: paramName,\r\n });\r\n }\r\n\r\n // Observation (パラメータ名 + 値で重複排除)\r\n const obsKey = `${hostname}:${port}:query:${paramName}:${paramValue}`;\r\n if (!observationsMap.has(obsKey)) {\r\n observationsMap.set(obsKey, {\r\n hostAuthority: hostname,\r\n port,\r\n inputLocation: 'query',\r\n inputName: paramName,\r\n rawValue: paramValue,\r\n normValue: paramValue,\r\n source: 'ffuf_url',\r\n confidence: 'high',\r\n });\r\n }\r\n }\r\n }\r\n\r\n return {\r\n hosts: [...hostsMap.values()],\r\n services: [...servicesMap.values()],\r\n serviceObservations: [],\r\n httpEndpoints: [...endpointsMap.values()],\r\n inputs: [...inputsMap.values()],\r\n endpointInputs: [...endpointInputsMap.values()],\r\n observations: [...observationsMap.values()],\r\n vulnerabilities: [],\r\n cves: [],\r\n };\r\n}\r\n","/**\n * sonobat — Nuclei JSONL パーサー\n *\n * nuclei の JSONL 出力を解析し、ParseResult 中間表現を返す。\n * 各行は独立した JSON オブジェクト(nuclei finding)として処理する。\n */\n\nimport type {\n ParseResult,\n ParsedHost,\n ParsedService,\n ParsedHttpEndpoint,\n ParsedVulnerability,\n ParsedCve,\n} from '../types/parser.js';\nimport { emptyParseResult } from '../types/parser.js';\n\n// ============================================================\n// nuclei finding の型定義(unknown から安全に取り出すための構造)\n// ============================================================\n\ninterface NucleiClassification {\n 'cve-id'?: string[];\n 'cvss-metrics'?: string;\n 'cvss-score'?: number;\n}\n\ninterface NucleiInfo {\n name: string;\n severity: string;\n tags: string[];\n classification?: NucleiClassification;\n}\n\ninterface NucleiFinding {\n 'template-id': string;\n info: NucleiInfo;\n type: string;\n host: string;\n 'matched-at': string;\n ip: string;\n port: string;\n scheme: string;\n url: string;\n}\n\n// ============================================================\n// 型ガード\n// ============================================================\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string');\n}\n\nfunction isNucleiClassification(value: unknown): value is NucleiClassification {\n if (!isRecord(value)) return false;\n // classification は空オブジェクトでも許容する\n if ('cve-id' in value && !isStringArray(value['cve-id'])) return false;\n if (\n 'cvss-metrics' in value &&\n typeof value['cvss-metrics'] !== 'string' &&\n value['cvss-metrics'] !== undefined\n )\n return false;\n if (\n 'cvss-score' in value &&\n typeof value['cvss-score'] !== 'number' &&\n value['cvss-score'] !== undefined\n )\n return false;\n return true;\n}\n\nfunction isNucleiInfo(value: unknown): value is NucleiInfo {\n if (!isRecord(value)) return false;\n if (typeof value.name !== 'string') return false;\n if (typeof value.severity !== 'string') return false;\n if (!isStringArray(value.tags)) return false;\n if (\n 'classification' in value &&\n value.classification !== undefined &&\n !isNucleiClassification(value.classification)\n )\n return false;\n return true;\n}\n\nfunction isNucleiFinding(value: unknown): value is NucleiFinding {\n if (!isRecord(value)) return false;\n if (typeof value['template-id'] !== 'string') return false;\n if (!isNucleiInfo(value.info)) return false;\n if (typeof value.type !== 'string') return false;\n if (typeof value.host !== 'string') return false;\n if (typeof value['matched-at'] !== 'string') return false;\n if (typeof value.ip !== 'string') return false;\n if (typeof value.port !== 'string') return false;\n if (typeof value.scheme !== 'string') return false;\n if (typeof value.url !== 'string') return false;\n return true;\n}\n\n// ============================================================\n// URL パス抽出(生文字列から、デコードせずに抽出する)\n// ============================================================\n\n/**\n * URL 文字列から pathname 部分を生文字列のまま抽出する。\n * Node.js の URL クラスは %2e を . にデコードしてパスを正規化してしまうため、\n * パストラバーサル系のペイロードを保存するには raw なパスが必要。\n */\nfunction extractRawPathname(urlStr: string): string {\n // scheme://authority の後のパス部分を取り出す\n // authority の終わり = 3つ目の / の位置(scheme://host:port/path...)\n const schemeEnd = urlStr.indexOf('://');\n if (schemeEnd === -1) {\n return '/';\n }\n const afterAuthority = urlStr.indexOf('/', schemeEnd + 3);\n if (afterAuthority === -1) {\n return '/';\n }\n // パスの終わり = ? または # の最初の出現位置\n const queryStart = urlStr.indexOf('?', afterAuthority);\n const fragmentStart = urlStr.indexOf('#', afterAuthority);\n let pathEnd = urlStr.length;\n if (queryStart !== -1 && queryStart < pathEnd) {\n pathEnd = queryStart;\n }\n if (fragmentStart !== -1 && fragmentStart < pathEnd) {\n pathEnd = fragmentStart;\n }\n return urlStr.substring(afterAuthority, pathEnd);\n}\n\n// ============================================================\n// vulnType 推定\n// ============================================================\n\n/** タグ配列から vulnType を推定する。優先度順に判定する。 */\nfunction inferVulnType(tags: string[]): string {\n const priorityTags = ['sqli', 'xss', 'rce', 'lfi', 'ssrf'] as const;\n for (const tag of priorityTags) {\n if (tags.includes(tag)) {\n return tag;\n }\n }\n return 'other';\n}\n\n// ============================================================\n// メインパーサー\n// ============================================================\n\n/**\n * nuclei JSONL 出力をパースし、ParseResult を返す。\n *\n * @param jsonl - nuclei の JSONL 出力文字列(1行1JSON)\n * @returns ParseResult 中間表現\n */\nexport function parseNucleiJsonl(jsonl: string): ParseResult {\n const result = emptyParseResult();\n\n if (jsonl.trim() === '') {\n return result;\n }\n\n const lines = jsonl.split('\\n');\n const seenHosts = new Set<string>();\n const seenServices = new Set<string>();\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === '') {\n continue;\n }\n\n const parsed: unknown = JSON.parse(trimmed);\n if (!isNucleiFinding(parsed)) {\n continue;\n }\n\n processFinding(parsed, result, seenHosts, seenServices);\n }\n\n return result;\n}\n\n/**\n * 単一の nuclei finding を処理し、結果に追加する。\n */\nfunction processFinding(\n finding: NucleiFinding,\n result: ParseResult,\n seenHosts: Set<string>,\n seenServices: Set<string>,\n): void {\n const ip = finding.ip;\n const port = Number(finding.port);\n const scheme = finding.scheme;\n const matchedAt = finding['matched-at'];\n\n // --- ホスト(重複排除) ---\n if (!seenHosts.has(ip)) {\n seenHosts.add(ip);\n const host: ParsedHost = {\n authority: ip,\n authorityKind: 'IP',\n };\n result.hosts.push(host);\n }\n\n // --- サービス(authority+port で重複排除) ---\n const serviceKey = `${ip}:${port}`;\n if (!seenServices.has(serviceKey)) {\n seenServices.add(serviceKey);\n const service: ParsedService = {\n hostAuthority: ip,\n transport: 'tcp',\n port,\n appProto: scheme,\n protoConfidence: 'high',\n state: 'open',\n };\n result.services.push(service);\n }\n\n // --- HTTP エンドポイント ---\n const rawPath = extractRawPathname(matchedAt);\n const baseUri = `${scheme}://${ip}:${port}`;\n\n const endpoint: ParsedHttpEndpoint = {\n hostAuthority: ip,\n port,\n baseUri,\n method: 'GET',\n path: rawPath,\n };\n result.httpEndpoints.push(endpoint);\n\n // --- 脆弱性 ---\n const info = finding.info;\n const vulnerability: ParsedVulnerability = {\n hostAuthority: ip,\n port,\n method: 'GET',\n path: rawPath,\n vulnType: inferVulnType(info.tags),\n title: info.name,\n severity: info.severity,\n confidence: 'high',\n };\n result.vulnerabilities.push(vulnerability);\n\n // --- CVE ---\n const classification = info.classification;\n if (\n classification !== undefined &&\n isRecord(classification) &&\n 'cve-id' in classification &&\n isStringArray(classification['cve-id']) &&\n classification['cve-id'].length > 0\n ) {\n for (const cveId of classification['cve-id']) {\n const cve: ParsedCve = {\n vulnerabilityTitle: info.name,\n cveId,\n cvssScore:\n typeof classification['cvss-score'] === 'number'\n ? classification['cvss-score']\n : undefined,\n cvssVector:\n typeof classification['cvss-metrics'] === 'string'\n ? classification['cvss-metrics']\n : undefined,\n };\n result.cves.push(cve);\n }\n }\n}\n","/**\r\n * sonobat — Normalizer (Graph-native)\r\n *\r\n * ParseResult(パーサーの中間表現)を受け取り、\r\n * NodeRepository / EdgeRepository を使ってグラフ DB に書き込む。\r\n * 全操作はトランザクションでラップし、アトミックに実行する。\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { ParseResult } from '../types/parser.js';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\n\r\n// ============================================================\r\n// 結果型\r\n// ============================================================\r\n\r\n/** normalize() の実行結果。各エンティティの新規作成数を返す。 */\r\nexport interface NormalizeResult {\r\n hostsCreated: number;\r\n servicesCreated: number;\r\n serviceObservationsCreated: number;\r\n httpEndpointsCreated: number;\r\n inputsCreated: number;\r\n endpointInputsCreated: number;\r\n observationsCreated: number;\r\n vulnerabilitiesCreated: number;\r\n cvesCreated: number;\r\n}\r\n\r\n// ============================================================\r\n// normalize\r\n// ============================================================\r\n\r\n/**\r\n * ParseResult を DB に正規化して書き込む(Graph-native 版)。\r\n *\r\n * - NodeRepository.upsert() で自然キーによる重複排除を行い、\r\n * EdgeRepository.upsert() でリレーションを作成する。\r\n * - 全操作は 1 トランザクション内で実行される。\r\n *\r\n * @param db better-sqlite3 の Database インスタンス\r\n * @param artifactId 根拠となる Artifact の ID(evidence_artifact_id に設定)\r\n * @param parseResult パーサーが返した中間表現\r\n * @returns 各エンティティの新規作成数\r\n */\r\nexport function normalize(\r\n db: Database.Database,\r\n artifactId: string,\r\n parseResult: ParseResult,\r\n): NormalizeResult {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n const run = db.transaction((): NormalizeResult => {\r\n const result: NormalizeResult = {\r\n hostsCreated: 0,\r\n servicesCreated: 0,\r\n serviceObservationsCreated: 0,\r\n httpEndpointsCreated: 0,\r\n inputsCreated: 0,\r\n endpointInputsCreated: 0,\r\n observationsCreated: 0,\r\n vulnerabilitiesCreated: 0,\r\n cvesCreated: 0,\r\n };\r\n\r\n // ---------------------------------------------------------\r\n // 自然キー → ノード ID のマッピング\r\n // ---------------------------------------------------------\r\n const hostIdByAuthority = new Map<string, string>();\r\n // key: \"hostNodeId:transport:port\"\r\n const serviceIdByKey = new Map<string, string>();\r\n // key: \"serviceNodeId:method:path\"\r\n const endpointIdByKey = new Map<string, string>();\r\n // key: \"serviceNodeId:location:name\"\r\n const inputIdByKey = new Map<string, string>();\r\n // key: vulnerability title → node ID\r\n const vulnIdByTitle = new Map<string, string>();\r\n\r\n // ---------------------------------------------------------\r\n // 1. Upsert hosts\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.hosts) {\r\n const { node, created } = nodeRepo.upsert('host', {\r\n authorityKind: parsed.authorityKind,\r\n authority: parsed.authority,\r\n resolvedIpsJson: JSON.stringify(parsed.resolvedIps ?? []),\r\n });\r\n\r\n hostIdByAuthority.set(parsed.authority, node.id);\r\n if (created) {\r\n result.hostsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 2. Upsert services + HOST_SERVICE edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.services) {\r\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\r\n if (!hostId) continue;\r\n\r\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\r\n if (serviceIdByKey.has(svcKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'service',\r\n {\r\n transport: parsed.transport,\r\n port: parsed.port,\r\n appProto: parsed.appProto,\r\n protoConfidence: parsed.protoConfidence,\r\n banner: parsed.banner,\r\n product: parsed.product,\r\n version: parsed.version,\r\n state: parsed.state,\r\n },\r\n artifactId,\r\n hostId,\r\n );\r\n\r\n serviceIdByKey.set(svcKey, node.id);\r\n\r\n // HOST_SERVICE edge\r\n edgeRepo.upsert('HOST_SERVICE', hostId, node.id, artifactId);\r\n\r\n if (created) {\r\n result.servicesCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // ヘルパー: hostAuthority + port → serviceId を解決\r\n // HTTP 系エンティティは transport が常に tcp\r\n // ---------------------------------------------------------\r\n function resolveServiceId(hostAuthority: string, port: number): string | undefined {\r\n const hostId = hostIdByAuthority.get(hostAuthority);\r\n if (!hostId) return undefined;\r\n return serviceIdByKey.get(`${hostId}:tcp:${port}`);\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 3. Service observations (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.serviceObservations) {\r\n const hostId = hostIdByAuthority.get(parsed.hostAuthority);\r\n if (!hostId) continue;\r\n\r\n const svcKey = `${hostId}:${parsed.transport}:${parsed.port}`;\r\n const serviceId = serviceIdByKey.get(svcKey);\r\n if (!serviceId) continue;\r\n\r\n const obsNode = nodeRepo.create(\r\n 'svc_observation',\r\n {\r\n key: parsed.key,\r\n value: parsed.value,\r\n confidence: parsed.confidence,\r\n },\r\n artifactId,\r\n );\r\n\r\n edgeRepo.create('SERVICE_OBSERVATION', serviceId, obsNode.id, artifactId);\r\n result.serviceObservationsCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 4. Upsert HTTP endpoints + SERVICE_ENDPOINT edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.httpEndpoints) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n if (endpointIdByKey.has(epKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'endpoint',\r\n {\r\n baseUri: parsed.baseUri,\r\n method: parsed.method,\r\n path: parsed.path,\r\n statusCode: parsed.statusCode,\r\n contentLength: parsed.contentLength,\r\n words: parsed.words,\r\n lines: parsed.lines,\r\n },\r\n artifactId,\r\n serviceId,\r\n );\r\n\r\n endpointIdByKey.set(epKey, node.id);\r\n\r\n // SERVICE_ENDPOINT edge\r\n edgeRepo.upsert('SERVICE_ENDPOINT', serviceId, node.id, artifactId);\r\n\r\n if (created) {\r\n result.httpEndpointsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 5. Upsert inputs + SERVICE_INPUT edges\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.inputs) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.location}:${parsed.name}`;\r\n if (inputIdByKey.has(inKey)) continue;\r\n\r\n const { node, created } = nodeRepo.upsert(\r\n 'input',\r\n {\r\n location: parsed.location,\r\n name: parsed.name,\r\n typeHint: parsed.typeHint,\r\n },\r\n undefined,\r\n serviceId,\r\n );\r\n\r\n inputIdByKey.set(inKey, node.id);\r\n\r\n // SERVICE_INPUT edge\r\n edgeRepo.upsert('SERVICE_INPUT', serviceId, node.id);\r\n\r\n if (created) {\r\n result.inputsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 6. Upsert endpoint_inputs (ENDPOINT_INPUT edges)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.endpointInputs) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n const endpointId = endpointIdByKey.get(epKey);\r\n if (!endpointId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\r\n const inputId = inputIdByKey.get(inKey);\r\n if (!inputId) continue;\r\n\r\n const { created } = edgeRepo.upsert('ENDPOINT_INPUT', endpointId, inputId, artifactId);\r\n if (created) {\r\n result.endpointInputsCreated++;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 7. Observations (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.observations) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n const inKey = `${serviceId}:${parsed.inputLocation}:${parsed.inputName}`;\r\n const inputId = inputIdByKey.get(inKey);\r\n if (!inputId) continue;\r\n\r\n const obsNode = nodeRepo.create(\r\n 'observation',\r\n {\r\n rawValue: parsed.rawValue,\r\n normValue: parsed.normValue,\r\n source: parsed.source,\r\n confidence: parsed.confidence,\r\n observedAt: new Date().toISOString(),\r\n },\r\n artifactId,\r\n );\r\n\r\n edgeRepo.create('INPUT_OBSERVATION', inputId, obsNode.id, artifactId);\r\n result.observationsCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 8. Vulnerabilities (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.vulnerabilities) {\r\n const serviceId = resolveServiceId(parsed.hostAuthority, parsed.port);\r\n if (!serviceId) continue;\r\n\r\n // endpoint への紐づけ(任意)\r\n let endpointId: string | undefined;\r\n if (parsed.method && parsed.path) {\r\n const epKey = `${serviceId}:${parsed.method}:${parsed.path}`;\r\n endpointId = endpointIdByKey.get(epKey);\r\n }\r\n\r\n const vulnNode = nodeRepo.create(\r\n 'vulnerability',\r\n {\r\n vulnType: parsed.vulnType,\r\n title: parsed.title,\r\n description: parsed.description,\r\n severity: parsed.severity,\r\n confidence: parsed.confidence,\r\n },\r\n artifactId,\r\n );\r\n\r\n vulnIdByTitle.set(parsed.title, vulnNode.id);\r\n\r\n // SERVICE_VULNERABILITY edge\r\n edgeRepo.create('SERVICE_VULNERABILITY', serviceId, vulnNode.id, artifactId);\r\n\r\n // optional ENDPOINT_VULNERABILITY edge\r\n if (endpointId) {\r\n edgeRepo.create('ENDPOINT_VULNERABILITY', endpointId, vulnNode.id, artifactId);\r\n }\r\n\r\n result.vulnerabilitiesCreated++;\r\n }\r\n\r\n // ---------------------------------------------------------\r\n // 9. CVEs (always create new)\r\n // ---------------------------------------------------------\r\n for (const parsed of parseResult.cves) {\r\n const vulnId = vulnIdByTitle.get(parsed.vulnerabilityTitle);\r\n if (!vulnId) continue;\r\n\r\n const cveNode = nodeRepo.create(\r\n 'cve',\r\n {\r\n cveId: parsed.cveId,\r\n description: parsed.description,\r\n cvssScore: parsed.cvssScore,\r\n cvssVector: parsed.cvssVector,\r\n referenceUrl: parsed.referenceUrl,\r\n },\r\n undefined,\r\n vulnId,\r\n );\r\n\r\n edgeRepo.create('VULNERABILITY_CVE', vulnId, cveNode.id);\r\n result.cvesCreated++;\r\n }\r\n\r\n return result;\r\n });\r\n\r\n return run();\r\n}\r\n","/**\n * sonobat — MCP Propose Tool\n *\n * Tool for generating next-step action proposals based on missing data.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type Database from 'better-sqlite3';\nimport { z } from 'zod';\nimport { propose } from '../../engine/proposer.js';\n\nexport function registerProposeTool(server: McpServer, db: Database.Database): void {\n server.tool(\n 'propose',\n 'Analyze the AttackDataGraph for missing data and propose next-step actions',\n {\n hostId: z.string().optional().describe('Limit proposals to a specific host (optional)'),\n },\n async ({ hostId }) => {\n const actions = propose(db, hostId);\n if (actions.length === 0) {\n return {\n content: [\n { type: 'text', text: 'No actions proposed. All discovered data appears complete.' },\n ],\n };\n }\n return { content: [{ type: 'text', text: JSON.stringify(actions, null, 2) }] };\n },\n );\n}\n","/**\r\n * sonobat — Proposer engine (graph-native)\r\n *\r\n * Analyzes the AttackDataGraph stored in SQLite and proposes\r\n * next-step actions (scans, discovery, etc.) based on missing data.\r\n *\r\n * Uses NodeRepository + EdgeRepository instead of entity-specific repositories.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport type { Action } from '../types/engine.js';\r\nimport type { GraphNode } from '../types/graph.js';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\n\r\n/** Helper: parse propsJson safely */\r\nfunction parseProps(node: GraphNode): Record<string, unknown> {\r\n return JSON.parse(node.propsJson) as Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Analyze the database for missing reconnaissance data and return\r\n * a list of proposed actions to fill the gaps.\r\n *\r\n * @param db - The better-sqlite3 database instance\r\n * @param hostId - Optional: limit analysis to a single host\r\n * @returns Array of proposed actions\r\n */\r\nexport function propose(db: Database.Database, hostId?: string): Action[] {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n\r\n const actions: Action[] = [];\r\n\r\n // Determine target hosts\r\n let hosts: GraphNode[];\r\n if (hostId !== undefined) {\r\n const host = nodeRepo.findById(hostId);\r\n if (host === undefined) {\r\n return [];\r\n }\r\n hosts = [host];\r\n } else {\r\n hosts = nodeRepo.findByKind('host');\r\n }\r\n\r\n for (const host of hosts) {\r\n const hostProps = parseProps(host);\r\n const authority = hostProps.authority as string;\r\n\r\n // Find services: edges from host where kind='HOST_SERVICE' -> target nodes\r\n const serviceEdges = edgeRepo.findBySource(host.id, 'HOST_SERVICE');\r\n\r\n // (a) No services at all -> suggest nmap scan\r\n if (serviceEdges.length === 0) {\r\n actions.push({\r\n kind: 'nmap_scan',\r\n description: `Port scan ${authority} to discover services`,\r\n command: `nmap -p- -sV ${authority}`,\r\n params: { hostId: host.id },\r\n });\r\n continue;\r\n }\r\n\r\n // (b) For each HTTP/HTTPS service, check for missing data\r\n for (const serviceEdge of serviceEdges) {\r\n const serviceNode = nodeRepo.findById(serviceEdge.targetId);\r\n if (serviceNode === undefined) {\r\n continue;\r\n }\r\n\r\n const serviceProps = parseProps(serviceNode);\r\n const appProto = serviceProps.appProto as string;\r\n const port = serviceProps.port as number;\r\n const state = serviceProps.state as string | undefined;\r\n\r\n if (appProto !== 'http' && appProto !== 'https') {\r\n continue;\r\n }\r\n if (state !== undefined && state !== 'open') {\r\n continue;\r\n }\r\n\r\n const baseUri = `${appProto}://${authority}:${port}`;\r\n\r\n proposeForHttpService(actions, host, serviceNode, authority, baseUri, nodeRepo, edgeRepo);\r\n }\r\n }\r\n\r\n return actions;\r\n}\r\n\r\n/**\r\n * Check a single HTTP/HTTPS service for missing data and push\r\n * proposed actions into the actions array.\r\n */\r\nfunction proposeForHttpService(\r\n actions: Action[],\r\n host: GraphNode,\r\n service: GraphNode,\r\n authority: string,\r\n baseUri: string,\r\n nodeRepo: NodeRepository,\r\n edgeRepo: EdgeRepository,\r\n): void {\r\n // Find endpoints: edges from service where kind='SERVICE_ENDPOINT' -> target nodes\r\n const endpointEdges = edgeRepo.findBySource(service.id, 'SERVICE_ENDPOINT');\r\n\r\n // No endpoints -> suggest directory/file discovery\r\n if (endpointEdges.length === 0) {\r\n actions.push({\r\n kind: 'ffuf_discovery',\r\n description: `Discover endpoints on ${baseUri}`,\r\n command: `ffuf -u ${baseUri}/FUZZ -w /usr/share/wordlists/dirb/common.txt`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities at service level (used for value_fuzz decision)\r\n // Filter out false_positive vulnerabilities — they should not prevent\r\n // the proposer from suggesting further testing actions.\r\n const vulnEdges = edgeRepo.findBySource(service.id, 'SERVICE_VULNERABILITY');\r\n const activeVulns = vulnEdges.filter((ve) => {\r\n const vulnNode = nodeRepo.findById(ve.targetId);\r\n if (vulnNode === undefined) {\r\n return false;\r\n }\r\n const vulnProps = parseProps(vulnNode);\r\n return vulnProps.status !== 'false_positive';\r\n });\r\n\r\n // For each endpoint: check inputs via ENDPOINT_INPUT edges (per-endpoint)\r\n for (const endpointEdge of endpointEdges) {\r\n const endpointNode = nodeRepo.findById(endpointEdge.targetId);\r\n if (endpointNode === undefined) {\r\n continue;\r\n }\r\n\r\n const endpointProps = parseProps(endpointNode);\r\n const endpointPath = endpointProps.path as string;\r\n\r\n // Find endpoint_input edges: edges from endpoint where kind='ENDPOINT_INPUT' -> input nodes\r\n const inputEdges = edgeRepo.findBySource(endpointNode.id, 'ENDPOINT_INPUT');\r\n\r\n if (inputEdges.length === 0) {\r\n actions.push({\r\n kind: 'parameter_discovery',\r\n description: `Discover input parameters for ${baseUri}${endpointPath}`,\r\n params: { hostId: host.id, serviceId: service.id, endpointId: endpointNode.id },\r\n });\r\n }\r\n\r\n // For each linked input: check observations\r\n for (const inputEdge of inputEdges) {\r\n const inputNode = nodeRepo.findById(inputEdge.targetId);\r\n if (inputNode === undefined) {\r\n continue;\r\n }\r\n\r\n const inputProps = parseProps(inputNode);\r\n const inputName = inputProps.name as string;\r\n const inputLocation = inputProps.location as string;\r\n\r\n // Find observations: edges from input where kind='INPUT_OBSERVATION' -> target nodes\r\n const observationEdges = edgeRepo.findBySource(inputNode.id, 'INPUT_OBSERVATION');\r\n\r\n if (observationEdges.length === 0) {\r\n actions.push({\r\n kind: 'value_collection',\r\n description: `Collect observed values for input \"${inputName}\" (${inputLocation})`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpointNode.id,\r\n inputId: inputNode.id,\r\n },\r\n });\r\n } else if (activeVulns.length === 0) {\r\n // Has input + observations, but no active vulnerabilities -> suggest fuzzing\r\n actions.push({\r\n kind: 'value_fuzz',\r\n description: `Fuzz input \"${inputName}\" (${inputLocation}) on ${baseUri}${endpointPath}`,\r\n params: {\r\n hostId: host.id,\r\n serviceId: service.id,\r\n endpointId: endpointNode.id,\r\n inputId: inputNode.id,\r\n },\r\n });\r\n }\r\n }\r\n }\r\n\r\n // Check vhosts: edges from host where kind='HOST_VHOST'\r\n const vhostEdges = edgeRepo.findBySource(host.id, 'HOST_VHOST');\r\n if (vhostEdges.length === 0) {\r\n actions.push({\r\n kind: 'vhost_discovery',\r\n description: `Discover virtual hosts for ${authority}`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n\r\n // Check vulnerabilities -> suggest nuclei scan if no active vulnerabilities\r\n if (activeVulns.length === 0) {\r\n actions.push({\r\n kind: 'nuclei_scan',\r\n description: `Scan ${baseUri} for known vulnerabilities`,\r\n command: `nuclei -u ${baseUri} -jsonl`,\r\n params: { hostId: host.id, serviceId: service.id },\r\n });\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Knowledge Base Tools\r\n *\r\n * Tools for searching technique documentation (HackTricks knowledge base)\r\n * and managing the FTS5 index.\r\n *\r\n * Features:\r\n * - search_kb: Full-text search with BM25 ranking\r\n * - index_kb: Auto-clone/pull HackTricks + incremental indexing\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport { z } from 'zod';\r\nimport { TechniqueDocRepository } from '../../db/repository/technique-doc-repository.js';\r\nimport { indexHacktricks } from '../../engine/indexer.js';\r\nimport { getHacktricksDir, ensureDataDir } from '../../engine/data-dir.js';\r\nimport { cloneHacktricks, pullHacktricks, isGitAvailable } from '../../engine/git-ops.js';\r\nimport type { IndexResult } from '../../engine/indexer.js';\r\n\r\n/**\r\n * Format an IndexResult into a human-readable message.\r\n */\r\nfunction formatIndexResult(result: IndexResult, dir: string): string {\r\n const lines: string[] = [\r\n `Successfully indexed from ${dir}.`,\r\n ` Chunks inserted: ${result.totalChunks}`,\r\n ` New files: ${result.newFiles}`,\r\n ` Updated files: ${result.updatedFiles}`,\r\n ` Deleted files: ${result.deletedFiles}`,\r\n ` Skipped (unchanged): ${result.skippedFiles}`,\r\n ];\r\n return lines.join('\\n');\r\n}\r\n\r\nexport function registerKbTools(server: McpServer, db: Database.Database): void {\r\n const repo = new TechniqueDocRepository(db);\r\n\r\n // 1. search_kb\r\n server.tool(\r\n 'search_kb',\r\n 'Search the technique knowledge base (HackTricks) using full-text search. Returns relevant technique documentation chunks ranked by relevance.',\r\n {\r\n query: z.string().describe('Search query (e.g. \"docker breakout\", \"SQL injection\")'),\r\n category: z\r\n .string()\r\n .optional()\r\n .describe('Filter by category (e.g. \"linux-hardening\", \"web\")'),\r\n limit: z.number().optional().describe('Maximum number of results (default: 10)'),\r\n },\r\n async ({ query, category, limit }) => {\r\n const results = repo.search(query, { limit: limit ?? 10, category });\r\n\r\n if (results.length === 0) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `No results found for \"${query}\".${repo.count() === 0 ? ' The technique index is empty. Run index_kb to populate it.' : ''}`,\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const formatted = results.map((r) => ({\r\n title: r.title,\r\n category: r.category,\r\n filePath: r.filePath,\r\n chunkIndex: r.chunkIndex,\r\n score: r.score,\r\n content: r.content,\r\n }));\r\n\r\n return { content: [{ type: 'text', text: JSON.stringify(formatted, null, 2) }] };\r\n },\r\n );\r\n\r\n // 2. index_kb (with auto-clone/pull)\r\n server.tool(\r\n 'index_kb',\r\n 'Index or re-index the HackTricks documentation into the full-text search database. When called without a path, automatically clones or updates HackTricks from GitHub. Uses incremental indexing to skip unchanged files.',\r\n {\r\n path: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Path to the HackTricks repository directory. If omitted, auto-clones to ~/.sonobat/data/hacktricks/',\r\n ),\r\n update: z\r\n .boolean()\r\n .optional()\r\n .describe(\r\n 'Whether to pull latest changes before indexing (default: true). Set to false to skip git pull.',\r\n ),\r\n },\r\n async ({ path: hacktricksPath, update }) => {\r\n try {\r\n let dir: string;\r\n const messages: string[] = [];\r\n\r\n if (hacktricksPath) {\r\n // Explicit path provided — use as-is\r\n dir = hacktricksPath;\r\n } else {\r\n // Auto-clone/pull flow\r\n dir = getHacktricksDir();\r\n ensureDataDir();\r\n\r\n if (!fs.existsSync(dir)) {\r\n // Directory doesn't exist → clone\r\n const gitAvailable = await isGitAvailable();\r\n if (!gitAvailable) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: 'git is not installed or not in PATH. Please install git and try again, or provide a path to an existing HackTricks directory.',\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n\r\n messages.push(`Cloning HackTricks to ${dir}...`);\r\n const cloneResult = await cloneHacktricks(dir);\r\n if (!cloneResult.ok) {\r\n return {\r\n content: [\r\n {\r\n type: 'text',\r\n text: `Failed to clone HackTricks: ${cloneResult.error.message}${cloneResult.error.cause ? ` (${cloneResult.error.cause})` : ''}`,\r\n },\r\n ],\r\n isError: true,\r\n };\r\n }\r\n messages.push('Clone completed.');\r\n } else if (update !== false) {\r\n // Directory exists → pull (failure is a warning, not an error)\r\n const pullResult = await pullHacktricks(dir);\r\n if (pullResult.ok) {\r\n messages.push(`Updated: ${pullResult.message}`);\r\n } else {\r\n messages.push(\r\n `Warning: git pull failed (${pullResult.error.message}). Continuing with existing data.`,\r\n );\r\n }\r\n }\r\n }\r\n\r\n const result = indexHacktricks(db, dir);\r\n messages.push(formatIndexResult(result, dir));\r\n\r\n return {\r\n content: [{ type: 'text', text: messages.join('\\n') }],\r\n };\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n return {\r\n content: [{ type: 'text', text: `Failed to index HackTricks: ${message}` }],\r\n isError: true,\r\n };\r\n }\r\n },\r\n );\r\n}\r\n","import type Database from 'better-sqlite3';\r\nimport crypto from 'node:crypto';\r\n\r\n/** Technique documentation entity (camelCase). */\r\nexport interface TechniqueDoc {\r\n id: string;\r\n source: string;\r\n filePath: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunkIndex: number;\r\n indexedAt: string;\r\n fileMtime: string | null;\r\n}\r\n\r\n/** Input for creating a new technique document (no id/indexedAt). */\r\nexport interface CreateTechniqueDocInput {\r\n source: string;\r\n filePath: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunkIndex: number;\r\n fileMtime?: string;\r\n}\r\n\r\n/**\r\n * Raw row shape returned by better-sqlite3 for the `technique_docs` table.\r\n */\r\ninterface TechniqueDocRow {\r\n id: string;\r\n source: string;\r\n file_path: string;\r\n title: string;\r\n category: string;\r\n content: string;\r\n chunk_index: number;\r\n indexed_at: string;\r\n file_mtime: string | null;\r\n}\r\n\r\n/**\r\n * Row shape for FTS5 search results with rank score.\r\n */\r\ninterface TechniqueDocSearchRow extends TechniqueDocRow {\r\n rank: number;\r\n}\r\n\r\n/** Maps a snake_case DB row to a camelCase TechniqueDoc entity. */\r\nfunction rowToTechniqueDoc(row: TechniqueDocRow): TechniqueDoc {\r\n return {\r\n id: row.id,\r\n source: row.source,\r\n filePath: row.file_path,\r\n title: row.title,\r\n category: row.category,\r\n content: row.content,\r\n chunkIndex: row.chunk_index,\r\n indexedAt: row.indexed_at,\r\n fileMtime: row.file_mtime,\r\n };\r\n}\r\n\r\n/** TechniqueDoc with search relevance score. */\r\nexport interface TechniqueDocSearchResult extends TechniqueDoc {\r\n score: number;\r\n}\r\n\r\n/** Options for FTS5 search. */\r\nexport interface SearchOptions {\r\n limit?: number;\r\n category?: string;\r\n}\r\n\r\nconst BATCH_SIZE = 100;\r\n\r\n/**\r\n * Repository for the `technique_docs` table with FTS5 full-text search.\r\n *\r\n * All queries use prepared statements to prevent SQL injection.\r\n */\r\nexport class TechniqueDocRepository {\r\n private readonly db: Database.Database;\r\n\r\n constructor(db: Database.Database) {\r\n this.db = db;\r\n }\r\n\r\n /**\r\n * Bulk-insert technique documents. Runs in batches within transactions.\r\n * Returns the number of inserted documents.\r\n */\r\n index(docs: CreateTechniqueDocInput[]): number {\r\n if (docs.length === 0) return 0;\r\n\r\n const now = new Date().toISOString();\r\n const stmt = this.db.prepare(\r\n `INSERT INTO technique_docs (id, source, file_path, title, category, content, chunk_index, indexed_at, file_mtime)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\r\n );\r\n\r\n let inserted = 0;\r\n\r\n for (let i = 0; i < docs.length; i += BATCH_SIZE) {\r\n const batch = docs.slice(i, i + BATCH_SIZE);\r\n const insertBatch = this.db.transaction(() => {\r\n for (const doc of batch) {\r\n const id = crypto.randomUUID();\r\n stmt.run(\r\n id,\r\n doc.source,\r\n doc.filePath,\r\n doc.title,\r\n doc.category,\r\n doc.content,\r\n doc.chunkIndex,\r\n now,\r\n doc.fileMtime ?? null,\r\n );\r\n inserted++;\r\n }\r\n });\r\n insertBatch();\r\n }\r\n\r\n return inserted;\r\n }\r\n\r\n /**\r\n * Full-text search using FTS5 BM25 ranking.\r\n * User input is wrapped in double quotes for literal matching to prevent FTS5 syntax errors.\r\n */\r\n search(query: string, options: SearchOptions = {}): TechniqueDocSearchResult[] {\r\n const { limit = 20, category } = options;\r\n\r\n // Split query into individual terms, wrap each in double quotes for safety,\r\n // and join with spaces (FTS5 implicit AND). This prevents FTS5 syntax errors\r\n // while allowing flexible multi-word matching.\r\n const terms = query\r\n .trim()\r\n .split(/\\s+/)\r\n .filter((t) => t.length > 0);\r\n const safeQuery = terms.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(' ');\r\n\r\n let sql: string;\r\n const params: unknown[] = [safeQuery];\r\n\r\n if (category) {\r\n sql = `SELECT td.*, fts.rank\r\n FROM technique_docs td\r\n JOIN technique_docs_fts fts ON td.rowid = fts.rowid\r\n WHERE technique_docs_fts MATCH ?\r\n AND td.category = ?\r\n ORDER BY fts.rank\r\n LIMIT ?`;\r\n params.push(category, limit);\r\n } else {\r\n sql = `SELECT td.*, fts.rank\r\n FROM technique_docs td\r\n JOIN technique_docs_fts fts ON td.rowid = fts.rowid\r\n WHERE technique_docs_fts MATCH ?\r\n ORDER BY fts.rank\r\n LIMIT ?`;\r\n params.push(limit);\r\n }\r\n\r\n const rows = this.db.prepare(sql).all(...params) as TechniqueDocSearchRow[];\r\n\r\n return rows.map((row) => ({\r\n ...rowToTechniqueDoc(row),\r\n score: row.rank * -1,\r\n }));\r\n }\r\n\r\n /** Return all unique categories. */\r\n listCategories(): string[] {\r\n const rows = this.db\r\n .prepare('SELECT DISTINCT category FROM technique_docs ORDER BY category')\r\n .all() as Array<{ category: string }>;\r\n return rows.map((r) => r.category);\r\n }\r\n\r\n /** Return all documents in a given category. */\r\n findByCategory(category: string): TechniqueDoc[] {\r\n const rows = this.db\r\n .prepare<[string], TechniqueDocRow>(\r\n `SELECT id, source, file_path, title, category, content, chunk_index, indexed_at, file_mtime\r\n FROM technique_docs\r\n WHERE category = ?\r\n ORDER BY file_path, chunk_index`,\r\n )\r\n .all(category);\r\n return rows.map(rowToTechniqueDoc);\r\n }\r\n\r\n /**\r\n * Delete all documents from a given source.\r\n * Returns the number of deleted documents.\r\n */\r\n deleteBySource(source: string): number {\r\n const result = this.db.prepare('DELETE FROM technique_docs WHERE source = ?').run(source);\r\n return result.changes;\r\n }\r\n\r\n /** Return the total number of indexed documents. */\r\n count(): number {\r\n const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM technique_docs').get() as {\r\n cnt: number;\r\n };\r\n return row.cnt;\r\n }\r\n\r\n /**\r\n * Get a map of file_path → file_mtime for all documents from a given source.\r\n * Returns one entry per unique file_path (uses the first mtime found).\r\n * Used for incremental indexing to detect changed/new/deleted files.\r\n */\r\n findMtimesBySource(source: string): Map<string, string | null> {\r\n const rows = this.db\r\n .prepare(\r\n `SELECT DISTINCT file_path, file_mtime FROM technique_docs WHERE source = ? GROUP BY file_path`,\r\n )\r\n .all(source) as Array<{ file_path: string; file_mtime: string | null }>;\r\n\r\n const map = new Map<string, string | null>();\r\n for (const row of rows) {\r\n map.set(row.file_path, row.file_mtime);\r\n }\r\n return map;\r\n }\r\n\r\n /**\r\n * Delete documents from a given source whose file_path is in the provided list.\r\n * Returns the number of deleted documents.\r\n * Used for incremental indexing to remove changed/deleted files before re-inserting.\r\n */\r\n deleteBySourceAndFilePaths(source: string, filePaths: string[]): number {\r\n if (filePaths.length === 0) return 0;\r\n\r\n // Use parameterized IN clause to prevent SQL injection\r\n const placeholders = filePaths.map(() => '?').join(', ');\r\n const stmt = this.db.prepare(\r\n `DELETE FROM technique_docs WHERE source = ? AND file_path IN (${placeholders})`,\r\n );\r\n\r\n const result = stmt.run(source, ...filePaths);\r\n return result.changes;\r\n }\r\n}\r\n","/**\r\n * sonobat — HackTricks Indexer Engine\r\n *\r\n * Parses Markdown files into searchable chunks and indexes them into\r\n * the technique_docs table with FTS5 full-text search.\r\n * Supports incremental indexing by comparing file mtimes.\r\n */\r\n\r\nimport type Database from 'better-sqlite3';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\nimport { TechniqueDocRepository } from '../db/repository/technique-doc-repository.js';\r\nimport type { CreateTechniqueDocInput } from '../db/repository/technique-doc-repository.js';\r\n\r\n/** A parsed chunk from a Markdown file. */\r\nexport interface MarkdownChunk {\r\n title: string;\r\n content: string;\r\n chunkIndex: number;\r\n}\r\n\r\n/** Result of an indexing operation. */\r\nexport interface IndexResult {\r\n totalChunks: number;\r\n newFiles: number;\r\n updatedFiles: number;\r\n deletedFiles: number;\r\n skippedFiles: number;\r\n}\r\n\r\n/**\r\n * Parse a Markdown file into chunks split by H2 boundaries.\r\n *\r\n * - H1 (`# Title`) is extracted as the document title (falls back to filename).\r\n * - Content is split at H2 (`## Section`) boundaries.\r\n * - Each chunk is an independent search unit.\r\n * - If no H2 is present, the entire content is one chunk.\r\n * - Empty/whitespace-only content returns an empty array.\r\n */\r\nexport function parseMarkdownChunks(markdown: string, fileName: string): MarkdownChunk[] {\r\n const trimmed = markdown.trim();\r\n if (trimmed.length === 0) return [];\r\n\r\n const lines = trimmed.split('\\n');\r\n\r\n // Extract H1 title\r\n let title: string | undefined;\r\n let contentStartIndex = 0;\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n const match = lines[i].match(/^#\\s+(.+)$/);\r\n if (match) {\r\n title = match[1].trim();\r\n contentStartIndex = i + 1;\r\n break;\r\n }\r\n }\r\n\r\n if (!title) {\r\n // Use filename without extension as title\r\n title = path.basename(fileName, path.extname(fileName));\r\n contentStartIndex = 0;\r\n }\r\n\r\n const contentLines = lines.slice(contentStartIndex);\r\n\r\n // Split by H2 boundaries\r\n const sections: Array<{ heading: string | null; lines: string[] }> = [];\r\n let currentSection: { heading: string | null; lines: string[] } = { heading: null, lines: [] };\r\n\r\n for (const line of contentLines) {\r\n const h2Match = line.match(/^##\\s+(.+)$/);\r\n if (h2Match) {\r\n // Save current section if it has content\r\n sections.push(currentSection);\r\n currentSection = { heading: h2Match[1].trim(), lines: [] };\r\n } else {\r\n currentSection.lines.push(line);\r\n }\r\n }\r\n sections.push(currentSection);\r\n\r\n // Build chunks\r\n const chunks: MarkdownChunk[] = [];\r\n let chunkIndex = 0;\r\n\r\n for (const section of sections) {\r\n const sectionContent = section.heading\r\n ? `## ${section.heading}\\n\\n${section.lines.join('\\n').trim()}`\r\n : section.lines.join('\\n').trim();\r\n\r\n if (sectionContent.length === 0) continue;\r\n\r\n chunks.push({\r\n title,\r\n content: sectionContent,\r\n chunkIndex,\r\n });\r\n chunkIndex++;\r\n }\r\n\r\n return chunks;\r\n}\r\n\r\n/**\r\n * Extract category from a file path (relative to the root directory).\r\n * Category is the directory portion of the path using forward slashes.\r\n *\r\n * Examples:\r\n * \"linux-hardening/privilege-escalation/docker-breakout.md\" → \"linux-hardening/privilege-escalation\"\r\n * \"web/sql-injection.md\" → \"web\"\r\n * \"README.md\" → \"\"\r\n */\r\nexport function extractCategory(filePath: string): string {\r\n // Normalize Windows backslashes to forward slashes\r\n const normalized = filePath.replace(/\\\\/g, '/');\r\n const dir = path.posix.dirname(normalized);\r\n return dir === '.' ? '' : dir;\r\n}\r\n\r\n/**\r\n * Recursively collect all .md files from a directory.\r\n */\r\nfunction collectMarkdownFiles(dir: string, baseDir: string): string[] {\r\n const files: string[] = [];\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n files.push(...collectMarkdownFiles(fullPath, baseDir));\r\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\r\n // Skip README files\r\n if (entry.name.toLowerCase() === 'readme.md') continue;\r\n files.push(fullPath);\r\n }\r\n }\r\n\r\n return files;\r\n}\r\n\r\nconst SOURCE_NAME = 'hacktricks';\r\n\r\n/**\r\n * Index all Markdown files from a HackTricks directory into the database.\r\n *\r\n * Uses incremental indexing:\r\n * 1. Fetches existing file_path → file_mtime map from DB\r\n * 2. Compares with disk files to classify: new, updated, deleted, unchanged\r\n * 3. Only processes new/updated files; deletes removed files from DB\r\n *\r\n * Returns an IndexResult with statistics about what was processed.\r\n */\r\nexport function indexHacktricks(db: Database.Database, hacktricksDir: string): IndexResult {\r\n const repo = new TechniqueDocRepository(db);\r\n\r\n // Step 1: Get existing mtimes from DB\r\n const existingMtimes = repo.findMtimesBySource(SOURCE_NAME);\r\n\r\n // Step 2: Collect current files on disk and their mtimes\r\n const mdFiles = collectMarkdownFiles(hacktricksDir, hacktricksDir);\r\n const diskFiles = new Map<string, string>(); // relativePath → mtime ISO string\r\n\r\n for (const filePath of mdFiles) {\r\n const relativePath = path.relative(hacktricksDir, filePath).replace(/\\\\/g, '/');\r\n const stat = fs.statSync(filePath);\r\n diskFiles.set(relativePath, stat.mtime.toISOString());\r\n }\r\n\r\n // Step 3: Classify files\r\n const newFiles: string[] = [];\r\n const updatedFiles: string[] = [];\r\n const skippedFiles: string[] = [];\r\n\r\n for (const [relativePath, diskMtime] of diskFiles) {\r\n const existingMtime = existingMtimes.get(relativePath);\r\n if (existingMtime === undefined) {\r\n // Not in DB → new file\r\n newFiles.push(relativePath);\r\n } else if (existingMtime !== diskMtime) {\r\n // In DB but mtime changed → updated file\r\n updatedFiles.push(relativePath);\r\n } else {\r\n // Same mtime → skip\r\n skippedFiles.push(relativePath);\r\n }\r\n }\r\n\r\n // Deleted files: in DB but not on disk\r\n const deletedFiles: string[] = [];\r\n for (const existingPath of existingMtimes.keys()) {\r\n if (!diskFiles.has(existingPath)) {\r\n deletedFiles.push(existingPath);\r\n }\r\n }\r\n\r\n // Step 4: Delete changed/removed files from DB\r\n const filesToDelete = [...updatedFiles, ...deletedFiles];\r\n if (filesToDelete.length > 0) {\r\n repo.deleteBySourceAndFilePaths(SOURCE_NAME, filesToDelete);\r\n }\r\n\r\n // Step 5: Parse and insert new/updated files\r\n const filesToInsert = [...newFiles, ...updatedFiles];\r\n const allDocs: CreateTechniqueDocInput[] = [];\r\n\r\n for (const relativePath of filesToInsert) {\r\n const fullPath = path.join(hacktricksDir, relativePath);\r\n const content = fs.readFileSync(fullPath, 'utf-8');\r\n const category = extractCategory(relativePath);\r\n const chunks = parseMarkdownChunks(content, path.basename(fullPath));\r\n const fileMtime = diskFiles.get(relativePath)!;\r\n\r\n for (const chunk of chunks) {\r\n allDocs.push({\r\n source: SOURCE_NAME,\r\n filePath: relativePath,\r\n title: chunk.title,\r\n category,\r\n content: chunk.content,\r\n chunkIndex: chunk.chunkIndex,\r\n fileMtime,\r\n });\r\n }\r\n }\r\n\r\n const insertedChunks = repo.index(allDocs);\r\n\r\n return {\r\n totalChunks: insertedChunks,\r\n newFiles: newFiles.length,\r\n updatedFiles: updatedFiles.length,\r\n deletedFiles: deletedFiles.length,\r\n skippedFiles: skippedFiles.length,\r\n };\r\n}\r\n","/**\r\n * sonobat — Data Directory Utilities\r\n *\r\n * Manages the default data directory (~/.sonobat/data/) where HackTricks\r\n * and other data sources are stored. Supports overriding via environment\r\n * variable SONOBAT_DATA_DIR for testing and custom setups.\r\n */\r\n\r\nimport fs from 'node:fs';\r\nimport os from 'node:os';\r\nimport path from 'node:path';\r\n\r\n/**\r\n * Get the root data directory path.\r\n * Default: ~/.sonobat/data/\r\n * Override: set SONOBAT_DATA_DIR environment variable.\r\n */\r\nexport function getDataDir(): string {\r\n const envDir = process.env['SONOBAT_DATA_DIR'];\r\n if (envDir) return envDir;\r\n return path.join(os.homedir(), '.sonobat', 'data');\r\n}\r\n\r\n/**\r\n * Get the HackTricks repository directory path.\r\n * Returns: <dataDir>/hacktricks/\r\n */\r\nexport function getHacktricksDir(): string {\r\n return path.join(getDataDir(), 'hacktricks');\r\n}\r\n\r\n/**\r\n * Ensure the data directory exists, creating it recursively if needed.\r\n */\r\nexport function ensureDataDir(): void {\r\n fs.mkdirSync(getDataDir(), { recursive: true });\r\n}\r\n","/**\r\n * sonobat — Git Operations for HackTricks\r\n *\r\n * Provides git clone/pull operations for managing the HackTricks repository.\r\n * Uses execFile (not shell) to prevent command injection.\r\n */\r\n\r\nimport { execFile as execFileCb } from 'node:child_process';\r\nimport { promisify } from 'node:util';\r\nimport fs from 'node:fs';\r\nimport path from 'node:path';\r\n\r\nconst execFile = promisify(execFileCb);\r\n\r\nconst HACKTRICKS_REPO = 'https://github.com/HackTricks-wiki/hacktricks.git';\r\nconst TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes\r\n\r\n/** Error kinds for git operations. */\r\nexport type GitErrorKind =\r\n | 'git_not_found'\r\n | 'clone_failed'\r\n | 'pull_failed'\r\n | 'permission_denied'\r\n | 'network_error'\r\n | 'directory_not_found'\r\n | 'unknown';\r\n\r\n/** Result type for git operations. */\r\nexport type GitResult =\r\n | { ok: true; message: string }\r\n | { ok: false; error: { kind: GitErrorKind; message: string; cause?: string } };\r\n\r\n/**\r\n * Check if git is available on the system PATH.\r\n */\r\nexport async function isGitAvailable(): Promise<boolean> {\r\n try {\r\n await execFile('git', ['--version'], { timeout: 10_000 });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Classify an error from execFile into a GitErrorKind.\r\n */\r\nfunction classifyError(err: unknown, operation: 'clone' | 'pull'): GitResult {\r\n const errorMessage = err instanceof Error ? err.message : String(err);\r\n const stderr =\r\n err !== null && typeof err === 'object' && 'stderr' in err\r\n ? String((err as { stderr: unknown }).stderr)\r\n : '';\r\n\r\n const combined = `${errorMessage} ${stderr}`.toLowerCase();\r\n\r\n if (combined.includes('enoent') || combined.includes('not recognized') || combined.includes('not found')) {\r\n return {\r\n ok: false,\r\n error: { kind: 'git_not_found', message: 'git is not installed or not in PATH', cause: errorMessage },\r\n };\r\n }\r\n\r\n if (combined.includes('permission denied') || combined.includes('access denied')) {\r\n return {\r\n ok: false,\r\n error: { kind: 'permission_denied', message: `Permission denied during ${operation}`, cause: errorMessage },\r\n };\r\n }\r\n\r\n if (\r\n combined.includes('could not resolve') ||\r\n combined.includes('unable to access') ||\r\n combined.includes('network') ||\r\n combined.includes('timed out')\r\n ) {\r\n return {\r\n ok: false,\r\n error: { kind: 'network_error', message: `Network error during ${operation}`, cause: errorMessage },\r\n };\r\n }\r\n\r\n return {\r\n ok: false,\r\n error: {\r\n kind: operation === 'clone' ? 'clone_failed' : 'pull_failed',\r\n message: `git ${operation} failed`,\r\n cause: errorMessage,\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Clone the HackTricks repository with --depth 1 (shallow clone).\r\n * The targetDir is the destination directory for the clone.\r\n * The parent directory of targetDir must exist.\r\n */\r\nexport async function cloneHacktricks(targetDir: string): Promise<GitResult> {\r\n // Check parent directory exists before attempting clone\r\n const parentDir = path.dirname(targetDir);\r\n\r\n if (!fs.existsSync(parentDir)) {\r\n return {\r\n ok: false,\r\n error: {\r\n kind: 'clone_failed',\r\n message: `Parent directory does not exist: ${parentDir}`,\r\n },\r\n };\r\n }\r\n\r\n try {\r\n const { stdout } = await execFile(\r\n 'git',\r\n ['clone', '--depth', '1', HACKTRICKS_REPO, targetDir],\r\n { timeout: TIMEOUT_MS },\r\n );\r\n return { ok: true, message: stdout.trim() || 'Clone completed successfully' };\r\n } catch (err) {\r\n return classifyError(err, 'clone');\r\n }\r\n}\r\n\r\n/**\r\n * Pull latest changes in an existing HackTricks repository.\r\n * Uses --ff-only to avoid merge conflicts.\r\n */\r\nexport async function pullHacktricks(repoDir: string): Promise<GitResult> {\r\n // Check if directory exists\r\n if (!fs.existsSync(repoDir)) {\r\n return {\r\n ok: false,\r\n error: {\r\n kind: 'directory_not_found',\r\n message: `Directory not found: ${repoDir}`,\r\n },\r\n };\r\n }\r\n\r\n try {\r\n const { stdout } = await execFile('git', ['pull', '--ff-only'], {\r\n cwd: repoDir,\r\n timeout: TIMEOUT_MS,\r\n });\r\n return { ok: true, message: stdout.trim() || 'Already up to date' };\r\n } catch (err) {\r\n return classifyError(err, 'pull');\r\n }\r\n}\r\n","/**\r\n * sonobat — MCP Resources\r\n *\r\n * Read-only resources for browsing the AttackDataGraph.\r\n * Uses the graph-native nodes/edges schema.\r\n */\r\n\r\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport type Database from 'better-sqlite3';\r\nimport { NodeRepository } from '../db/repository/node-repository.js';\r\nimport { EdgeRepository } from '../db/repository/edge-repository.js';\r\nimport { TechniqueDocRepository } from '../db/repository/technique-doc-repository.js';\r\nimport { NODE_KINDS } from '../types/graph.js';\r\n\r\nexport function registerResources(server: McpServer, db: Database.Database): void {\r\n const nodeRepo = new NodeRepository(db);\r\n const edgeRepo = new EdgeRepository(db);\r\n const techDocRepo = new TechniqueDocRepository(db);\r\n\r\n // 1. sonobat://nodes?kind=host — Node list (replaces sonobat://hosts)\r\n server.resource(\r\n 'nodes',\r\n 'sonobat://nodes',\r\n {\r\n description:\r\n 'List of all nodes in the AttackDataGraph (optionally filter by kind via query param)',\r\n },\r\n async (uri) => {\r\n const kindParam = uri.searchParams?.get('kind');\r\n let nodes;\r\n if (kindParam && NODE_KINDS.includes(kindParam as (typeof NODE_KINDS)[number])) {\r\n nodes = nodeRepo.findByKind(kindParam as (typeof NODE_KINDS)[number]);\r\n } else {\r\n // Return all nodes (summary view)\r\n nodes = NODE_KINDS.flatMap((k) => nodeRepo.findByKind(k));\r\n }\r\n const result = nodes.map((n) => ({ ...n, props: JSON.parse(n.propsJson) }));\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify(result, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 2. sonobat://nodes/{id} — Node detail (replaces sonobat://hosts/{id})\r\n server.resource(\r\n 'node-detail',\r\n 'sonobat://nodes/{id}',\r\n { description: 'Detailed node with adjacent edges and neighbor nodes' },\r\n async (uri) => {\r\n const nodeId = uri.pathname.split('/').pop() ?? '';\r\n const node = nodeRepo.findById(nodeId);\r\n if (!node) {\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify({ error: `Node not found: ${nodeId}` }),\r\n },\r\n ],\r\n };\r\n }\r\n\r\n const outEdges = edgeRepo.findBySource(node.id);\r\n const inEdges = edgeRepo.findByTarget(node.id);\r\n const adjacentNodeIds = new Set([\r\n ...outEdges.map((e) => e.targetId),\r\n ...inEdges.map((e) => e.sourceId),\r\n ]);\r\n const adjacentNodes = [...adjacentNodeIds]\r\n .map((nid) => nodeRepo.findById(nid))\r\n .filter(Boolean)\r\n .map((n) => ({ ...n!, props: JSON.parse(n!.propsJson) }));\r\n\r\n const result = {\r\n ...node,\r\n props: JSON.parse(node.propsJson),\r\n outEdges,\r\n inEdges,\r\n adjacentNodes,\r\n };\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n mimeType: 'application/json',\r\n text: JSON.stringify(result, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 3. sonobat://summary — Statistics summary\r\n server.resource(\r\n 'summary',\r\n 'sonobat://summary',\r\n { description: 'Summary statistics of the AttackDataGraph' },\r\n async () => {\r\n const nodeCounts: Record<string, number> = {};\r\n for (const k of NODE_KINDS) {\r\n nodeCounts[k] = nodeRepo.findByKind(k).length;\r\n }\r\n const edgeCount = (db.prepare('SELECT COUNT(*) AS cnt FROM edges').get() as { cnt: number })\r\n .cnt;\r\n const artifactCount = (\r\n db.prepare('SELECT COUNT(*) AS cnt FROM artifacts').get() as { cnt: number }\r\n ).cnt;\r\n\r\n return {\r\n contents: [\r\n {\r\n uri: 'sonobat://summary',\r\n mimeType: 'application/json',\r\n text: JSON.stringify(\r\n { nodes: nodeCounts, edges: edgeCount, artifacts: artifactCount },\r\n null,\r\n 2,\r\n ),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n\r\n // 4. sonobat://techniques/categories — Technique categories\r\n server.resource(\r\n 'technique-categories',\r\n 'sonobat://techniques/categories',\r\n { description: 'List of all technique documentation categories' },\r\n async () => {\r\n const categories = techDocRepo.listCategories();\r\n return {\r\n contents: [\r\n {\r\n uri: 'sonobat://techniques/categories',\r\n mimeType: 'application/json',\r\n text: JSON.stringify(categories, null, 2),\r\n },\r\n ],\r\n };\r\n },\r\n );\r\n}\r\n"],"mappings":"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","names":["db","migration","db","migration","db","migration","db","migration","db","randomUUID","migration","db","db","migration","db","z","crypto","randomUUID","db","crypto","rows","crypto","db","rows","rowToEdge","db","currentId","server","db","z","z","db","server","z","z","crypto","transport","isRecord","db","db","crypto","server","db","z","path","z","db","server","db","z","fs","z","crypto","db","fs","path","path","fs","db","fs","path","fs","path","server","db","z","fs","server","db","db","server"]}
|