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.
@@ -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;AAwFD;;;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"}
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"}
@@ -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":"AAkFA;;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,CAqOxB"}
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"}
@@ -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;AAoBzE,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"}
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;AA+5BpE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAg1CzD"}
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"}
@@ -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.1",
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",