defense-mcp-server 0.6.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/CHANGELOG.md +471 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/build/core/auto-installer.d.ts +102 -0
- package/build/core/auto-installer.d.ts.map +1 -0
- package/build/core/auto-installer.js +833 -0
- package/build/core/backup-manager.d.ts +63 -0
- package/build/core/backup-manager.d.ts.map +1 -0
- package/build/core/backup-manager.js +189 -0
- package/build/core/changelog.d.ts +75 -0
- package/build/core/changelog.d.ts.map +1 -0
- package/build/core/changelog.js +123 -0
- package/build/core/command-allowlist.d.ts +129 -0
- package/build/core/command-allowlist.d.ts.map +1 -0
- package/build/core/command-allowlist.js +849 -0
- package/build/core/config.d.ts +79 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +193 -0
- package/build/core/dependency-validator.d.ts +106 -0
- package/build/core/dependency-validator.d.ts.map +1 -0
- package/build/core/dependency-validator.js +405 -0
- package/build/core/distro-adapter.d.ts +177 -0
- package/build/core/distro-adapter.d.ts.map +1 -0
- package/build/core/distro-adapter.js +481 -0
- package/build/core/distro.d.ts +68 -0
- package/build/core/distro.d.ts.map +1 -0
- package/build/core/distro.js +457 -0
- package/build/core/encrypted-state.d.ts +76 -0
- package/build/core/encrypted-state.d.ts.map +1 -0
- package/build/core/encrypted-state.js +209 -0
- package/build/core/executor.d.ts +56 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +350 -0
- package/build/core/installer.d.ts +92 -0
- package/build/core/installer.d.ts.map +1 -0
- package/build/core/installer.js +1072 -0
- package/build/core/logger.d.ts +102 -0
- package/build/core/logger.d.ts.map +1 -0
- package/build/core/logger.js +132 -0
- package/build/core/parsers.d.ts +151 -0
- package/build/core/parsers.d.ts.map +1 -0
- package/build/core/parsers.js +479 -0
- package/build/core/policy-engine.d.ts +170 -0
- package/build/core/policy-engine.d.ts.map +1 -0
- package/build/core/policy-engine.js +656 -0
- package/build/core/preflight.d.ts +157 -0
- package/build/core/preflight.d.ts.map +1 -0
- package/build/core/preflight.js +638 -0
- package/build/core/privilege-manager.d.ts +108 -0
- package/build/core/privilege-manager.d.ts.map +1 -0
- package/build/core/privilege-manager.js +363 -0
- package/build/core/rate-limiter.d.ts +67 -0
- package/build/core/rate-limiter.d.ts.map +1 -0
- package/build/core/rate-limiter.js +129 -0
- package/build/core/rollback.d.ts +73 -0
- package/build/core/rollback.d.ts.map +1 -0
- package/build/core/rollback.js +278 -0
- package/build/core/safeguards.d.ts +58 -0
- package/build/core/safeguards.d.ts.map +1 -0
- package/build/core/safeguards.js +448 -0
- package/build/core/sanitizer.d.ts +118 -0
- package/build/core/sanitizer.d.ts.map +1 -0
- package/build/core/sanitizer.js +459 -0
- package/build/core/secure-fs.d.ts +67 -0
- package/build/core/secure-fs.d.ts.map +1 -0
- package/build/core/secure-fs.js +143 -0
- package/build/core/spawn-safe.d.ts +55 -0
- package/build/core/spawn-safe.d.ts.map +1 -0
- package/build/core/spawn-safe.js +146 -0
- package/build/core/sudo-guard.d.ts +145 -0
- package/build/core/sudo-guard.d.ts.map +1 -0
- package/build/core/sudo-guard.js +349 -0
- package/build/core/sudo-session.d.ts +100 -0
- package/build/core/sudo-session.d.ts.map +1 -0
- package/build/core/sudo-session.js +319 -0
- package/build/core/tool-dependencies.d.ts +61 -0
- package/build/core/tool-dependencies.d.ts.map +1 -0
- package/build/core/tool-dependencies.js +571 -0
- package/build/core/tool-registry.d.ts +111 -0
- package/build/core/tool-registry.d.ts.map +1 -0
- package/build/core/tool-registry.js +656 -0
- package/build/core/tool-wrapper.d.ts +73 -0
- package/build/core/tool-wrapper.d.ts.map +1 -0
- package/build/core/tool-wrapper.js +296 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +247 -0
- package/build/tools/access-control.d.ts +9 -0
- package/build/tools/access-control.d.ts.map +1 -0
- package/build/tools/access-control.js +1818 -0
- package/build/tools/api-security.d.ts +12 -0
- package/build/tools/api-security.d.ts.map +1 -0
- package/build/tools/api-security.js +901 -0
- package/build/tools/app-hardening.d.ts +11 -0
- package/build/tools/app-hardening.d.ts.map +1 -0
- package/build/tools/app-hardening.js +768 -0
- package/build/tools/backup.d.ts +8 -0
- package/build/tools/backup.d.ts.map +1 -0
- package/build/tools/backup.js +381 -0
- package/build/tools/cloud-security.d.ts +17 -0
- package/build/tools/cloud-security.d.ts.map +1 -0
- package/build/tools/cloud-security.js +739 -0
- package/build/tools/compliance.d.ts +10 -0
- package/build/tools/compliance.d.ts.map +1 -0
- package/build/tools/compliance.js +1225 -0
- package/build/tools/container-security.d.ts +14 -0
- package/build/tools/container-security.d.ts.map +1 -0
- package/build/tools/container-security.js +788 -0
- package/build/tools/deception.d.ts +13 -0
- package/build/tools/deception.d.ts.map +1 -0
- package/build/tools/deception.js +763 -0
- package/build/tools/dns-security.d.ts +93 -0
- package/build/tools/dns-security.d.ts.map +1 -0
- package/build/tools/dns-security.js +745 -0
- package/build/tools/drift-detection.d.ts +8 -0
- package/build/tools/drift-detection.d.ts.map +1 -0
- package/build/tools/drift-detection.js +326 -0
- package/build/tools/ebpf-security.d.ts +15 -0
- package/build/tools/ebpf-security.d.ts.map +1 -0
- package/build/tools/ebpf-security.js +294 -0
- package/build/tools/encryption.d.ts +9 -0
- package/build/tools/encryption.d.ts.map +1 -0
- package/build/tools/encryption.js +1667 -0
- package/build/tools/firewall.d.ts +9 -0
- package/build/tools/firewall.d.ts.map +1 -0
- package/build/tools/firewall.js +1398 -0
- package/build/tools/hardening.d.ts +10 -0
- package/build/tools/hardening.d.ts.map +1 -0
- package/build/tools/hardening.js +2654 -0
- package/build/tools/ids.d.ts +9 -0
- package/build/tools/ids.d.ts.map +1 -0
- package/build/tools/ids.js +624 -0
- package/build/tools/incident-response.d.ts +10 -0
- package/build/tools/incident-response.d.ts.map +1 -0
- package/build/tools/incident-response.js +1180 -0
- package/build/tools/logging.d.ts +12 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +454 -0
- package/build/tools/malware.d.ts +10 -0
- package/build/tools/malware.d.ts.map +1 -0
- package/build/tools/malware.js +532 -0
- package/build/tools/meta.d.ts +11 -0
- package/build/tools/meta.d.ts.map +1 -0
- package/build/tools/meta.js +2278 -0
- package/build/tools/network-defense.d.ts +12 -0
- package/build/tools/network-defense.d.ts.map +1 -0
- package/build/tools/network-defense.js +760 -0
- package/build/tools/patch-management.d.ts +3 -0
- package/build/tools/patch-management.d.ts.map +1 -0
- package/build/tools/patch-management.js +708 -0
- package/build/tools/process-security.d.ts +12 -0
- package/build/tools/process-security.d.ts.map +1 -0
- package/build/tools/process-security.js +784 -0
- package/build/tools/reporting.d.ts +11 -0
- package/build/tools/reporting.d.ts.map +1 -0
- package/build/tools/reporting.js +559 -0
- package/build/tools/secrets.d.ts +9 -0
- package/build/tools/secrets.d.ts.map +1 -0
- package/build/tools/secrets.js +596 -0
- package/build/tools/siem-integration.d.ts +18 -0
- package/build/tools/siem-integration.d.ts.map +1 -0
- package/build/tools/siem-integration.js +754 -0
- package/build/tools/sudo-management.d.ts +18 -0
- package/build/tools/sudo-management.d.ts.map +1 -0
- package/build/tools/sudo-management.js +737 -0
- package/build/tools/supply-chain-security.d.ts +8 -0
- package/build/tools/supply-chain-security.d.ts.map +1 -0
- package/build/tools/supply-chain-security.js +256 -0
- package/build/tools/threat-intel.d.ts +22 -0
- package/build/tools/threat-intel.d.ts.map +1 -0
- package/build/tools/threat-intel.js +749 -0
- package/build/tools/vulnerability-management.d.ts +11 -0
- package/build/tools/vulnerability-management.d.ts.map +1 -0
- package/build/tools/vulnerability-management.js +667 -0
- package/build/tools/waf.d.ts +12 -0
- package/build/tools/waf.d.ts.map +1 -0
- package/build/tools/waf.js +843 -0
- package/build/tools/wireless-security.d.ts +19 -0
- package/build/tools/wireless-security.d.ts.map +1 -0
- package/build/tools/wireless-security.js +826 -0
- package/build/tools/zero-trust-network.d.ts +8 -0
- package/build/tools/zero-trust-network.d.ts.map +1 -0
- package/build/tools/zero-trust-network.js +367 -0
- package/docs/SAFEGUARDS.md +518 -0
- package/docs/TOOLS-REFERENCE.md +665 -0
- package/package.json +87 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output parsing utilities for defensive security tool output.
|
|
3
|
+
* Converts raw command output into structured data for MCP responses.
|
|
4
|
+
*/
|
|
5
|
+
// ─── Generic Parsers ─────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* Parses key:value pair output into a Record.
|
|
8
|
+
* Lines without the separator are skipped.
|
|
9
|
+
*/
|
|
10
|
+
export function parseKeyValue(output, separator = ":") {
|
|
11
|
+
const result = {};
|
|
12
|
+
for (const line of output.split("\n")) {
|
|
13
|
+
const idx = line.indexOf(separator);
|
|
14
|
+
if (idx === -1)
|
|
15
|
+
continue;
|
|
16
|
+
const key = line.substring(0, idx).trim();
|
|
17
|
+
const value = line.substring(idx + separator.length).trim();
|
|
18
|
+
if (key.length > 0) {
|
|
19
|
+
result[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses whitespace-delimited table output into an array of Records.
|
|
26
|
+
* First non-empty line is treated as the header row.
|
|
27
|
+
*/
|
|
28
|
+
export function parseTable(output) {
|
|
29
|
+
const lines = output
|
|
30
|
+
.split("\n")
|
|
31
|
+
.map((l) => l.trim())
|
|
32
|
+
.filter((l) => l.length > 0);
|
|
33
|
+
if (lines.length < 2)
|
|
34
|
+
return [];
|
|
35
|
+
const headers = lines[0].split(/\s+/).map((h) => h.toLowerCase());
|
|
36
|
+
const rows = [];
|
|
37
|
+
for (let i = 1; i < lines.length; i++) {
|
|
38
|
+
const values = lines[i].split(/\s+/);
|
|
39
|
+
const row = {};
|
|
40
|
+
for (let j = 0; j < headers.length; j++) {
|
|
41
|
+
// Last column gets remainder of line to handle values with spaces
|
|
42
|
+
if (j === headers.length - 1) {
|
|
43
|
+
row[headers[j]] = values.slice(j).join(" ");
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
row[headers[j]] = values[j] ?? "";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
rows.push(row);
|
|
50
|
+
}
|
|
51
|
+
return rows;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Safely parses JSON text. Returns null on parse failure.
|
|
55
|
+
*/
|
|
56
|
+
export function parseJsonSafe(text) {
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(text);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Formats any data into MCP text content.
|
|
66
|
+
* Objects are JSON-stringified with indentation.
|
|
67
|
+
*/
|
|
68
|
+
export function formatToolOutput(data) {
|
|
69
|
+
if (typeof data === "string") {
|
|
70
|
+
return { type: "text", text: data };
|
|
71
|
+
}
|
|
72
|
+
return { type: "text", text: JSON.stringify(data, null, 2) };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates a simple MCP text content object.
|
|
76
|
+
*/
|
|
77
|
+
export function createTextContent(text) {
|
|
78
|
+
return { type: "text", text };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Creates an MCP text content object with an error prefix.
|
|
82
|
+
*/
|
|
83
|
+
export function createErrorContent(msg) {
|
|
84
|
+
return { type: "text", text: `Error: ${msg}` };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parses `iptables -L -n -v` output into structured rules.
|
|
88
|
+
*/
|
|
89
|
+
export function parseIptablesOutput(output) {
|
|
90
|
+
const rules = [];
|
|
91
|
+
let currentChain = "";
|
|
92
|
+
let currentPolicy = "";
|
|
93
|
+
for (const line of output.split("\n")) {
|
|
94
|
+
const trimmed = line.trim();
|
|
95
|
+
if (!trimmed)
|
|
96
|
+
continue;
|
|
97
|
+
// Chain header: "Chain INPUT (policy ACCEPT 0 packets, 0 bytes)"
|
|
98
|
+
const chainMatch = trimmed.match(/^Chain\s+(\S+)\s+\(policy\s+(\S+).*\)/);
|
|
99
|
+
if (chainMatch) {
|
|
100
|
+
currentChain = chainMatch[1];
|
|
101
|
+
currentPolicy = chainMatch[2];
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// Chain header without policy: "Chain DOCKER (1 references)"
|
|
105
|
+
const chainRefMatch = trimmed.match(/^Chain\s+(\S+)\s+\(/);
|
|
106
|
+
if (chainRefMatch) {
|
|
107
|
+
currentChain = chainRefMatch[1];
|
|
108
|
+
currentPolicy = "";
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// Skip table header
|
|
112
|
+
if (trimmed.startsWith("pkts") || trimmed.startsWith("num"))
|
|
113
|
+
continue;
|
|
114
|
+
// Rule line: "pkts bytes target prot opt in out source destination extra..."
|
|
115
|
+
const parts = trimmed.split(/\s+/);
|
|
116
|
+
if (parts.length >= 9) {
|
|
117
|
+
rules.push({
|
|
118
|
+
chain: currentChain,
|
|
119
|
+
policy: currentPolicy || undefined,
|
|
120
|
+
packets: parts[0],
|
|
121
|
+
bytes: parts[1],
|
|
122
|
+
target: parts[2],
|
|
123
|
+
protocol: parts[3],
|
|
124
|
+
opt: parts[4],
|
|
125
|
+
in: parts[5],
|
|
126
|
+
out: parts[6],
|
|
127
|
+
source: parts[7],
|
|
128
|
+
destination: parts[8],
|
|
129
|
+
extra: parts.slice(9).join(" "),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return rules;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parses `nft list ruleset` output into structured sections.
|
|
137
|
+
*/
|
|
138
|
+
export function parseNftOutput(output) {
|
|
139
|
+
const result = {};
|
|
140
|
+
let currentTable = "";
|
|
141
|
+
for (const line of output.split("\n")) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (!trimmed)
|
|
144
|
+
continue;
|
|
145
|
+
const tableMatch = trimmed.match(/^table\s+(\S+\s+\S+)\s*\{/);
|
|
146
|
+
if (tableMatch) {
|
|
147
|
+
currentTable = tableMatch[1];
|
|
148
|
+
result[currentTable] = [];
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (currentTable && trimmed !== "}") {
|
|
152
|
+
result[currentTable]?.push(trimmed);
|
|
153
|
+
}
|
|
154
|
+
if (trimmed === "}" && currentTable) {
|
|
155
|
+
currentTable = "";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Parses `sysctl -a` output into structured entries.
|
|
162
|
+
*/
|
|
163
|
+
export function parseSysctlOutput(output) {
|
|
164
|
+
const entries = [];
|
|
165
|
+
for (const line of output.split("\n")) {
|
|
166
|
+
const idx = line.indexOf("=");
|
|
167
|
+
if (idx === -1)
|
|
168
|
+
continue;
|
|
169
|
+
const key = line.substring(0, idx).trim();
|
|
170
|
+
const value = line.substring(idx + 1).trim();
|
|
171
|
+
if (key.length > 0) {
|
|
172
|
+
entries.push({ key, value });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return entries;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Parses `ausearch` output into structured audit entries.
|
|
179
|
+
*/
|
|
180
|
+
export function parseAuditdOutput(output) {
|
|
181
|
+
const entries = [];
|
|
182
|
+
let current = null;
|
|
183
|
+
for (const line of output.split("\n")) {
|
|
184
|
+
const trimmed = line.trim();
|
|
185
|
+
if (!trimmed || trimmed.startsWith("----") || trimmed.startsWith("time->"))
|
|
186
|
+
continue;
|
|
187
|
+
// type=SYSCALL msg=audit(1234567890.123:456): ...
|
|
188
|
+
const typeMatch = trimmed.match(/^type=(\S+)\s+msg=audit\(([^)]+)\):\s*(.*)/);
|
|
189
|
+
if (typeMatch) {
|
|
190
|
+
current = {
|
|
191
|
+
type: typeMatch[1],
|
|
192
|
+
timestamp: typeMatch[2],
|
|
193
|
+
fields: {},
|
|
194
|
+
};
|
|
195
|
+
// Parse key=value pairs from rest of line
|
|
196
|
+
const rest = typeMatch[3];
|
|
197
|
+
const kvRe = /(\w+)=("[^"]*"|\S+)/g;
|
|
198
|
+
let match;
|
|
199
|
+
while ((match = kvRe.exec(rest)) !== null) {
|
|
200
|
+
current.fields[match[1]] = match[2].replace(/^"|"$/g, "");
|
|
201
|
+
}
|
|
202
|
+
entries.push(current);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return entries;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Parses Lynis audit output for findings/warnings/suggestions.
|
|
209
|
+
*/
|
|
210
|
+
export function parseLynisOutput(output) {
|
|
211
|
+
const findings = [];
|
|
212
|
+
for (const line of output.split("\n")) {
|
|
213
|
+
const trimmed = line.trim();
|
|
214
|
+
// Warning: [TEST-ID] Description
|
|
215
|
+
const warningMatch = trimmed.match(/^\s*Warning:\s*\[(\S+)\]\s*(.*)/);
|
|
216
|
+
if (warningMatch) {
|
|
217
|
+
findings.push({
|
|
218
|
+
severity: "warning",
|
|
219
|
+
testId: warningMatch[1],
|
|
220
|
+
description: warningMatch[2],
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// Suggestion: [TEST-ID] Description
|
|
225
|
+
const suggestionMatch = trimmed.match(/^\s*Suggestion:\s*\[(\S+)\]\s*(.*)/);
|
|
226
|
+
if (suggestionMatch) {
|
|
227
|
+
findings.push({
|
|
228
|
+
severity: "suggestion",
|
|
229
|
+
testId: suggestionMatch[1],
|
|
230
|
+
description: suggestionMatch[2],
|
|
231
|
+
});
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
// * Finding [TEST-ID]
|
|
235
|
+
const findingMatch = trimmed.match(/^\s*\*\s*Finding\s*\[(\S+)\]\s*(.*)/);
|
|
236
|
+
if (findingMatch) {
|
|
237
|
+
findings.push({
|
|
238
|
+
severity: "finding",
|
|
239
|
+
testId: findingMatch[1],
|
|
240
|
+
description: findingMatch[2],
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return findings;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Parses OpenSCAP text/XML results output.
|
|
248
|
+
* Handles the common text report format.
|
|
249
|
+
*/
|
|
250
|
+
export function parseOscapOutput(output) {
|
|
251
|
+
const results = [];
|
|
252
|
+
// Match rule results from oscap text output
|
|
253
|
+
// Title
|
|
254
|
+
// Rule ID: xccdf_...
|
|
255
|
+
// Result: pass/fail/notapplicable
|
|
256
|
+
// Severity: low/medium/high
|
|
257
|
+
let currentTitle = "";
|
|
258
|
+
let currentRule = {};
|
|
259
|
+
for (const line of output.split("\n")) {
|
|
260
|
+
const trimmed = line.trim();
|
|
261
|
+
const titleMatch = trimmed.match(/^Title\s*:\s*(.*)/);
|
|
262
|
+
if (titleMatch) {
|
|
263
|
+
currentTitle = titleMatch[1];
|
|
264
|
+
currentRule = { title: currentTitle };
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const ruleMatch = trimmed.match(/^Rule\s*:\s*(.*)/);
|
|
268
|
+
if (ruleMatch) {
|
|
269
|
+
currentRule.ruleId = ruleMatch[1];
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const resultMatch = trimmed.match(/^Result\s*:\s*(.*)/);
|
|
273
|
+
if (resultMatch) {
|
|
274
|
+
currentRule.result = resultMatch[1];
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const sevMatch = trimmed.match(/^Severity\s*:\s*(.*)/);
|
|
278
|
+
if (sevMatch) {
|
|
279
|
+
currentRule.severity = sevMatch[1];
|
|
280
|
+
// We have all fields, push the result
|
|
281
|
+
if (currentRule.ruleId && currentRule.result) {
|
|
282
|
+
results.push({
|
|
283
|
+
ruleId: currentRule.ruleId,
|
|
284
|
+
result: currentRule.result,
|
|
285
|
+
severity: currentRule.severity ?? "unknown",
|
|
286
|
+
title: currentRule.title ?? "",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
currentRule = {};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return results;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Parses `clamscan` output into structured results.
|
|
296
|
+
*/
|
|
297
|
+
export function parseClamavOutput(output) {
|
|
298
|
+
const results = [];
|
|
299
|
+
for (const line of output.split("\n")) {
|
|
300
|
+
const trimmed = line.trim();
|
|
301
|
+
if (!trimmed)
|
|
302
|
+
continue;
|
|
303
|
+
// /path/to/file: OK
|
|
304
|
+
if (trimmed.endsWith(": OK")) {
|
|
305
|
+
results.push({
|
|
306
|
+
file: trimmed.slice(0, -4),
|
|
307
|
+
status: "OK",
|
|
308
|
+
});
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
// /path/to/file: VirusName FOUND
|
|
312
|
+
const foundMatch = trimmed.match(/^(.+?):\s+(.+?)\s+FOUND$/);
|
|
313
|
+
if (foundMatch) {
|
|
314
|
+
results.push({
|
|
315
|
+
file: foundMatch[1],
|
|
316
|
+
status: "FOUND",
|
|
317
|
+
virus: foundMatch[2],
|
|
318
|
+
});
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
// /path/to/file: Error message ERROR
|
|
322
|
+
const errorMatch = trimmed.match(/^(.+?):\s+.*ERROR$/);
|
|
323
|
+
if (errorMatch) {
|
|
324
|
+
results.push({
|
|
325
|
+
file: errorMatch[1],
|
|
326
|
+
status: "ERROR",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return results;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Parses `ss -tulnp` output into structured entries.
|
|
334
|
+
*/
|
|
335
|
+
export function parseSsOutput(output) {
|
|
336
|
+
const entries = [];
|
|
337
|
+
const lines = output.split("\n");
|
|
338
|
+
// Skip header
|
|
339
|
+
for (let i = 1; i < lines.length; i++) {
|
|
340
|
+
const line = lines[i].trim();
|
|
341
|
+
if (!line)
|
|
342
|
+
continue;
|
|
343
|
+
// ss -tulnp columns:
|
|
344
|
+
// 0: Netid 1: State 2: Recv-Q 3: Send-Q 4: Local Address:Port 5: Peer Address:Port 6+: Process
|
|
345
|
+
const parts = line.split(/\s+/);
|
|
346
|
+
if (parts.length >= 6) {
|
|
347
|
+
entries.push({
|
|
348
|
+
state: parts[1],
|
|
349
|
+
recv: parts[2],
|
|
350
|
+
send: parts[3],
|
|
351
|
+
local: parts[4],
|
|
352
|
+
peer: parts[5],
|
|
353
|
+
process: parts.slice(6).join(" "),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return entries;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Parses `fail2ban-client status` output.
|
|
361
|
+
*/
|
|
362
|
+
export function parseFail2banOutput(output) {
|
|
363
|
+
const jails = [];
|
|
364
|
+
let current = null;
|
|
365
|
+
for (const line of output.split("\n")) {
|
|
366
|
+
const trimmed = line.trim();
|
|
367
|
+
// Jail name from status output
|
|
368
|
+
const jailMatch = trimmed.match(/^Status for the jail:\s*(\S+)/);
|
|
369
|
+
if (jailMatch) {
|
|
370
|
+
current = { name: jailMatch[1], bannedIPs: [] };
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (!current)
|
|
374
|
+
continue;
|
|
375
|
+
const kvMatch = trimmed.match(/^\|-\s*(\S.*?):\s*(.*)/);
|
|
376
|
+
if (kvMatch) {
|
|
377
|
+
const key = kvMatch[1].trim().toLowerCase();
|
|
378
|
+
const value = kvMatch[2].trim();
|
|
379
|
+
if (key.includes("currently failed")) {
|
|
380
|
+
current.currentlyFailed = parseInt(value, 10) || 0;
|
|
381
|
+
}
|
|
382
|
+
else if (key.includes("total failed")) {
|
|
383
|
+
current.totalFailed = parseInt(value, 10) || 0;
|
|
384
|
+
}
|
|
385
|
+
else if (key.includes("currently banned")) {
|
|
386
|
+
current.currentlyBanned = parseInt(value, 10) || 0;
|
|
387
|
+
}
|
|
388
|
+
else if (key.includes("total banned")) {
|
|
389
|
+
current.totalBanned = parseInt(value, 10) || 0;
|
|
390
|
+
}
|
|
391
|
+
else if (key.includes("banned ip")) {
|
|
392
|
+
current.bannedIPs = value
|
|
393
|
+
.split(/\s+/)
|
|
394
|
+
.filter((ip) => ip.length > 0);
|
|
395
|
+
}
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const leafMatch = trimmed.match(/^`-\s*(\S.*?):\s*(.*)/);
|
|
399
|
+
if (leafMatch) {
|
|
400
|
+
const key = leafMatch[1].trim().toLowerCase();
|
|
401
|
+
const value = leafMatch[2].trim();
|
|
402
|
+
if (key.includes("currently failed")) {
|
|
403
|
+
current.currentlyFailed = parseInt(value, 10) || 0;
|
|
404
|
+
}
|
|
405
|
+
else if (key.includes("total failed")) {
|
|
406
|
+
current.totalFailed = parseInt(value, 10) || 0;
|
|
407
|
+
}
|
|
408
|
+
else if (key.includes("currently banned")) {
|
|
409
|
+
current.currentlyBanned = parseInt(value, 10) || 0;
|
|
410
|
+
}
|
|
411
|
+
else if (key.includes("total banned")) {
|
|
412
|
+
current.totalBanned = parseInt(value, 10) || 0;
|
|
413
|
+
}
|
|
414
|
+
else if (key.includes("banned ip")) {
|
|
415
|
+
current.bannedIPs = value
|
|
416
|
+
.split(/\s+/)
|
|
417
|
+
.filter((ip) => ip.length > 0);
|
|
418
|
+
// Last field, push the jail
|
|
419
|
+
jails.push({
|
|
420
|
+
name: current.name ?? "unknown",
|
|
421
|
+
status: current.status ?? "active",
|
|
422
|
+
currentlyFailed: current.currentlyFailed ?? 0,
|
|
423
|
+
totalFailed: current.totalFailed ?? 0,
|
|
424
|
+
currentlyBanned: current.currentlyBanned ?? 0,
|
|
425
|
+
totalBanned: current.totalBanned ?? 0,
|
|
426
|
+
bannedIPs: current.bannedIPs ?? [],
|
|
427
|
+
});
|
|
428
|
+
current = null;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Push any remaining jail
|
|
433
|
+
if (current?.name) {
|
|
434
|
+
jails.push({
|
|
435
|
+
name: current.name,
|
|
436
|
+
status: current.status ?? "active",
|
|
437
|
+
currentlyFailed: current.currentlyFailed ?? 0,
|
|
438
|
+
totalFailed: current.totalFailed ?? 0,
|
|
439
|
+
currentlyBanned: current.currentlyBanned ?? 0,
|
|
440
|
+
totalBanned: current.totalBanned ?? 0,
|
|
441
|
+
bannedIPs: current.bannedIPs ?? [],
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return jails;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Parses `systemctl list-units` output into structured entries.
|
|
448
|
+
*/
|
|
449
|
+
export function parseSystemctlOutput(output) {
|
|
450
|
+
const units = [];
|
|
451
|
+
const lines = output.split("\n");
|
|
452
|
+
for (const line of lines) {
|
|
453
|
+
const trimmed = line.trim();
|
|
454
|
+
if (!trimmed)
|
|
455
|
+
continue;
|
|
456
|
+
// Skip header lines and legend
|
|
457
|
+
if (trimmed.startsWith("UNIT") ||
|
|
458
|
+
trimmed.startsWith("LOAD") ||
|
|
459
|
+
trimmed.startsWith("To show") ||
|
|
460
|
+
trimmed.includes("loaded units listed") ||
|
|
461
|
+
trimmed.startsWith("LEGEND")) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
// "● unit.service loaded active running Description text"
|
|
465
|
+
// or "unit.service loaded active running Description text"
|
|
466
|
+
const cleanLine = trimmed.replace(/^[●○]\s*/, "");
|
|
467
|
+
const parts = cleanLine.split(/\s+/);
|
|
468
|
+
if (parts.length >= 4) {
|
|
469
|
+
units.push({
|
|
470
|
+
unit: parts[0],
|
|
471
|
+
load: parts[1],
|
|
472
|
+
active: parts[2],
|
|
473
|
+
sub: parts[3],
|
|
474
|
+
description: parts.slice(4).join(" "),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return units;
|
|
479
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Severity levels for policy rules, aligned with common security frameworks.
|
|
3
|
+
*/
|
|
4
|
+
export type PolicySeverity = "critical" | "high" | "medium" | "low" | "info";
|
|
5
|
+
/**
|
|
6
|
+
* A single compliance policy rule defining a check and optional remediation.
|
|
7
|
+
*/
|
|
8
|
+
export interface PolicyRule {
|
|
9
|
+
/** Unique rule identifier (e.g., "CIS-1.1.1") */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Human-readable title */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Detailed description of what this rule checks */
|
|
14
|
+
description: string;
|
|
15
|
+
/** Severity of non-compliance */
|
|
16
|
+
severity: PolicySeverity;
|
|
17
|
+
/** Category (e.g., "filesystem", "network", "authentication") */
|
|
18
|
+
category: string;
|
|
19
|
+
/** Command to run to check compliance (array: [command, ...args]) */
|
|
20
|
+
check: string[];
|
|
21
|
+
/** Expected output pattern (regex string or exact match) */
|
|
22
|
+
expectedOutput?: string;
|
|
23
|
+
/** Command to remediate non-compliance (array: [command, ...args]) */
|
|
24
|
+
remediation?: string[];
|
|
25
|
+
/** Reference IDs (e.g., CIS benchmark, NIST control IDs) */
|
|
26
|
+
references?: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Result of evaluating a single policy rule.
|
|
30
|
+
*/
|
|
31
|
+
export interface PolicyResult {
|
|
32
|
+
/** The rule that was evaluated */
|
|
33
|
+
rule: PolicyRule;
|
|
34
|
+
/** Whether the system passed this check */
|
|
35
|
+
passed: boolean;
|
|
36
|
+
/** Actual output from the check command */
|
|
37
|
+
actual: string;
|
|
38
|
+
/** Human-readable result message */
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A collection of policy rules forming a compliance policy set.
|
|
43
|
+
*/
|
|
44
|
+
export interface PolicySet {
|
|
45
|
+
/** Policy set name (e.g., "CIS Level 1 - Server") */
|
|
46
|
+
name: string;
|
|
47
|
+
/** Version of this policy set */
|
|
48
|
+
version: string;
|
|
49
|
+
/** Description of the policy set */
|
|
50
|
+
description: string;
|
|
51
|
+
/** Array of policy rules */
|
|
52
|
+
rules: PolicyRule[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Summary of a policy evaluation.
|
|
56
|
+
*/
|
|
57
|
+
export interface PolicyEvaluationSummary {
|
|
58
|
+
/** Policy set that was evaluated */
|
|
59
|
+
policyName: string;
|
|
60
|
+
/** Total number of rules */
|
|
61
|
+
totalRules: number;
|
|
62
|
+
/** Number of rules that passed */
|
|
63
|
+
passed: number;
|
|
64
|
+
/** Number of rules that failed */
|
|
65
|
+
failed: number;
|
|
66
|
+
/** Number of rules with errors */
|
|
67
|
+
errors: number;
|
|
68
|
+
/** Compliance percentage (0-100) */
|
|
69
|
+
compliancePercent: number;
|
|
70
|
+
/** Individual rule results */
|
|
71
|
+
results: PolicyResult[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validates a policy rule's check (or remediation) command array.
|
|
75
|
+
*
|
|
76
|
+
* Security controls:
|
|
77
|
+
* 1. Command (check[0]) must be in the security allowlist
|
|
78
|
+
* 2. Shell interpreters are explicitly blocked (even if allowlisted)
|
|
79
|
+
* 3. Arguments are checked for null bytes and control characters
|
|
80
|
+
*
|
|
81
|
+
* Note: Shell metacharacters (|, &, $, etc.) in arguments are NOT blocked
|
|
82
|
+
* because policy rules use execFile (no shell), making these characters
|
|
83
|
+
* harmless literal values. Policy rules legitimately need regex
|
|
84
|
+
* metacharacters as arguments to grep/awk/sed.
|
|
85
|
+
*
|
|
86
|
+
* @param check The command array [command, ...args]
|
|
87
|
+
* @param label Human-readable label for error messages (e.g., "check", "remediation")
|
|
88
|
+
* @throws {Error} If validation fails
|
|
89
|
+
*/
|
|
90
|
+
export declare function validateRuleCheck(check: string[], label?: string): void;
|
|
91
|
+
/**
|
|
92
|
+
* SECURITY (CORE-009): ReDoS (Regular Expression Denial of Service) protection.
|
|
93
|
+
*
|
|
94
|
+
* Safely tests a regex pattern against input with multiple layers of defense
|
|
95
|
+
* against catastrophic backtracking:
|
|
96
|
+
*
|
|
97
|
+
* 1. **Length limit**: Patterns longer than 200 characters are rejected to reduce
|
|
98
|
+
* the attack surface for complex regex injection.
|
|
99
|
+
* 2. **Nested quantifier detection**: Patterns like `(a+)+`, `(a*)*`, `(a+)*`
|
|
100
|
+
* are rejected because they cause exponential backtracking on non-matching
|
|
101
|
+
* input. The check uses two heuristics:
|
|
102
|
+
* - Repeated quantifiers: `a++`, `a**`, `{n,m}{` (possessive-like syntax
|
|
103
|
+
* that JavaScript doesn't support, indicating malformed patterns)
|
|
104
|
+
* - Group-level nesting: `([...]+)+` or `([...]*)*` where a quantified
|
|
105
|
+
* group is itself quantified
|
|
106
|
+
* 3. **try-catch**: Invalid regex syntax is caught and reported clearly.
|
|
107
|
+
*
|
|
108
|
+
* These checks are applied to user-supplied `expectedOutput` regex patterns
|
|
109
|
+
* in policy rules before they are compiled or executed.
|
|
110
|
+
*
|
|
111
|
+
* @param pattern The regex pattern string
|
|
112
|
+
* @param input The string to test against
|
|
113
|
+
* @returns Whether the pattern matches the input
|
|
114
|
+
* @throws {Error} If the pattern is dangerous, invalid, or too long
|
|
115
|
+
*/
|
|
116
|
+
export declare function safeRegexTest(pattern: string, input: string): boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Evaluates a single policy rule by executing its check command
|
|
119
|
+
* and comparing the output against the expected pattern.
|
|
120
|
+
*
|
|
121
|
+
* Before execution, the check command is validated against the
|
|
122
|
+
* security allowlist and shell interpreters are blocked.
|
|
123
|
+
*
|
|
124
|
+
* @param rule The policy rule to evaluate
|
|
125
|
+
* @returns The evaluation result
|
|
126
|
+
*/
|
|
127
|
+
export declare function evaluateRule(rule: PolicyRule): Promise<PolicyResult>;
|
|
128
|
+
/**
|
|
129
|
+
* Evaluates all rules in a policy set and returns a summary.
|
|
130
|
+
*
|
|
131
|
+
* @param policySet The policy set to evaluate
|
|
132
|
+
* @returns Evaluation summary with individual results
|
|
133
|
+
*/
|
|
134
|
+
export declare function evaluatePolicy(policySet: PolicySet): Promise<PolicyEvaluationSummary>;
|
|
135
|
+
/**
|
|
136
|
+
* Loads a policy set from a JSON file with strict schema validation.
|
|
137
|
+
*
|
|
138
|
+
* Validates:
|
|
139
|
+
* 1. JSON structure via Zod schema (field types, lengths, required fields)
|
|
140
|
+
* 2. All check commands against the security allowlist
|
|
141
|
+
* 3. All remediation commands against the security allowlist
|
|
142
|
+
*
|
|
143
|
+
* @param path Absolute or relative path to the policy JSON file
|
|
144
|
+
* @returns The loaded and validated policy set
|
|
145
|
+
* @throws If the file cannot be read, parsed, or fails validation
|
|
146
|
+
*/
|
|
147
|
+
export declare function loadPolicy(path: string): PolicySet;
|
|
148
|
+
/**
|
|
149
|
+
* Saves a policy set to a JSON file with secure permissions.
|
|
150
|
+
* Creates parent directories with owner-only permissions (0o700).
|
|
151
|
+
* Files are written with owner-only permissions (0o600).
|
|
152
|
+
*
|
|
153
|
+
* @param path Path to save the policy file
|
|
154
|
+
* @param policy The policy set to save
|
|
155
|
+
*/
|
|
156
|
+
export declare function savePolicy(path: string, policy: PolicySet): void;
|
|
157
|
+
/**
|
|
158
|
+
* Returns a list of built-in policy file names from the policy directory.
|
|
159
|
+
* Returns empty array if the directory doesn't exist or is empty.
|
|
160
|
+
*/
|
|
161
|
+
export declare function getBuiltinPolicies(): string[];
|
|
162
|
+
/**
|
|
163
|
+
* Built-in policy rule templates for common hardening checks.
|
|
164
|
+
* These can be used as a starting point for custom policies.
|
|
165
|
+
*
|
|
166
|
+
* SECURITY: All check commands use direct binary invocation (no shell).
|
|
167
|
+
* Shell interpreters (sh, bash, etc.) are never used in check or remediation arrays.
|
|
168
|
+
*/
|
|
169
|
+
export declare const BUILTIN_RULE_TEMPLATES: PolicyRule[];
|
|
170
|
+
//# sourceMappingURL=policy-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-engine.d.ts","sourceRoot":"","sources":["../../src/core/policy-engine.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,QAAQ,EAAE,cAAc,CAAC;IACzB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,8BAA8B;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AA4ED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,SAAU,GAAG,IAAI,CA0CxE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA0BrE;AAID;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CA0E1E;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,uBAAuB,CAAC,CAwClC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAwBlD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAQhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAY7C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,UAAU,EAwT9C,CAAC"}
|