@panguard-ai/panguard-guard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/analyze-agent.d.ts +62 -0
- package/dist/agent/analyze-agent.d.ts.map +1 -0
- package/dist/agent/analyze-agent.js +327 -0
- package/dist/agent/analyze-agent.js.map +1 -0
- package/dist/agent/detect-agent.d.ts +59 -0
- package/dist/agent/detect-agent.d.ts.map +1 -0
- package/dist/agent/detect-agent.js +214 -0
- package/dist/agent/detect-agent.js.map +1 -0
- package/dist/agent/index.d.ts +15 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +14 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/report-agent.d.ts +122 -0
- package/dist/agent/report-agent.d.ts.map +1 -0
- package/dist/agent/report-agent.js +468 -0
- package/dist/agent/report-agent.js.map +1 -0
- package/dist/agent/respond-agent.d.ts +113 -0
- package/dist/agent/respond-agent.d.ts.map +1 -0
- package/dist/agent/respond-agent.js +749 -0
- package/dist/agent/respond-agent.js.map +1 -0
- package/dist/agent-client/index.d.ts +81 -0
- package/dist/agent-client/index.d.ts.map +1 -0
- package/dist/agent-client/index.js +170 -0
- package/dist/agent-client/index.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +295 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +108 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/index.d.ts +66 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +284 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/dashboard/index.d.ts +78 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +455 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/guard-engine.d.ts +108 -0
- package/dist/guard-engine.d.ts.map +1 -0
- package/dist/guard-engine.js +740 -0
- package/dist/guard-engine.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/install/index.d.ts +23 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +216 -0
- package/dist/install/index.js.map +1 -0
- package/dist/investigation/index.d.ts +80 -0
- package/dist/investigation/index.d.ts.map +1 -0
- package/dist/investigation/index.js +570 -0
- package/dist/investigation/index.js.map +1 -0
- package/dist/license/index.d.ts +46 -0
- package/dist/license/index.d.ts.map +1 -0
- package/dist/license/index.js +145 -0
- package/dist/license/index.js.map +1 -0
- package/dist/memory/baseline.d.ts +34 -0
- package/dist/memory/baseline.d.ts.map +1 -0
- package/dist/memory/baseline.js +224 -0
- package/dist/memory/baseline.js.map +1 -0
- package/dist/memory/index.d.ts +32 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +58 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/learning.d.ts +35 -0
- package/dist/memory/learning.d.ts.map +1 -0
- package/dist/memory/learning.js +60 -0
- package/dist/memory/learning.js.map +1 -0
- package/dist/monitors/falco-monitor.d.ts +62 -0
- package/dist/monitors/falco-monitor.d.ts.map +1 -0
- package/dist/monitors/falco-monitor.js +226 -0
- package/dist/monitors/falco-monitor.js.map +1 -0
- package/dist/monitors/suricata-monitor.d.ts +80 -0
- package/dist/monitors/suricata-monitor.d.ts.map +1 -0
- package/dist/monitors/suricata-monitor.js +227 -0
- package/dist/monitors/suricata-monitor.js.map +1 -0
- package/dist/notify/email.d.ts +23 -0
- package/dist/notify/email.d.ts.map +1 -0
- package/dist/notify/email.js +124 -0
- package/dist/notify/email.js.map +1 -0
- package/dist/notify/index.d.ts +31 -0
- package/dist/notify/index.d.ts.map +1 -0
- package/dist/notify/index.js +70 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/notify/line-notify.d.ts.map +1 -0
- package/dist/notify/slack.d.ts +21 -0
- package/dist/notify/slack.d.ts.map +1 -0
- package/dist/notify/slack.js +92 -0
- package/dist/notify/slack.js.map +1 -0
- package/dist/notify/telegram.d.ts +21 -0
- package/dist/notify/telegram.d.ts.map +1 -0
- package/dist/notify/telegram.js +89 -0
- package/dist/notify/telegram.js.map +1 -0
- package/dist/response/file-quarantine.d.ts +63 -0
- package/dist/response/file-quarantine.d.ts.map +1 -0
- package/dist/response/file-quarantine.js +137 -0
- package/dist/response/file-quarantine.js.map +1 -0
- package/dist/response/index.d.ts +4 -0
- package/dist/response/index.d.ts.map +1 -0
- package/dist/response/index.js +4 -0
- package/dist/response/index.js.map +1 -0
- package/dist/response/ip-blocker.d.ts +69 -0
- package/dist/response/ip-blocker.d.ts.map +1 -0
- package/dist/response/ip-blocker.js +191 -0
- package/dist/response/ip-blocker.js.map +1 -0
- package/dist/response/process-killer.d.ts +49 -0
- package/dist/response/process-killer.d.ts.map +1 -0
- package/dist/response/process-killer.js +230 -0
- package/dist/response/process-killer.js.map +1 -0
- package/dist/rules/builtin-rules.d.ts +12 -0
- package/dist/rules/builtin-rules.d.ts.map +1 -0
- package/dist/rules/builtin-rules.js +471 -0
- package/dist/rules/builtin-rules.js.map +1 -0
- package/dist/threat-cloud/client-id.d.ts +13 -0
- package/dist/threat-cloud/client-id.d.ts.map +1 -0
- package/dist/threat-cloud/client-id.js +38 -0
- package/dist/threat-cloud/client-id.js.map +1 -0
- package/dist/threat-cloud/index.d.ts +103 -0
- package/dist/threat-cloud/index.d.ts.map +1 -0
- package/dist/threat-cloud/index.js +386 -0
- package/dist/threat-cloud/index.js.map +1 -0
- package/dist/types.d.ts +336 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +42 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GuardEngine - Main orchestrator for PanguardGuard
|
|
3
|
+
* GuardEngine - PanguardGuard 主引擎
|
|
4
|
+
*
|
|
5
|
+
* Orchestrates the complete detection-analysis-response pipeline:
|
|
6
|
+
* 1. Receives security events from MonitorEngine
|
|
7
|
+
* 2. Routes through DetectAgent -> AnalyzeAgent -> RespondAgent -> ReportAgent
|
|
8
|
+
* 3. Manages Context Memory baseline learning and protection transitions
|
|
9
|
+
* 4. Coordinates notifications, threat cloud, and dashboard updates
|
|
10
|
+
*
|
|
11
|
+
* 協調完整的偵測-分析-回應管線:
|
|
12
|
+
* 1. 從 MonitorEngine 接收安全事件
|
|
13
|
+
* 2. 經過 DetectAgent -> AnalyzeAgent -> RespondAgent -> ReportAgent 路由
|
|
14
|
+
* 3. 管理 Context Memory 基線學習和防護轉換
|
|
15
|
+
* 4. 協調通知、威脅雲和儀表板更新
|
|
16
|
+
*
|
|
17
|
+
* @module @panguard-ai/panguard-guard/guard-engine
|
|
18
|
+
*/
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { createLogger, RuleEngine, MonitorEngine, ThreatIntelFeedManager, YaraScanner, setFeedManager, } from '@panguard-ai/core';
|
|
21
|
+
import { TIER_FEATURES } from './types.js';
|
|
22
|
+
import { DetectAgent, AnalyzeAgent, RespondAgent, ReportAgent } from './agent/index.js';
|
|
23
|
+
import { loadBaseline, saveBaseline, isLearningComplete, getLearningProgress, switchToProtectionMode, } from './memory/index.js';
|
|
24
|
+
import { InvestigationEngine } from './investigation/index.js';
|
|
25
|
+
import { sendNotifications } from './notify/index.js';
|
|
26
|
+
import { ThreatCloudClient } from './threat-cloud/index.js';
|
|
27
|
+
import { DashboardServer } from './dashboard/index.js';
|
|
28
|
+
import { PidFile, Watchdog } from './daemon/index.js';
|
|
29
|
+
import { validateLicense, hasFeature } from './license/index.js';
|
|
30
|
+
import { loadSecurityPolicy, runSecurityAudit, logAuditEvent, SyslogAdapter, } from '@panguard-ai/security-hardening';
|
|
31
|
+
import { FalcoMonitor } from './monitors/falco-monitor.js';
|
|
32
|
+
import { SuricataMonitor } from './monitors/suricata-monitor.js';
|
|
33
|
+
import { BUILTIN_RULES } from './rules/builtin-rules.js';
|
|
34
|
+
import { PanguardAgentClient } from './agent-client/index.js';
|
|
35
|
+
const logger = createLogger('panguard-guard:engine');
|
|
36
|
+
/**
|
|
37
|
+
* Attempt to auto-detect and create an LLM provider.
|
|
38
|
+
* Priority: local encrypted config > environment variables > Ollama fallback.
|
|
39
|
+
* Falls back to null if no provider is available (graceful degradation).
|
|
40
|
+
*/
|
|
41
|
+
async function autoDetectLLM() {
|
|
42
|
+
try {
|
|
43
|
+
// Dynamic import to avoid requiring @panguard-ai/core/ai at load time
|
|
44
|
+
const { createLLM } = await import('@panguard-ai/core');
|
|
45
|
+
let provider = null;
|
|
46
|
+
let apiKey;
|
|
47
|
+
let model;
|
|
48
|
+
// 1. Check local encrypted LLM config (~/.panguard/llm.enc)
|
|
49
|
+
try {
|
|
50
|
+
const { homedir } = await import('node:os');
|
|
51
|
+
const { existsSync, readFileSync } = await import('node:fs');
|
|
52
|
+
const { createHash, createDecipheriv } = await import('node:crypto');
|
|
53
|
+
const { hostname, userInfo } = await import('node:os');
|
|
54
|
+
const llmPath = join(homedir(), '.panguard', 'llm.enc');
|
|
55
|
+
if (existsSync(llmPath)) {
|
|
56
|
+
const encrypted = readFileSync(llmPath, 'utf-8');
|
|
57
|
+
const parts = encrypted.split(':');
|
|
58
|
+
if (parts.length === 3) {
|
|
59
|
+
const machineId = `${hostname()}-${userInfo().username}-panguard-ai`;
|
|
60
|
+
const key = createHash('sha256').update(machineId).digest();
|
|
61
|
+
const iv = Buffer.from(parts[0], 'base64');
|
|
62
|
+
const authTag = Buffer.from(parts[1], 'base64');
|
|
63
|
+
const data = Buffer.from(parts[2], 'base64');
|
|
64
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
65
|
+
decipher.setAuthTag(authTag);
|
|
66
|
+
const decrypted = Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
|
|
67
|
+
const llmConfig = JSON.parse(decrypted);
|
|
68
|
+
if (llmConfig.provider && (llmConfig.apiKey || llmConfig.provider === 'ollama')) {
|
|
69
|
+
provider = llmConfig.provider;
|
|
70
|
+
apiKey = llmConfig.apiKey;
|
|
71
|
+
model = llmConfig.model;
|
|
72
|
+
logger.info(`LLM config loaded from local encrypted store (provider: ${provider})`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Local config not available, fall through to env vars
|
|
79
|
+
}
|
|
80
|
+
// 2. Fall back to environment variables
|
|
81
|
+
if (!provider) {
|
|
82
|
+
if (process.env['ANTHROPIC_API_KEY']) {
|
|
83
|
+
provider = 'claude';
|
|
84
|
+
apiKey = process.env['ANTHROPIC_API_KEY'];
|
|
85
|
+
model = process.env['PANGUARD_LLM_MODEL'] ?? 'claude-sonnet-4-20250514';
|
|
86
|
+
}
|
|
87
|
+
else if (process.env['OPENAI_API_KEY']) {
|
|
88
|
+
provider = 'openai';
|
|
89
|
+
apiKey = process.env['OPENAI_API_KEY'];
|
|
90
|
+
model = process.env['PANGUARD_LLM_MODEL'] ?? 'gpt-4o';
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Try Ollama (local, no API key needed)
|
|
94
|
+
provider = 'ollama';
|
|
95
|
+
model = process.env['PANGUARD_LLM_MODEL'] ?? 'llama3';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Allow env var model override even when using local config
|
|
99
|
+
if (process.env['PANGUARD_LLM_MODEL']) {
|
|
100
|
+
model = process.env['PANGUARD_LLM_MODEL'];
|
|
101
|
+
}
|
|
102
|
+
// Default model per provider
|
|
103
|
+
const defaultModels = {
|
|
104
|
+
claude: 'claude-sonnet-4-20250514',
|
|
105
|
+
openai: 'gpt-4o',
|
|
106
|
+
ollama: 'llama3',
|
|
107
|
+
};
|
|
108
|
+
const resolvedModel = model ?? defaultModels[provider] ?? 'llama3';
|
|
109
|
+
const llmProvider = createLLM({ provider, model: resolvedModel, apiKey, lang: 'en' });
|
|
110
|
+
const available = await llmProvider.isAvailable();
|
|
111
|
+
if (!available) {
|
|
112
|
+
logger.info(`LLM provider '${provider}' not available, running without AI`);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
logger.info(`LLM provider '${provider}' (model: ${model}) connected`);
|
|
116
|
+
// Adapt LLMProvider to AnalyzeLLM interface
|
|
117
|
+
const adapter = {
|
|
118
|
+
async analyze(prompt, context) {
|
|
119
|
+
const result = await llmProvider.analyze(prompt, context);
|
|
120
|
+
return {
|
|
121
|
+
summary: result.summary,
|
|
122
|
+
severity: result.severity,
|
|
123
|
+
confidence: result.confidence,
|
|
124
|
+
recommendations: result.recommendations,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
async classify(event) {
|
|
128
|
+
const result = await llmProvider.classify(event);
|
|
129
|
+
return {
|
|
130
|
+
technique: result.technique,
|
|
131
|
+
severity: result.severity,
|
|
132
|
+
confidence: result.confidence,
|
|
133
|
+
description: result.description,
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
async isAvailable() {
|
|
137
|
+
return llmProvider.isAvailable();
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
return adapter;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
logger.info(`LLM auto-detect failed, running without AI: ${err instanceof Error ? err.message : String(err)}`);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* GuardEngine is the central orchestrator for all PanguardGuard functionality
|
|
149
|
+
* GuardEngine 是所有 PanguardGuard 功能的中央協調器
|
|
150
|
+
*/
|
|
151
|
+
export class GuardEngine {
|
|
152
|
+
config;
|
|
153
|
+
mode;
|
|
154
|
+
baseline;
|
|
155
|
+
baselinePath;
|
|
156
|
+
// Agents / 代理
|
|
157
|
+
ruleEngine;
|
|
158
|
+
detectAgent;
|
|
159
|
+
analyzeAgent;
|
|
160
|
+
respondAgent;
|
|
161
|
+
reportAgent;
|
|
162
|
+
investigationEngine;
|
|
163
|
+
// Infrastructure / 基礎設施
|
|
164
|
+
threatCloud;
|
|
165
|
+
feedManager;
|
|
166
|
+
dashboard = null;
|
|
167
|
+
pidFile;
|
|
168
|
+
watchdog = null;
|
|
169
|
+
monitorEngine = null;
|
|
170
|
+
syslogAdapter = null;
|
|
171
|
+
falcoMonitor = null;
|
|
172
|
+
suricataMonitor = null;
|
|
173
|
+
agentClient = null;
|
|
174
|
+
// YARA scanner / YARA 掃描器
|
|
175
|
+
yaraScanner;
|
|
176
|
+
// State / 狀態
|
|
177
|
+
running = false;
|
|
178
|
+
startTime = 0;
|
|
179
|
+
eventsProcessed = 0;
|
|
180
|
+
threatsDetected = 0;
|
|
181
|
+
actionsExecuted = 0;
|
|
182
|
+
threatCloudUploaded = 0;
|
|
183
|
+
statusTimer = null;
|
|
184
|
+
learningCheckTimer = null;
|
|
185
|
+
cloudSyncTimer = null;
|
|
186
|
+
eventCallback;
|
|
187
|
+
constructor(config, llm = null) {
|
|
188
|
+
this.config = config;
|
|
189
|
+
this.mode = config.mode;
|
|
190
|
+
this.baselinePath = join(config.dataDir, 'baseline.json');
|
|
191
|
+
// Load or create baseline / 載入或建立基線
|
|
192
|
+
this.baseline = loadBaseline(this.baselinePath);
|
|
193
|
+
// Validate license / 驗證授權
|
|
194
|
+
let license = validateLicense(config.licenseKey);
|
|
195
|
+
// If CLI tier is provided, map it to Guard's internal LicenseTier
|
|
196
|
+
// CLI tiers: community/solo/pro/business/enterprise
|
|
197
|
+
// Guard tiers: free/pro/enterprise
|
|
198
|
+
if (config.cliTier) {
|
|
199
|
+
const cliTierMap = {
|
|
200
|
+
community: 'free',
|
|
201
|
+
solo: 'pro',
|
|
202
|
+
pro: 'pro',
|
|
203
|
+
business: 'enterprise',
|
|
204
|
+
enterprise: 'enterprise',
|
|
205
|
+
};
|
|
206
|
+
const mappedTier = cliTierMap[config.cliTier] ?? 'free';
|
|
207
|
+
const keyTierLevel = { free: 0, pro: 1, enterprise: 2 }[license.tier] ?? 0;
|
|
208
|
+
const cliTierLevel = { free: 0, pro: 1, enterprise: 2 }[mappedTier] ?? 0;
|
|
209
|
+
// Use whichever tier is higher
|
|
210
|
+
if (cliTierLevel > keyTierLevel) {
|
|
211
|
+
license = {
|
|
212
|
+
...license,
|
|
213
|
+
tier: mappedTier,
|
|
214
|
+
features: TIER_FEATURES[mappedTier],
|
|
215
|
+
isValid: true,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
logger.info(`License: ${license.tier} tier (valid: ${license.isValid}) / ` +
|
|
220
|
+
`授權: ${license.tier} 等級 (有效: ${license.isValid})`);
|
|
221
|
+
// Initialize rule engine with built-in + bundled Sigma rules
|
|
222
|
+
// 初始化規則引擎(內建 + 打包的 Sigma 規則)
|
|
223
|
+
this.ruleEngine = new RuleEngine({
|
|
224
|
+
rulesDir: join(config.dataDir, 'rules'),
|
|
225
|
+
communityRulesDir: config.bundledSigmaDir,
|
|
226
|
+
hotReload: true,
|
|
227
|
+
customRules: BUILTIN_RULES,
|
|
228
|
+
});
|
|
229
|
+
// Initialize YARA scanner / 初始化 YARA 掃描器
|
|
230
|
+
this.yaraScanner = new YaraScanner();
|
|
231
|
+
// Initialize agents / 初始化代理
|
|
232
|
+
this.detectAgent = new DetectAgent(this.ruleEngine);
|
|
233
|
+
const analyzeLLM = hasFeature(license, 'ai_analysis') ? llm : null;
|
|
234
|
+
this.analyzeAgent = new AnalyzeAgent(analyzeLLM);
|
|
235
|
+
this.respondAgent = new RespondAgent(config.actionPolicy, this.mode);
|
|
236
|
+
this.reportAgent = new ReportAgent(join(config.dataDir, 'events.jsonl'), this.mode);
|
|
237
|
+
// Initialize investigation engine / 初始化調查引擎
|
|
238
|
+
this.investigationEngine = new InvestigationEngine(this.baseline);
|
|
239
|
+
// Initialize threat cloud - always enable upload for all tiers
|
|
240
|
+
// Full threat cloud API access (rule fetching, stats) gated by enterprise
|
|
241
|
+
this.threatCloud = new ThreatCloudClient(config.threatCloudEndpoint, config.dataDir);
|
|
242
|
+
// Initialize threat intel feed manager
|
|
243
|
+
this.feedManager = new ThreatIntelFeedManager({
|
|
244
|
+
abuseIPDBKey: process.env['ABUSEIPDB_KEY'],
|
|
245
|
+
});
|
|
246
|
+
setFeedManager(this.feedManager);
|
|
247
|
+
// PID file / PID 檔案
|
|
248
|
+
this.pidFile = new PidFile(config.dataDir);
|
|
249
|
+
logger.info('GuardEngine initialized / GuardEngine 已初始化');
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Create a GuardEngine with auto-detected LLM provider.
|
|
253
|
+
* Checks env vars for ANTHROPIC_API_KEY, OPENAI_API_KEY, or tries Ollama.
|
|
254
|
+
*/
|
|
255
|
+
static async create(config) {
|
|
256
|
+
const llm = await autoDetectLLM();
|
|
257
|
+
return new GuardEngine(config, llm);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Start the guard engine / 啟動守護引擎
|
|
261
|
+
*/
|
|
262
|
+
async start() {
|
|
263
|
+
if (this.running) {
|
|
264
|
+
logger.warn('GuardEngine already running / GuardEngine 已在執行中');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
logger.info(`Starting GuardEngine in ${this.mode} mode / 啟動 GuardEngine(${this.mode} 模式)`);
|
|
268
|
+
// Write PID file / 寫入 PID 檔案
|
|
269
|
+
this.pidFile.write();
|
|
270
|
+
// Security self-audit on startup / 啟動時安全自檢
|
|
271
|
+
try {
|
|
272
|
+
const policy = loadSecurityPolicy({});
|
|
273
|
+
const auditReport = runSecurityAudit(policy);
|
|
274
|
+
const unfixed = auditReport.findings.filter((f) => !f.fixed);
|
|
275
|
+
if (unfixed.length > 0) {
|
|
276
|
+
logger.warn(`Security audit: ${unfixed.length} issue(s) found (risk score: ${auditReport.riskScore})`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
logger.info('Security audit: all checks passed');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
logger.warn(`Security audit skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
284
|
+
}
|
|
285
|
+
// Initialize syslog adapter if configured / 如有設定則初始化 Syslog 轉送器
|
|
286
|
+
const syslogServer = process.env['PANGUARD_SYSLOG_SERVER'];
|
|
287
|
+
if (syslogServer) {
|
|
288
|
+
const syslogPort = parseInt(process.env['PANGUARD_SYSLOG_PORT'] ?? '514', 10);
|
|
289
|
+
this.syslogAdapter = new SyslogAdapter(syslogServer, syslogPort);
|
|
290
|
+
logger.info(`Syslog adapter initialized: ${syslogServer}:${syslogPort}`);
|
|
291
|
+
}
|
|
292
|
+
// Start threat intel feeds (non-blocking, best-effort)
|
|
293
|
+
this.feedManager
|
|
294
|
+
.start()
|
|
295
|
+
.then(() => {
|
|
296
|
+
logger.info(`Threat intel feeds loaded: ${this.feedManager.getIoCCount()} IoCs, ${this.feedManager.getIPCount()} IPs indexed`);
|
|
297
|
+
})
|
|
298
|
+
.catch((err) => {
|
|
299
|
+
logger.warn(`Threat intel feed start failed (using hardcoded list): ${err instanceof Error ? err.message : String(err)}`);
|
|
300
|
+
});
|
|
301
|
+
// Load rules / 載入規則
|
|
302
|
+
await this.ruleEngine.loadRules();
|
|
303
|
+
// Load community rules from threat cloud / 從威脅雲載入社群規則
|
|
304
|
+
const cloudRules = await this.threatCloud.fetchRules();
|
|
305
|
+
for (const rule of cloudRules) {
|
|
306
|
+
try {
|
|
307
|
+
const parsed = JSON.parse(rule.ruleContent);
|
|
308
|
+
if (parsed.id && parsed.title && parsed.detection) {
|
|
309
|
+
this.ruleEngine.addRule(parsed);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// Skip invalid cloud rules / 跳過無效的雲端規則
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Fetch IP blocklist from Threat Cloud and inject into feed manager
|
|
317
|
+
// 從 Threat Cloud 取得 IP 封鎖清單並注入威脅情報管理器
|
|
318
|
+
this.threatCloud
|
|
319
|
+
.fetchBlocklist()
|
|
320
|
+
.then((ips) => {
|
|
321
|
+
if (ips.length > 0) {
|
|
322
|
+
const added = this.feedManager.addExternalIPs(ips, 'threat_cloud_blocklist', 85);
|
|
323
|
+
logger.info(`Threat Cloud blocklist loaded: ${added} IPs / ` +
|
|
324
|
+
`Threat Cloud 封鎖清單已載入: ${added} 個 IP`);
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
.catch((err) => {
|
|
328
|
+
logger.warn(`Threat Cloud blocklist fetch skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
329
|
+
});
|
|
330
|
+
// Load YARA rules (non-blocking, best-effort) / 載入 YARA 規則
|
|
331
|
+
// Custom rules from user's dataDir, bundled rules from installation
|
|
332
|
+
{
|
|
333
|
+
const yaraCustomDir = this.config.yaraRulesDir
|
|
334
|
+
? join(this.config.yaraRulesDir, 'custom')
|
|
335
|
+
: join(this.config.dataDir, 'yara-rules', 'custom');
|
|
336
|
+
const yaraBundledDir = this.config.bundledYaraDir;
|
|
337
|
+
this.yaraScanner
|
|
338
|
+
.loadAllRules(yaraCustomDir, yaraBundledDir)
|
|
339
|
+
.then((count) => {
|
|
340
|
+
logger.info(`YARA rules loaded: ${count} rules / YARA 規則已載入: ${count} 條`);
|
|
341
|
+
})
|
|
342
|
+
.catch((err) => {
|
|
343
|
+
logger.warn(`YARA rules load failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
// Periodic Threat Cloud sync (rules + blocklist every hour)
|
|
347
|
+
// 定期同步 Threat Cloud(每小時更新規則和封鎖清單)
|
|
348
|
+
this.cloudSyncTimer = setInterval(() => {
|
|
349
|
+
void this.syncThreatCloud();
|
|
350
|
+
}, 60 * 60 * 1000); // 1 hour
|
|
351
|
+
// Start monitor engine / 啟動監控引擎
|
|
352
|
+
this.monitorEngine = new MonitorEngine({
|
|
353
|
+
networkPollInterval: this.config.monitors.networkPollInterval,
|
|
354
|
+
processPollInterval: this.config.monitors.processPollInterval,
|
|
355
|
+
});
|
|
356
|
+
this.monitorEngine.on('event', (event) => {
|
|
357
|
+
void this.processEvent(event);
|
|
358
|
+
});
|
|
359
|
+
this.monitorEngine.start();
|
|
360
|
+
// Start Falco eBPF monitor (optional, graceful degradation)
|
|
361
|
+
// 啟動 Falco eBPF 監控(可選,優雅降級)
|
|
362
|
+
this.falcoMonitor = new FalcoMonitor();
|
|
363
|
+
const falcoAvailable = await this.falcoMonitor.checkAvailability();
|
|
364
|
+
if (falcoAvailable) {
|
|
365
|
+
this.falcoMonitor.on('event', (event) => void this.processEvent(event));
|
|
366
|
+
await this.falcoMonitor.start();
|
|
367
|
+
logger.info('Falco eBPF kernel-level monitoring active');
|
|
368
|
+
}
|
|
369
|
+
// Start Suricata network IDS monitor (optional, graceful degradation)
|
|
370
|
+
// 啟動 Suricata 網路 IDS 監控(可選,優雅降級)
|
|
371
|
+
this.suricataMonitor = new SuricataMonitor();
|
|
372
|
+
const suricataAvailable = await this.suricataMonitor.checkAvailability();
|
|
373
|
+
if (suricataAvailable) {
|
|
374
|
+
this.suricataMonitor.on('event', (event) => void this.processEvent(event));
|
|
375
|
+
await this.suricataMonitor.start();
|
|
376
|
+
logger.info('Suricata network IDS monitoring active');
|
|
377
|
+
}
|
|
378
|
+
// Start agent client if manager URL is configured (distributed mode)
|
|
379
|
+
// 如有設定 Manager URL 則啟動 Agent 客戶端(分散式模式)
|
|
380
|
+
if (this.config.managerUrl) {
|
|
381
|
+
try {
|
|
382
|
+
this.agentClient = new PanguardAgentClient(this.config.managerUrl);
|
|
383
|
+
const reg = await this.agentClient.register('1.0.0');
|
|
384
|
+
logger.info(`Agent mode active: registered as ${reg.agentId}`);
|
|
385
|
+
this.agentClient.startHeartbeat(() => ({
|
|
386
|
+
eventsProcessed: this.eventsProcessed,
|
|
387
|
+
threatsDetected: this.threatsDetected,
|
|
388
|
+
actionsExecuted: this.actionsExecuted,
|
|
389
|
+
mode: this.mode,
|
|
390
|
+
uptime: Date.now() - this.startTime,
|
|
391
|
+
memoryUsageMB: Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 10) / 10,
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
logger.warn(`Agent registration failed (standalone mode): ${err instanceof Error ? err.message : String(err)}`);
|
|
396
|
+
this.agentClient = null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Start dashboard if enabled / 啟動儀表板(如已啟用)
|
|
400
|
+
const license = validateLicense(this.config.licenseKey);
|
|
401
|
+
if (this.config.dashboardEnabled && hasFeature(license, 'dashboard')) {
|
|
402
|
+
this.dashboard = new DashboardServer(this.config.dashboardPort);
|
|
403
|
+
this.dashboard.setConfigGetter(() => this.config);
|
|
404
|
+
await this.dashboard.start();
|
|
405
|
+
}
|
|
406
|
+
// Start watchdog if enabled / 啟動看門狗(如已啟用)
|
|
407
|
+
if (this.config.watchdogEnabled) {
|
|
408
|
+
this.watchdog = new Watchdog(this.config.watchdogInterval, () => {
|
|
409
|
+
logger.error('Watchdog triggered restart / 看門狗觸發重啟');
|
|
410
|
+
});
|
|
411
|
+
this.watchdog.start();
|
|
412
|
+
}
|
|
413
|
+
// Periodic status update / 定期狀態更新
|
|
414
|
+
this.statusTimer = setInterval(() => {
|
|
415
|
+
this.updateDashboardStatus();
|
|
416
|
+
this.eventCallback?.('status', {
|
|
417
|
+
eventsProcessed: this.eventsProcessed,
|
|
418
|
+
threatsDetected: this.threatsDetected,
|
|
419
|
+
actionsExecuted: this.actionsExecuted,
|
|
420
|
+
uploaded: this.threatCloudUploaded,
|
|
421
|
+
mode: this.mode,
|
|
422
|
+
uptime: Date.now() - this.startTime,
|
|
423
|
+
});
|
|
424
|
+
if (this.watchdog)
|
|
425
|
+
this.watchdog.heartbeat();
|
|
426
|
+
}, 5000);
|
|
427
|
+
// Learning period check / 學習期檢查
|
|
428
|
+
this.learningCheckTimer = setInterval(() => {
|
|
429
|
+
this.checkLearningTransition();
|
|
430
|
+
}, 60000);
|
|
431
|
+
this.running = true;
|
|
432
|
+
this.startTime = Date.now();
|
|
433
|
+
logger.info('GuardEngine started / GuardEngine 已啟動');
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Stop the guard engine / 停止守護引擎
|
|
437
|
+
*/
|
|
438
|
+
async stop() {
|
|
439
|
+
if (!this.running)
|
|
440
|
+
return;
|
|
441
|
+
logger.info('Stopping GuardEngine / 停止 GuardEngine');
|
|
442
|
+
// Clear timers / 清除計時器
|
|
443
|
+
if (this.statusTimer) {
|
|
444
|
+
clearInterval(this.statusTimer);
|
|
445
|
+
this.statusTimer = null;
|
|
446
|
+
}
|
|
447
|
+
if (this.learningCheckTimer) {
|
|
448
|
+
clearInterval(this.learningCheckTimer);
|
|
449
|
+
this.learningCheckTimer = null;
|
|
450
|
+
}
|
|
451
|
+
if (this.cloudSyncTimer) {
|
|
452
|
+
clearInterval(this.cloudSyncTimer);
|
|
453
|
+
this.cloudSyncTimer = null;
|
|
454
|
+
}
|
|
455
|
+
// Stop components / 停止元件
|
|
456
|
+
this.feedManager.stop();
|
|
457
|
+
setFeedManager(null);
|
|
458
|
+
if (this.monitorEngine)
|
|
459
|
+
this.monitorEngine.stop();
|
|
460
|
+
if (this.dashboard)
|
|
461
|
+
await this.dashboard.stop();
|
|
462
|
+
if (this.watchdog)
|
|
463
|
+
this.watchdog.stop();
|
|
464
|
+
if (this.syslogAdapter)
|
|
465
|
+
this.syslogAdapter = null;
|
|
466
|
+
if (this.falcoMonitor) {
|
|
467
|
+
this.falcoMonitor.stop();
|
|
468
|
+
this.falcoMonitor = null;
|
|
469
|
+
}
|
|
470
|
+
if (this.suricataMonitor) {
|
|
471
|
+
this.suricataMonitor.stop();
|
|
472
|
+
this.suricataMonitor = null;
|
|
473
|
+
}
|
|
474
|
+
if (this.agentClient) {
|
|
475
|
+
this.agentClient.stopHeartbeat();
|
|
476
|
+
this.agentClient = null;
|
|
477
|
+
}
|
|
478
|
+
// Save baseline / 儲存基線
|
|
479
|
+
saveBaseline(this.baselinePath, this.baseline);
|
|
480
|
+
// Flush threat cloud buffer and queue / 清空威脅雲緩衝和佇列
|
|
481
|
+
await this.threatCloud.flushQueue();
|
|
482
|
+
// Clean up / 清理
|
|
483
|
+
this.ruleEngine.destroy();
|
|
484
|
+
this.pidFile.remove();
|
|
485
|
+
this.running = false;
|
|
486
|
+
logger.info('GuardEngine stopped / GuardEngine 已停止');
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Process a single security event through the full pipeline
|
|
490
|
+
* 透過完整管線處理單一安全事件
|
|
491
|
+
*/
|
|
492
|
+
async processEvent(event) {
|
|
493
|
+
this.eventsProcessed++;
|
|
494
|
+
// Audit log every event / 稽核日誌記錄每個事件
|
|
495
|
+
logAuditEvent({
|
|
496
|
+
level: 'info',
|
|
497
|
+
action: 'policy_check',
|
|
498
|
+
target: event.id,
|
|
499
|
+
result: 'success',
|
|
500
|
+
context: { source: event.source, category: event.category },
|
|
501
|
+
});
|
|
502
|
+
if (this.syslogAdapter) {
|
|
503
|
+
this.syslogAdapter.send({
|
|
504
|
+
level: 'info',
|
|
505
|
+
action: 'policy_check',
|
|
506
|
+
target: event.id,
|
|
507
|
+
result: 'success',
|
|
508
|
+
module: 'panguard-guard',
|
|
509
|
+
context: { source: event.source, description: event.description },
|
|
510
|
+
timestamp: new Date().toISOString(),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
// YARA scan for file events / 對檔案事件執行 YARA 掃描
|
|
515
|
+
if (event.source === 'file' &&
|
|
516
|
+
this.yaraScanner.getRuleCount() > 0 &&
|
|
517
|
+
event.metadata?.['filePath'] &&
|
|
518
|
+
event.metadata?.['action'] !== 'deleted') {
|
|
519
|
+
try {
|
|
520
|
+
const yaraResult = await this.yaraScanner.scanFile(event.metadata['filePath']);
|
|
521
|
+
const yaraEvent = this.yaraScanner.toSecurityEvent(yaraResult);
|
|
522
|
+
if (yaraEvent) {
|
|
523
|
+
// Process YARA match as a separate high-priority event
|
|
524
|
+
// 將 YARA 比對結果作為獨立高優先事件處理
|
|
525
|
+
logger.warn(`YARA match: ${yaraResult.matches.map((m) => m.rule).join(', ')} in ${yaraResult.filePath}`);
|
|
526
|
+
void this.processEvent(yaraEvent);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
// YARA scan failure is non-fatal / YARA 掃描失敗不影響主流程
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Stage 1: Detect / 階段 1: 偵測
|
|
534
|
+
const detection = this.detectAgent.detect(event);
|
|
535
|
+
if (!detection) {
|
|
536
|
+
// No threat detected - still update baseline in learning mode
|
|
537
|
+
// 未偵測到威脅 - 學習模式下仍更新基線
|
|
538
|
+
if (this.mode === 'learning') {
|
|
539
|
+
const { updateBaseline } = await import('./memory/baseline.js');
|
|
540
|
+
this.baseline = updateBaseline(this.baseline, event);
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
this.threatsDetected++;
|
|
545
|
+
// Stage 2: Analyze (with Dynamic Reasoning investigation)
|
|
546
|
+
// 階段 2: 分析(使用動態推理調查)
|
|
547
|
+
const verdict = await this.analyzeAgent.analyze(detection, this.baseline);
|
|
548
|
+
// Run investigation for suspicious/malicious verdicts
|
|
549
|
+
// 對可疑/惡意判決執行調查
|
|
550
|
+
if (verdict.conclusion !== 'benign') {
|
|
551
|
+
const investigation = await this.investigationEngine.investigate(event);
|
|
552
|
+
verdict.investigationSteps = investigation.steps;
|
|
553
|
+
}
|
|
554
|
+
// Stage 3: Respond / 階段 3: 回應
|
|
555
|
+
const response = await this.respondAgent.respond(verdict);
|
|
556
|
+
if (response.action !== 'log_only') {
|
|
557
|
+
this.actionsExecuted++;
|
|
558
|
+
}
|
|
559
|
+
// Send notifications if needed / 需要時發送通知
|
|
560
|
+
if (response.action === 'notify' ||
|
|
561
|
+
verdict.confidence >= this.config.actionPolicy.notifyAndWait) {
|
|
562
|
+
await sendNotifications(this.config.notifications, verdict, event.description);
|
|
563
|
+
}
|
|
564
|
+
// Stage 4: Report / 階段 4: 報告
|
|
565
|
+
const { updatedBaseline, anonymizedData } = this.reportAgent.report(event, verdict, response, this.baseline);
|
|
566
|
+
this.baseline = updatedBaseline;
|
|
567
|
+
// Report to Manager if in agent mode / 在 Agent 模式下回報給 Manager
|
|
568
|
+
if (this.agentClient?.isRegistered()) {
|
|
569
|
+
this.agentClient
|
|
570
|
+
.reportEvent({
|
|
571
|
+
event,
|
|
572
|
+
verdict: {
|
|
573
|
+
conclusion: verdict.conclusion,
|
|
574
|
+
confidence: verdict.confidence,
|
|
575
|
+
action: response.action,
|
|
576
|
+
},
|
|
577
|
+
})
|
|
578
|
+
.catch((err) => {
|
|
579
|
+
logger.warn(`Agent event report failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
// Upload to threat cloud / 上傳至威脅雲
|
|
583
|
+
if (anonymizedData) {
|
|
584
|
+
await this.threatCloud.upload(anonymizedData);
|
|
585
|
+
this.threatCloudUploaded++;
|
|
586
|
+
}
|
|
587
|
+
// Notify event callback (for CLI quiet mode display)
|
|
588
|
+
this.eventCallback?.('threat', {
|
|
589
|
+
category: event.category,
|
|
590
|
+
description: event.description,
|
|
591
|
+
conclusion: verdict.conclusion,
|
|
592
|
+
confidence: verdict.confidence,
|
|
593
|
+
action: response.action,
|
|
594
|
+
sourceIP: event.metadata?.['sourceIP'] ??
|
|
595
|
+
event.metadata?.['remoteAddress'] ??
|
|
596
|
+
'unknown',
|
|
597
|
+
});
|
|
598
|
+
// Update dashboard / 更新儀表板
|
|
599
|
+
if (this.dashboard) {
|
|
600
|
+
this.dashboard.addVerdict(verdict);
|
|
601
|
+
this.dashboard.pushEvent({
|
|
602
|
+
type: 'new_event',
|
|
603
|
+
data: { event: event.id, verdict: verdict.conclusion, confidence: verdict.confidence },
|
|
604
|
+
timestamp: new Date().toISOString(),
|
|
605
|
+
});
|
|
606
|
+
// Update threat map if we have IP / 如有 IP 則更新威脅地圖
|
|
607
|
+
const sourceIP = event.metadata?.['sourceIP'] ?? event.metadata?.['remoteAddress'];
|
|
608
|
+
if (sourceIP && verdict.conclusion !== 'benign') {
|
|
609
|
+
this.dashboard.addThreatMapEntry({
|
|
610
|
+
sourceIP,
|
|
611
|
+
attackType: event.category,
|
|
612
|
+
count: 1,
|
|
613
|
+
lastSeen: new Date().toISOString(),
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// Save baseline periodically / 定期儲存基線
|
|
618
|
+
if (this.eventsProcessed % 100 === 0) {
|
|
619
|
+
saveBaseline(this.baselinePath, this.baseline);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
624
|
+
logger.error(`Event processing failed: ${msg} / 事件處理失敗: ${msg}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Check if learning period should transition to protection
|
|
629
|
+
* 檢查學習期是否應轉換為防護模式
|
|
630
|
+
*/
|
|
631
|
+
checkLearningTransition() {
|
|
632
|
+
if (this.mode !== 'learning')
|
|
633
|
+
return;
|
|
634
|
+
if (isLearningComplete(this.baseline, this.config.learningDays)) {
|
|
635
|
+
logger.info('Learning period complete, switching to protection / 學習期完成,切換至防護模式');
|
|
636
|
+
this.baseline = switchToProtectionMode(this.baseline);
|
|
637
|
+
this.mode = 'protection';
|
|
638
|
+
this.respondAgent.setMode('protection');
|
|
639
|
+
this.reportAgent.setMode('protection');
|
|
640
|
+
saveBaseline(this.baselinePath, this.baseline);
|
|
641
|
+
if (this.dashboard) {
|
|
642
|
+
this.dashboard.updateStatus({ mode: 'protection' });
|
|
643
|
+
this.dashboard.pushEvent({
|
|
644
|
+
type: 'learning_progress',
|
|
645
|
+
data: { progress: 100, mode: 'protection' },
|
|
646
|
+
timestamp: new Date().toISOString(),
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Periodic Threat Cloud sync: re-fetch rules and blocklist
|
|
653
|
+
* 定期同步 Threat Cloud:重新取得規則和封鎖清單
|
|
654
|
+
*/
|
|
655
|
+
async syncThreatCloud() {
|
|
656
|
+
try {
|
|
657
|
+
// Refresh rules / 更新規則
|
|
658
|
+
const cloudRules = await this.threatCloud.fetchRules();
|
|
659
|
+
let newRules = 0;
|
|
660
|
+
for (const rule of cloudRules) {
|
|
661
|
+
try {
|
|
662
|
+
const parsed = JSON.parse(rule.ruleContent);
|
|
663
|
+
if (parsed.id && parsed.title && parsed.detection) {
|
|
664
|
+
this.ruleEngine.addRule(parsed);
|
|
665
|
+
newRules++;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
// skip invalid
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Refresh blocklist / 更新封鎖清單
|
|
673
|
+
const ips = await this.threatCloud.fetchBlocklist();
|
|
674
|
+
const added = ips.length > 0 ? this.feedManager.addExternalIPs(ips, 'threat_cloud_blocklist', 85) : 0;
|
|
675
|
+
logger.info(`Threat Cloud sync: ${newRules} rules, ${added} blocklist IPs / ` +
|
|
676
|
+
`Threat Cloud 同步: ${newRules} 條規則, ${added} 個封鎖 IP`);
|
|
677
|
+
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
logger.warn(`Threat Cloud sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Update dashboard with current status / 更新儀表板狀態
|
|
684
|
+
*/
|
|
685
|
+
updateDashboardStatus() {
|
|
686
|
+
if (!this.dashboard)
|
|
687
|
+
return;
|
|
688
|
+
const memUsage = process.memoryUsage();
|
|
689
|
+
this.dashboard.updateStatus({
|
|
690
|
+
mode: this.mode,
|
|
691
|
+
uptime: Date.now() - this.startTime,
|
|
692
|
+
eventsProcessed: this.eventsProcessed,
|
|
693
|
+
threatsDetected: this.threatsDetected,
|
|
694
|
+
actionsExecuted: this.actionsExecuted,
|
|
695
|
+
learningProgress: getLearningProgress(this.baseline, this.config.learningDays),
|
|
696
|
+
baselineConfidence: this.baseline.confidenceLevel,
|
|
697
|
+
memoryUsageMB: Math.round((memUsage.heapUsed / 1024 / 1024) * 10) / 10,
|
|
698
|
+
cpuPercent: 0, // CPU tracking would need os.cpus()
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Register event callback for external consumers (e.g. CLI quiet mode).
|
|
703
|
+
* 註冊事件回調給外部消費者(如 CLI 安靜模式)。
|
|
704
|
+
*/
|
|
705
|
+
setEventCallback(cb) {
|
|
706
|
+
this.eventCallback = cb;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Get current engine status / 取得引擎狀態
|
|
710
|
+
*/
|
|
711
|
+
getStatus() {
|
|
712
|
+
const license = validateLicense(this.config.licenseKey);
|
|
713
|
+
const memUsage = process.memoryUsage();
|
|
714
|
+
return {
|
|
715
|
+
running: this.running,
|
|
716
|
+
mode: this.mode,
|
|
717
|
+
uptime: this.running ? Date.now() - this.startTime : 0,
|
|
718
|
+
eventsProcessed: this.eventsProcessed,
|
|
719
|
+
threatsDetected: this.threatsDetected,
|
|
720
|
+
actionsExecuted: this.actionsExecuted,
|
|
721
|
+
learningProgress: getLearningProgress(this.baseline, this.config.learningDays),
|
|
722
|
+
baselineConfidence: this.baseline.confidenceLevel,
|
|
723
|
+
memoryUsageMB: Math.round((memUsage.heapUsed / 1024 / 1024) * 10) / 10,
|
|
724
|
+
licenseTier: license.tier,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Get the current baseline / 取得當前基線
|
|
729
|
+
*/
|
|
730
|
+
getBaseline() {
|
|
731
|
+
return this.baseline;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Check if engine is running / 檢查引擎是否執行中
|
|
735
|
+
*/
|
|
736
|
+
isRunning() {
|
|
737
|
+
return this.running;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
//# sourceMappingURL=guard-engine.js.map
|