defense-mcp-server 0.9.1 → 0.9.2
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/build/core/config.d.ts +16 -0
- package/build/core/config.d.ts.map +1 -1
- package/build/core/config.js +6 -0
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +13 -0
- package/build/core/output-redactor.d.ts +26 -0
- package/build/core/output-redactor.d.ts.map +1 -0
- package/build/core/output-redactor.js +96 -0
- package/build/core/tool-annotations.d.ts +13 -0
- package/build/core/tool-annotations.d.ts.map +1 -0
- package/build/core/tool-annotations.js +49 -0
- package/build/core/tool-wrapper.d.ts.map +1 -1
- package/build/core/tool-wrapper.js +18 -0
- package/build/tools/meta.d.ts.map +1 -1
- package/build/tools/meta.js +137 -2
- package/package.json +1 -1
package/build/core/config.d.ts
CHANGED
|
@@ -59,6 +59,22 @@ export interface DefenseConfig {
|
|
|
59
59
|
commandTimeout: number;
|
|
60
60
|
/** Network operation timeout in ms (default: 30s; env: DEFENSE_MCP_NETWORK_TIMEOUT) */
|
|
61
61
|
networkTimeout: number;
|
|
62
|
+
/**
|
|
63
|
+
* SECURITY: Redact sensitive data (passwords, tokens, keys) from command
|
|
64
|
+
* output before returning to the LLM. Defaults to `true`.
|
|
65
|
+
* Env: DEFENSE_MCP_REDACT_OUTPUT (set to "false" to disable)
|
|
66
|
+
*/
|
|
67
|
+
redactOutput: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* When true, only register tools with readOnlyHint: true annotations.
|
|
70
|
+
* Env: DEFENSE_MCP_READ_ONLY (default: false)
|
|
71
|
+
*/
|
|
72
|
+
readOnly: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Comma-separated list of tool names to register. Empty means all tools.
|
|
75
|
+
* Env: DEFENSE_MCP_ALLOWED_TOOLS (default: "")
|
|
76
|
+
*/
|
|
77
|
+
allowedTools: string[];
|
|
62
78
|
}
|
|
63
79
|
/**
|
|
64
80
|
* Returns the current configuration by reading environment variables.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,WAAW,iKAed,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB;IACpB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC9C;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,4CAA4C;IAC5C,WAAW,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;;OAMG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAC7B,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uGAAuG;IACvG,cAAc,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;CACxB;AAmGD;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAQzC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,WAAW,iKAed,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB;IACpB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC9C;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,4CAA4C;IAC5C,WAAW,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;;OAMG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAC7B,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uGAAuG;IACvG,cAAc,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAmGD;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAQzC;AA8FD;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,aAAa,GACrB,MAAM,CAIR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,aAAa,GACrB,MAAM,CAkBR"}
|
package/build/core/config.js
CHANGED
|
@@ -168,6 +168,12 @@ function buildConfigFromEnv() {
|
|
|
168
168
|
const sec = parseInt(process.env.DEFENSE_MCP_NETWORK_TIMEOUT ?? "30", 10);
|
|
169
169
|
return isNaN(sec) || sec <= 0 ? 30_000 : sec * 1000;
|
|
170
170
|
})(),
|
|
171
|
+
redactOutput: process.env.DEFENSE_MCP_REDACT_OUTPUT !== "false",
|
|
172
|
+
readOnly: process.env.DEFENSE_MCP_READ_ONLY === "true",
|
|
173
|
+
allowedTools: (() => {
|
|
174
|
+
const raw = process.env.DEFENSE_MCP_ALLOWED_TOOLS ?? "";
|
|
175
|
+
return raw.split(",").map(s => s.trim()).filter(s => s.length > 0);
|
|
176
|
+
})(),
|
|
171
177
|
};
|
|
172
178
|
// Warn when dry-run is active so operators know no changes will be applied
|
|
173
179
|
if (config.dryRun) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"AAmFA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AA0FD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAoPxB"}
|
package/build/core/executor.js
CHANGED
|
@@ -6,6 +6,7 @@ import { getConfig, getToolTimeout } from "./config.js";
|
|
|
6
6
|
import { SudoSession } from "./sudo-session.js";
|
|
7
7
|
import { SudoGuard } from "./sudo-guard.js";
|
|
8
8
|
import { resolveCommand, resolveSudoCommand } from "./command-allowlist.js";
|
|
9
|
+
import { redactOutput } from "./output-redactor.js";
|
|
9
10
|
// ── Askpass helper detection ─────────────────────────────────────────────────
|
|
10
11
|
/**
|
|
11
12
|
* Ordered list of known graphical sudo/SSH askpass helpers.
|
|
@@ -328,6 +329,18 @@ export async function executeCommand(options) {
|
|
|
328
329
|
const exitCode = timedOut ? 124 : (code ?? 1);
|
|
329
330
|
let stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
330
331
|
let stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
332
|
+
// ── Output sanitization: redact credentials before returning to LLM ──
|
|
333
|
+
if (getConfig().redactOutput) {
|
|
334
|
+
const stdoutR = redactOutput(stdout);
|
|
335
|
+
const stderrR = redactOutput(stderr);
|
|
336
|
+
stdout = stdoutR.text;
|
|
337
|
+
stderr = stderrR.text;
|
|
338
|
+
const total = stdoutR.redactionCount + stderrR.redactionCount;
|
|
339
|
+
if (total > 0) {
|
|
340
|
+
const patterns = [...new Set([...stdoutR.matchedPatterns, ...stderrR.matchedPatterns])];
|
|
341
|
+
console.error(`[output-redactor] Redacted ${total} sensitive pattern(s) from '${options.toolName}': ${patterns.join(", ")}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
331
344
|
if (stdoutCapped) {
|
|
332
345
|
stdout += "\n[OUTPUT TRUNCATED - exceeded max buffer]";
|
|
333
346
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-redactor.ts — Post-execution output sanitization.
|
|
3
|
+
*
|
|
4
|
+
* Scans command stdout/stderr for sensitive data patterns and replaces
|
|
5
|
+
* them with [REDACTED] before returning results to the LLM.
|
|
6
|
+
*
|
|
7
|
+
* SECURITY: Over-redacting is preferred to under-redacting.
|
|
8
|
+
*
|
|
9
|
+
* @module output-redactor
|
|
10
|
+
*/
|
|
11
|
+
export interface RedactionResult {
|
|
12
|
+
/** The sanitized text */
|
|
13
|
+
text: string;
|
|
14
|
+
/** Number of redactions applied */
|
|
15
|
+
redactionCount: number;
|
|
16
|
+
/** Labels of patterns that matched */
|
|
17
|
+
matchedPatterns: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Redact sensitive data from command output.
|
|
21
|
+
*
|
|
22
|
+
* @param text - Raw stdout or stderr text
|
|
23
|
+
* @returns Sanitized text with redaction metadata
|
|
24
|
+
*/
|
|
25
|
+
export declare function redactOutput(text: string): RedactionResult;
|
|
26
|
+
//# sourceMappingURL=output-redactor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-redactor.d.ts","sourceRoot":"","sources":["../../src/core/output-redactor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmFH,MAAM,WAAW,eAAe;IAC9B,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAmB1D"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-redactor.ts — Post-execution output sanitization.
|
|
3
|
+
*
|
|
4
|
+
* Scans command stdout/stderr for sensitive data patterns and replaces
|
|
5
|
+
* them with [REDACTED] before returning results to the LLM.
|
|
6
|
+
*
|
|
7
|
+
* SECURITY: Over-redacting is preferred to under-redacting.
|
|
8
|
+
*
|
|
9
|
+
* @module output-redactor
|
|
10
|
+
*/
|
|
11
|
+
const REDACTION_PATTERNS = [
|
|
12
|
+
// Private key blocks (PEM format)
|
|
13
|
+
{
|
|
14
|
+
pattern: /-----BEGIN\s[\w\s]*PRIVATE KEY-----[\s\S]*?-----END\s[\w\s]*PRIVATE KEY-----/g,
|
|
15
|
+
replacement: "[REDACTED: private key block]",
|
|
16
|
+
label: "private-key",
|
|
17
|
+
},
|
|
18
|
+
// AWS access key IDs (AKIA...)
|
|
19
|
+
{
|
|
20
|
+
pattern: /\bAKIA[0-9A-Z]{16}\b/g,
|
|
21
|
+
replacement: "[REDACTED: AWS access key]",
|
|
22
|
+
label: "aws-key",
|
|
23
|
+
},
|
|
24
|
+
// AWS secret access key after known labels
|
|
25
|
+
{
|
|
26
|
+
pattern: /(?:aws_secret_access_key|secret[_-]?access[_-]?key)\s*[=:]\s*[A-Za-z0-9/+=]{40}/gi,
|
|
27
|
+
replacement: "[REDACTED: AWS secret key]",
|
|
28
|
+
label: "aws-secret",
|
|
29
|
+
},
|
|
30
|
+
// Generic password patterns
|
|
31
|
+
{
|
|
32
|
+
pattern: /(?:password|passwd|pass|pwd)\s*[=:]\s*\S+/gi,
|
|
33
|
+
replacement: "[REDACTED: password]",
|
|
34
|
+
label: "password",
|
|
35
|
+
},
|
|
36
|
+
// Authorization / Bearer / Basic auth headers
|
|
37
|
+
{
|
|
38
|
+
pattern: /(?:Authorization|Bearer|Basic)\s*[:=]\s*\S+/gi,
|
|
39
|
+
replacement: "[REDACTED: auth token]",
|
|
40
|
+
label: "auth-header",
|
|
41
|
+
},
|
|
42
|
+
// API keys and tokens
|
|
43
|
+
{
|
|
44
|
+
pattern: /(?:api[_-]?key|api[_-]?token|access[_-]?token|auth[_-]?token|secret[_-]?key)\s*[=:]\s*\S+/gi,
|
|
45
|
+
replacement: "[REDACTED: api key/token]",
|
|
46
|
+
label: "api-key",
|
|
47
|
+
},
|
|
48
|
+
// Connection strings with embedded credentials
|
|
49
|
+
{
|
|
50
|
+
pattern: /(?:mysql|postgres(?:ql)?|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:]+:[^@]+@/gi,
|
|
51
|
+
replacement: "[REDACTED: connection string]://",
|
|
52
|
+
label: "connection-string",
|
|
53
|
+
},
|
|
54
|
+
// /etc/shadow password hashes (user:$hash:...)
|
|
55
|
+
{
|
|
56
|
+
pattern: /^([^:]+):\$[0-9a-z]+\$[^:]+:/gm,
|
|
57
|
+
replacement: "$1:[REDACTED: password hash]:",
|
|
58
|
+
label: "shadow-hash",
|
|
59
|
+
},
|
|
60
|
+
// GitHub / GitLab personal access tokens
|
|
61
|
+
{
|
|
62
|
+
pattern: /\b(?:ghp|gho|ghu|ghs|ghr|glpat)-[A-Za-z0-9_]{20,}\b/g,
|
|
63
|
+
replacement: "[REDACTED: git token]",
|
|
64
|
+
label: "git-token",
|
|
65
|
+
},
|
|
66
|
+
// Generic hex tokens (32+ chars after token/secret/key labels)
|
|
67
|
+
{
|
|
68
|
+
pattern: /(?:token|secret|key)\s*[=:]\s*[0-9a-f]{32,}/gi,
|
|
69
|
+
replacement: "[REDACTED: hex token]",
|
|
70
|
+
label: "hex-token",
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
/**
|
|
74
|
+
* Redact sensitive data from command output.
|
|
75
|
+
*
|
|
76
|
+
* @param text - Raw stdout or stderr text
|
|
77
|
+
* @returns Sanitized text with redaction metadata
|
|
78
|
+
*/
|
|
79
|
+
export function redactOutput(text) {
|
|
80
|
+
if (!text)
|
|
81
|
+
return { text, redactionCount: 0, matchedPatterns: [] };
|
|
82
|
+
let result = text;
|
|
83
|
+
let redactionCount = 0;
|
|
84
|
+
const matchedPatterns = [];
|
|
85
|
+
for (const { pattern, replacement, label } of REDACTION_PATTERNS) {
|
|
86
|
+
// Reset lastIndex for global regexes
|
|
87
|
+
pattern.lastIndex = 0;
|
|
88
|
+
const matches = result.match(pattern);
|
|
89
|
+
if (matches && matches.length > 0) {
|
|
90
|
+
redactionCount += matches.length;
|
|
91
|
+
matchedPatterns.push(label);
|
|
92
|
+
result = result.replace(pattern, replacement);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { text: result, redactionCount, matchedPatterns };
|
|
96
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-annotations.ts — Centralized MCP ToolAnnotations for all tools.
|
|
3
|
+
*
|
|
4
|
+
* Annotations are auto-injected by the tool-wrapper proxy at registration
|
|
5
|
+
* time, so individual tool files do not need modification.
|
|
6
|
+
*
|
|
7
|
+
* @module tool-annotations
|
|
8
|
+
*/
|
|
9
|
+
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
export declare const TOOL_ANNOTATIONS: Record<string, ToolAnnotations>;
|
|
11
|
+
export declare function getToolAnnotations(toolName: string): ToolAnnotations | undefined;
|
|
12
|
+
export declare function isReadOnlyTool(toolName: string): boolean;
|
|
13
|
+
//# sourceMappingURL=tool-annotations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-annotations.d.ts","sourceRoot":"","sources":["../../src/core/tool-annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAE1E,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAmC5D,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEhF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-annotations.ts — Centralized MCP ToolAnnotations for all tools.
|
|
3
|
+
*
|
|
4
|
+
* Annotations are auto-injected by the tool-wrapper proxy at registration
|
|
5
|
+
* time, so individual tool files do not need modification.
|
|
6
|
+
*
|
|
7
|
+
* @module tool-annotations
|
|
8
|
+
*/
|
|
9
|
+
export const TOOL_ANNOTATIONS = {
|
|
10
|
+
// Read-only tools (ALL actions are non-modifying)
|
|
11
|
+
secrets: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
12
|
+
cloud_security: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
13
|
+
process_security: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
14
|
+
api_security: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
15
|
+
// Destructive tools (at least one state-modifying action)
|
|
16
|
+
firewall: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
17
|
+
harden_kernel: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
18
|
+
harden_host: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
19
|
+
access_control: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
20
|
+
compliance: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
21
|
+
integrity: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
22
|
+
log_management: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
23
|
+
malware: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
24
|
+
container_docker: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
25
|
+
container_isolation: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
26
|
+
ebpf: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
27
|
+
crypto: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
28
|
+
network_defense: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
29
|
+
patch: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
30
|
+
incident_response: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
31
|
+
defense_mgmt: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
32
|
+
sudo_session: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
33
|
+
backup: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
34
|
+
supply_chain: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
35
|
+
zero_trust: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
36
|
+
honeypot_manage: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
37
|
+
dns_security: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
38
|
+
threat_intel: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
39
|
+
vuln_manage: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
40
|
+
waf_manage: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
41
|
+
wireless_security: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
42
|
+
app_harden: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
43
|
+
};
|
|
44
|
+
export function getToolAnnotations(toolName) {
|
|
45
|
+
return TOOL_ANNOTATIONS[toolName];
|
|
46
|
+
}
|
|
47
|
+
export function isReadOnlyTool(toolName) {
|
|
48
|
+
return TOOL_ANNOTATIONS[toolName]?.readOnlyHint === true;
|
|
49
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-wrapper.d.ts","sourceRoot":"","sources":["../../src/core/tool-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-wrapper.d.ts","sourceRoot":"","sources":["../../src/core/tool-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsBzE,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAeD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,cAAmB,GAC3B,SAAS,CAkDX;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD"}
|
|
@@ -27,6 +27,8 @@ import { PrivilegeManager } from "./privilege-manager.js";
|
|
|
27
27
|
import { SudoGuard } from "./sudo-guard.js";
|
|
28
28
|
import { SudoSession } from "./sudo-session.js";
|
|
29
29
|
import { RateLimiter } from "./rate-limiter.js";
|
|
30
|
+
import { getConfig } from "./config.js";
|
|
31
|
+
import { getToolAnnotations, isReadOnlyTool } from "./tool-annotations.js";
|
|
30
32
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
31
33
|
/**
|
|
32
34
|
* Tools that always skip pre-flight because they manage the sudo session
|
|
@@ -131,6 +133,16 @@ function createWrappedToolMethod(server, ctx) {
|
|
|
131
133
|
return originalTool(...args);
|
|
132
134
|
}
|
|
133
135
|
const toolName = args[0];
|
|
136
|
+
// ── Tool filtering (read-only mode & allowlisting) ──────────────
|
|
137
|
+
const config = getConfig();
|
|
138
|
+
if (config.allowedTools.length > 0 && !config.allowedTools.includes(toolName)) {
|
|
139
|
+
console.error(`[tool-filter] Skipping '${toolName}' — not in DEFENSE_MCP_ALLOWED_TOOLS`);
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
if (config.readOnly && !isReadOnlyTool(toolName)) {
|
|
143
|
+
console.error(`[tool-filter] Skipping destructive tool '${toolName}' — read-only mode`);
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
134
146
|
// ── Bypass check ─────────────────────────────────────────────────
|
|
135
147
|
if (shouldBypassPreflight(toolName, ctx)) {
|
|
136
148
|
return originalTool(...args);
|
|
@@ -141,6 +153,12 @@ function createWrappedToolMethod(server, ctx) {
|
|
|
141
153
|
// Reconstruct args with the wrapped handler in the last position
|
|
142
154
|
const wrappedArgs = [...args];
|
|
143
155
|
wrappedArgs[wrappedArgs.length - 1] = wrappedHandler;
|
|
156
|
+
// ── Auto-inject tool annotations ────────────────────────────────
|
|
157
|
+
const annotations = getToolAnnotations(toolName);
|
|
158
|
+
if (annotations) {
|
|
159
|
+
// Insert annotations before the handler (last position)
|
|
160
|
+
wrappedArgs.splice(wrappedArgs.length - 1, 0, annotations);
|
|
161
|
+
}
|
|
144
162
|
return originalTool(...wrappedArgs);
|
|
145
163
|
};
|
|
146
164
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../src/tools/meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../src/tools/meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAi6BpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAu8CzD"}
|
package/build/tools/meta.js
CHANGED
|
@@ -14,7 +14,7 @@ import { executeCommand } from "../core/executor.js";
|
|
|
14
14
|
import { resolveCommand, isAllowlisted } from "../core/command-allowlist.js";
|
|
15
15
|
import { getConfig, getToolTimeout } from "../core/config.js";
|
|
16
16
|
import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
|
|
17
|
-
import { logChange, createChangeEntry, getChangelog } from "../core/changelog.js";
|
|
17
|
+
import { logChange, createChangeEntry, getChangelog, verifyChangelog } from "../core/changelog.js";
|
|
18
18
|
import { checkAllTools, installMissing, getInstallHint, getAlternativesMap, } from "../core/installer.js";
|
|
19
19
|
import { SafeguardRegistry } from "../core/safeguards.js";
|
|
20
20
|
import { listThirdPartyTools, installThirdPartyTool, getVerifiedInstallInstructions, isThirdPartyInstallEnabled, } from "../core/third-party-installer.js";
|
|
@@ -23,7 +23,9 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSy
|
|
|
23
23
|
import { join } from "node:path";
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
25
|
import { spawnSafe } from "../core/spawn-safe.js";
|
|
26
|
-
import { secureWriteFileSync } from "../core/secure-fs.js";
|
|
26
|
+
import { secureWriteFileSync, verifySecurePermissions } from "../core/secure-fs.js";
|
|
27
|
+
import { verifyAllBinaries } from "../core/command-allowlist.js";
|
|
28
|
+
import { RateLimiter } from "../core/rate-limiter.js";
|
|
27
29
|
// Suppress unused import warnings
|
|
28
30
|
void getToolTimeout;
|
|
29
31
|
// ── Security Posture Helpers ───────────────────────────────────────────────
|
|
@@ -804,6 +806,7 @@ export function registerMetaTools(server) {
|
|
|
804
806
|
"report_formats",
|
|
805
807
|
"check_optional_deps",
|
|
806
808
|
"install_optional_deps",
|
|
809
|
+
"self_audit",
|
|
807
810
|
]).describe("Defense management action"),
|
|
808
811
|
// check_tools params
|
|
809
812
|
category: z.string().optional().describe("Filter by tool category"),
|
|
@@ -828,6 +831,8 @@ export function registerMetaTools(server) {
|
|
|
828
831
|
severity_filter: z.enum(["critical", "high", "medium", "low"]).optional().default("medium").describe("Minimum severity to include"),
|
|
829
832
|
session_id: z.string().optional().describe("Remediation session ID"),
|
|
830
833
|
output_format: z.enum(["text", "json"]).optional().default("text").describe("Output format"),
|
|
834
|
+
// self_audit params
|
|
835
|
+
verbose: z.boolean().optional().default(false).describe("Include detailed findings in self-audit output"),
|
|
831
836
|
// optional deps params
|
|
832
837
|
tool_name: z.string().optional().describe("Tool binary name to install (omit for all missing)"),
|
|
833
838
|
force: z.boolean().optional().default(false).describe("Force reinstall even if installed"),
|
|
@@ -2101,6 +2106,136 @@ export function registerMetaTools(server) {
|
|
|
2101
2106
|
return { content: [createErrorContent(`Optional deps installation failed: ${msg}`)], isError: true };
|
|
2102
2107
|
}
|
|
2103
2108
|
}
|
|
2109
|
+
// ── self_audit ──────────────────────────────────────────────────
|
|
2110
|
+
case "self_audit": {
|
|
2111
|
+
try {
|
|
2112
|
+
const verbose = params.verbose ?? false;
|
|
2113
|
+
const sections = [];
|
|
2114
|
+
sections.push("=== Defense MCP Self-Security Audit ===");
|
|
2115
|
+
sections.push("");
|
|
2116
|
+
let score = 0;
|
|
2117
|
+
let totalChecks = 0;
|
|
2118
|
+
let warnings = 0;
|
|
2119
|
+
// 1. State directory permissions
|
|
2120
|
+
totalChecks++;
|
|
2121
|
+
const stateDir = join(homedir(), ".defense-mcp");
|
|
2122
|
+
const stateDirs = [stateDir, join(stateDir, "backups"), join(stateDir, "quarantine"), join(stateDir, "policies")];
|
|
2123
|
+
sections.push("1. State Directory Permissions");
|
|
2124
|
+
let dirAllOk = true;
|
|
2125
|
+
for (const dir of stateDirs) {
|
|
2126
|
+
if (!existsSync(dir)) {
|
|
2127
|
+
sections.push(` ${dir}: MISSING (not created yet)`);
|
|
2128
|
+
}
|
|
2129
|
+
else if (verifySecurePermissions(dir)) {
|
|
2130
|
+
if (verbose)
|
|
2131
|
+
sections.push(` ${dir}: OK (0700)`);
|
|
2132
|
+
}
|
|
2133
|
+
else {
|
|
2134
|
+
sections.push(` ${dir}: WARN — permissions too open`);
|
|
2135
|
+
dirAllOk = false;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
if (dirAllOk) {
|
|
2139
|
+
score++;
|
|
2140
|
+
sections.push(" Status: OK");
|
|
2141
|
+
}
|
|
2142
|
+
else {
|
|
2143
|
+
warnings++;
|
|
2144
|
+
sections.push(" Status: WARNING");
|
|
2145
|
+
}
|
|
2146
|
+
sections.push("");
|
|
2147
|
+
// 2. Changelog hash chain integrity
|
|
2148
|
+
totalChecks++;
|
|
2149
|
+
const clResult = verifyChangelog();
|
|
2150
|
+
sections.push("2. Changelog Integrity");
|
|
2151
|
+
sections.push(` Total entries: ${clResult.totalEntries}`);
|
|
2152
|
+
sections.push(` Hashed entries: ${clResult.hashedEntries}`);
|
|
2153
|
+
if (clResult.valid) {
|
|
2154
|
+
sections.push(" Chain integrity: VALID");
|
|
2155
|
+
score++;
|
|
2156
|
+
}
|
|
2157
|
+
else {
|
|
2158
|
+
sections.push(` Chain integrity: BROKEN (${clResult.brokenLinks.length} broken link(s))`);
|
|
2159
|
+
warnings++;
|
|
2160
|
+
if (verbose) {
|
|
2161
|
+
for (const link of clResult.brokenLinks.slice(0, 5)) {
|
|
2162
|
+
sections.push(` - Entry ${link.index} (${link.entryId})`);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
sections.push("");
|
|
2167
|
+
// 3. Binary integrity
|
|
2168
|
+
totalChecks++;
|
|
2169
|
+
sections.push("3. Binary Integrity");
|
|
2170
|
+
try {
|
|
2171
|
+
const binResults = await verifyAllBinaries();
|
|
2172
|
+
const verified = binResults.filter(r => r.verified).length;
|
|
2173
|
+
const warnBins = binResults.filter(r => !r.verified).length;
|
|
2174
|
+
const skipped = binResults.length === 0 ? 0 : 0;
|
|
2175
|
+
sections.push(` Verified: ${verified}, Warnings: ${warnBins}, Total: ${binResults.length}`);
|
|
2176
|
+
if (warnBins === 0) {
|
|
2177
|
+
score++;
|
|
2178
|
+
sections.push(" Status: OK");
|
|
2179
|
+
}
|
|
2180
|
+
else {
|
|
2181
|
+
warnings++;
|
|
2182
|
+
sections.push(" Status: WARNING");
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
catch {
|
|
2186
|
+
sections.push(" Status: ERROR — could not verify binaries");
|
|
2187
|
+
warnings++;
|
|
2188
|
+
}
|
|
2189
|
+
sections.push("");
|
|
2190
|
+
// 4. Configuration security
|
|
2191
|
+
totalChecks++;
|
|
2192
|
+
const cfg = getConfig();
|
|
2193
|
+
sections.push("4. Configuration Security");
|
|
2194
|
+
const cfgChecks = [
|
|
2195
|
+
["dryRun", cfg.dryRun, "safe preview mode"],
|
|
2196
|
+
["requireConfirmation", cfg.requireConfirmation, "confirm before changes"],
|
|
2197
|
+
["backupEnabled", cfg.backupEnabled, "backup before modify"],
|
|
2198
|
+
["redactOutput", cfg.redactOutput, "credential redaction"],
|
|
2199
|
+
];
|
|
2200
|
+
let cfgAllOk = true;
|
|
2201
|
+
for (const [name, value, desc] of cfgChecks) {
|
|
2202
|
+
if (value) {
|
|
2203
|
+
sections.push(` [OK] ${name}: true (${desc})`);
|
|
2204
|
+
}
|
|
2205
|
+
else {
|
|
2206
|
+
sections.push(` [WARN] ${name}: false — consider enabling in production`);
|
|
2207
|
+
cfgAllOk = false;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
if (cfg.readOnly) {
|
|
2211
|
+
sections.push(` [INFO] readOnly: true (audit-only mode)`);
|
|
2212
|
+
}
|
|
2213
|
+
else if (verbose) {
|
|
2214
|
+
sections.push(` [INFO] readOnly: false`);
|
|
2215
|
+
}
|
|
2216
|
+
if (cfgAllOk)
|
|
2217
|
+
score++;
|
|
2218
|
+
else
|
|
2219
|
+
warnings++;
|
|
2220
|
+
sections.push("");
|
|
2221
|
+
// 5. Rate limiter status
|
|
2222
|
+
totalChecks++;
|
|
2223
|
+
const rl = RateLimiter.instance();
|
|
2224
|
+
sections.push("5. Rate Limiter");
|
|
2225
|
+
sections.push(` Per-tool limit: ${rl.maxPerTool}/${Math.round(rl.windowMs / 1000)}s`);
|
|
2226
|
+
sections.push(` Global limit: ${rl.maxGlobal}/${Math.round(rl.windowMs / 1000)}s`);
|
|
2227
|
+
sections.push(" Status: ACTIVE");
|
|
2228
|
+
score++;
|
|
2229
|
+
sections.push("");
|
|
2230
|
+
// Overall score
|
|
2231
|
+
sections.push(`Overall: ${score}/${totalChecks} (${warnings} warning(s))`);
|
|
2232
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
2233
|
+
}
|
|
2234
|
+
catch (err) {
|
|
2235
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2236
|
+
return { content: [createErrorContent(`Self-audit failed: ${msg}`)], isError: true };
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2104
2239
|
default:
|
|
2105
2240
|
return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
|
|
2106
2241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "defense-mcp-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Defense MCP Server — 31 defensive security tools with 250+ actions for system hardening, compliance, and threat detection on Linux",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|