defense-mcp-server 0.7.2 → 0.8.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.
Files changed (75) hide show
  1. package/README.md +1 -1
  2. package/build/core/auto-installer.d.ts.map +1 -1
  3. package/build/core/auto-installer.js +51 -0
  4. package/build/core/changelog.d.ts +44 -0
  5. package/build/core/changelog.d.ts.map +1 -1
  6. package/build/core/changelog.js +76 -1
  7. package/build/core/command-allowlist.d.ts.map +1 -1
  8. package/build/core/command-allowlist.js +1 -0
  9. package/build/core/dependency-validator.d.ts.map +1 -1
  10. package/build/core/dependency-validator.js +1 -0
  11. package/build/core/distro-adapter.js +1 -1
  12. package/build/core/distro.js +5 -5
  13. package/build/core/executor.d.ts +2 -2
  14. package/build/core/executor.d.ts.map +1 -1
  15. package/build/core/installer.d.ts.map +1 -1
  16. package/build/core/installer.js +4 -0
  17. package/build/core/logger.d.ts +16 -0
  18. package/build/core/logger.d.ts.map +1 -1
  19. package/build/core/logger.js +90 -1
  20. package/build/core/metrics.d.ts +74 -0
  21. package/build/core/metrics.d.ts.map +1 -0
  22. package/build/core/metrics.js +97 -0
  23. package/build/core/policy-engine.d.ts.map +1 -1
  24. package/build/core/policy-engine.js +1 -0
  25. package/build/core/rollback.d.ts.map +1 -1
  26. package/build/core/rollback.js +4 -0
  27. package/build/core/safeguards.d.ts.map +1 -1
  28. package/build/core/safeguards.js +3 -1
  29. package/build/core/sanitizer.d.ts.map +1 -1
  30. package/build/core/sanitizer.js +22 -0
  31. package/build/core/sudo-guard.js +3 -3
  32. package/build/core/tool-dependencies.d.ts.map +1 -1
  33. package/build/core/tool-dependencies.js +2 -1
  34. package/build/index.js +9 -10
  35. package/build/tools/app-hardening.d.ts.map +1 -1
  36. package/build/tools/app-hardening.js +8 -3
  37. package/build/tools/compliance.d.ts.map +1 -1
  38. package/build/tools/compliance.js +42 -8
  39. package/build/tools/container-security.d.ts +1 -0
  40. package/build/tools/container-security.d.ts.map +1 -1
  41. package/build/tools/container-security.js +89 -16
  42. package/build/tools/drift-detection.d.ts +6 -2
  43. package/build/tools/drift-detection.d.ts.map +1 -1
  44. package/build/tools/drift-detection.js +7 -18
  45. package/build/tools/ebpf-security.d.ts.map +1 -1
  46. package/build/tools/ebpf-security.js +11 -3
  47. package/build/tools/firewall.d.ts.map +1 -1
  48. package/build/tools/firewall.js +28 -6
  49. package/build/tools/hardening.d.ts.map +1 -1
  50. package/build/tools/hardening.js +18 -3
  51. package/build/tools/integrity.d.ts.map +1 -1
  52. package/build/tools/integrity.js +5 -0
  53. package/build/tools/meta.d.ts.map +1 -1
  54. package/build/tools/meta.js +58 -24
  55. package/build/tools/patch-management.js +9 -9
  56. package/build/tools/reporting.d.ts +6 -2
  57. package/build/tools/reporting.d.ts.map +1 -1
  58. package/build/tools/reporting.js +7 -18
  59. package/build/tools/secrets.d.ts.map +1 -1
  60. package/build/tools/secrets.js +4 -2
  61. package/build/tools/siem-integration.d.ts +5 -2
  62. package/build/tools/siem-integration.d.ts.map +1 -1
  63. package/build/tools/siem-integration.js +6 -18
  64. package/build/tools/sudo-management.d.ts.map +1 -1
  65. package/build/tools/sudo-management.js +123 -147
  66. package/build/tools/supply-chain-security.d.ts.map +1 -1
  67. package/build/tools/supply-chain-security.js +10 -4
  68. package/build/tools/threat-intel.d.ts.map +1 -1
  69. package/build/tools/threat-intel.js +19 -2
  70. package/build/tools/zero-trust-network.d.ts.map +1 -1
  71. package/build/tools/zero-trust-network.js +15 -6
  72. package/package.json +11 -3
  73. package/build/tools/ids.d.ts +0 -2
  74. package/build/tools/ids.d.ts.map +0 -1
  75. package/build/tools/ids.js +0 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Defense MCP Server
2
2
 
3
- A Model Context Protocol (MCP) server that gives AI assistants access to **94 defensive security tools** on Linux. Connect it to Claude Desktop, Cursor, or any MCP-compatible client to harden systems, manage firewalls, scan for vulnerabilities, and enforce compliance — all through natural language conversation.
3
+ A Model Context Protocol (MCP) server that gives AI assistants access to **31 defensive security tools** (with 150+ actions) on Linux. Connect it to Claude Desktop, Cursor, or any MCP-compatible client to harden systems, manage firewalls, scan for vulnerabilities, and enforce compliance — all through natural language conversation.
4
4
 
5
5
  ## Why I Made This
6
6
  Basically I'm a total noob when it comes to really serious system hardening so I thought I'd test the latest LLM models and see how far I could get. Turns out they're pretty helpful! I got tired of hardening my new systems by hand every time I spun up a new one so I made this MCP server to make it pretty easy. I jam packed as many security tools as I could into this thing so be prepared to burn tokens using it. Hopefully it helps you about half as much as its helped me.
@@ -1 +1 @@
1
- {"version":3,"file":"auto-installer.d.ts","sourceRoot":"","sources":["../../src/core/auto-installer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,GAAG,eAAe,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC;IACtE,MAAM,EACF,gBAAgB,GAChB,KAAK,GACL,KAAK,GACL,OAAO,GACP,YAAY,GACZ,iBAAiB,GACjB,mBAAmB,GACnB,UAAU,GACV,SAAS,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,sBAAsB,EAAE,MAAM,EAAE,CAAC;CAClC;AAyFD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAgLD,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,SAAS,CAA8B;IACtD,OAAO,CAAC,WAAW,CAA2B;IAE9C,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,IAAI,aAAa;IAahC;;;OAGG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B,mDAAmD;IACnD,SAAS,IAAI,OAAO;IAIpB;;;;;OAKG;IACG,UAAU,CACd,QAAQ,EAAE,YAAY,EACtB,eAAe,EAAE,MAAM,EAAE,EACzB,aAAa,CAAC,EAAE,MAAM,EAAE,EACxB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAC1B,OAAO,CAAC,iBAAiB,CAAC;IA6G7B;;;;;;OAMG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA4J5D;;;;;;;OAOG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAiHlE;;;;;;OAMG;IACG,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA8G7D;;;;;;OAMG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAyG1D;;OAEG;YACW,SAAS;IAOvB;;OAEG;IACH,OAAO,CAAC,aAAa;CAmBtB"}
1
+ {"version":3,"file":"auto-installer.d.ts","sourceRoot":"","sources":["../../src/core/auto-installer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,GAAG,eAAe,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC;IACtE,MAAM,EACF,gBAAgB,GAChB,KAAK,GACL,KAAK,GACL,OAAO,GACP,YAAY,GACZ,iBAAiB,GACjB,mBAAmB,GACnB,UAAU,GACV,SAAS,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,sBAAsB,EAAE,MAAM,EAAE,CAAC;CAClC;AAyFD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAiOD,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,SAAS,CAA8B;IACtD,OAAO,CAAC,WAAW,CAA2B;IAE9C,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,IAAI,aAAa;IAahC;;;OAGG;IACH,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B,mDAAmD;IACnD,SAAS,IAAI,OAAO;IAIpB;;;;;OAKG;IACG,UAAU,CACd,QAAQ,EAAE,YAAY,EACtB,eAAe,EAAE,MAAM,EAAE,EACzB,aAAa,CAAC,EAAE,MAAM,EAAE,EACxB,UAAU,CAAC,EAAE,MAAM,EAAE,EACrB,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAC1B,OAAO,CAAC,iBAAiB,CAAC;IA6G7B;;;;;;OAMG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAwK5D;;;;;;;OAOG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAiHlE;;;;;;OAMG;IACG,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA8G7D;;;;;;OAMG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAyG1D;;OAEG;YACW,SAAS;IAOvB;;OAEG;IACH,OAAO,CAAC,aAAa;CAmBtB"}
@@ -240,6 +240,48 @@ function binaryAvailable(binary) {
240
240
  return false;
241
241
  }
242
242
  }
243
+ // ── Post-install binary identity verification ────────────────────────────────
244
+ /**
245
+ * Expected version output patterns for binaries that come from third-party repos.
246
+ * Protects against package name collisions in third-party or default repos
247
+ * (e.g., Trivy and Grype don't exist in stock Debian/Ubuntu repos).
248
+ */
249
+ const BINARY_VERSION_PATTERNS = {
250
+ trivy: /trivy/i,
251
+ grype: /grype/i,
252
+ syft: /syft/i,
253
+ cosign: /cosign/i,
254
+ };
255
+ /**
256
+ * After installing a package, verify the binary is the expected tool.
257
+ * Checks `--version` output against known patterns.
258
+ * Protects against package name collisions in third-party or default repos.
259
+ *
260
+ * @returns `true` if verified or no verification needed, `false` if mismatch detected
261
+ */
262
+ function verifyInstalledBinary(binary) {
263
+ const pattern = BINARY_VERSION_PATTERNS[binary];
264
+ if (!pattern)
265
+ return true; // No verification needed for well-known distro packages
266
+ try {
267
+ const stdout = execFileSafe(binary, ["--version"], {
268
+ timeout: 10_000,
269
+ encoding: "utf-8",
270
+ stdio: ["pipe", "pipe", "pipe"],
271
+ });
272
+ if (!pattern.test(stdout ?? "")) {
273
+ console.error(`[auto-installer] ⚠️ Installed '${binary}' but --version output doesn't match ` +
274
+ `expected pattern /${pattern.source}/. The package may be a name collision, not the security tool.`);
275
+ return false;
276
+ }
277
+ return true;
278
+ }
279
+ catch {
280
+ // Can't verify — warn but don't block
281
+ console.error(`[auto-installer] ⚠️ Could not verify '${binary}' identity via --version (non-fatal)`);
282
+ return false;
283
+ }
284
+ }
243
285
  // ── Helper: resolve package install command args per distro ───────────────────
244
286
  function getInstallArgs(pkgManager, packageName) {
245
287
  switch (pkgManager) {
@@ -490,6 +532,15 @@ export class AutoInstaller {
490
532
  // Verify installation
491
533
  const installed = binaryAvailable(binary);
492
534
  const elapsed = ((Date.now() - start) / 1000).toFixed(1);
535
+ // Post-install identity verification for third-party repo binaries
536
+ if (installed) {
537
+ const verified = verifyInstalledBinary(binary);
538
+ if (!verified) {
539
+ console.error(`[auto-installer] ⚠️ Binary '${binary}' installed but identity verification failed. ` +
540
+ `The package may be a name collision (not the expected security tool). ` +
541
+ `Trivy, Grype, Syft, and Cosign require third-party repositories.`);
542
+ }
543
+ }
493
544
  if (installed) {
494
545
  console.error(`[auto-installer] ✓ Installed '${binary}' via ${distro.packageManager} (${elapsed}s)`);
495
546
  // Log successful installation to the audit changelog
@@ -30,6 +30,17 @@ export interface ChangeEntry {
30
30
  user?: string;
31
31
  /** MCP session identifier (if available) */
32
32
  sessionId?: string;
33
+ /**
34
+ * SHA-256 hash-chain value.
35
+ *
36
+ * For the first entry: SHA-256 of the entry's core fields with previousHash="genesis".
37
+ * For subsequent entries: SHA-256 of the entry's core fields concatenated with
38
+ * the previous entry's hash. This creates a tamper-evident chain — modifying or
39
+ * deleting any entry breaks the chain, which is detectable via `verifyChangelog()`.
40
+ *
41
+ * Auto-populated by `logChange()`. Not present in legacy entries.
42
+ */
43
+ hash?: string;
33
44
  }
34
45
  /**
35
46
  * Versioned changelog state file format.
@@ -39,6 +50,38 @@ export interface ChangelogState {
39
50
  version: 1;
40
51
  entries: ChangeEntry[];
41
52
  }
53
+ /**
54
+ * Compute the SHA-256 hash-chain value for a changelog entry.
55
+ *
56
+ * The hash is computed over the entry's immutable fields (id, timestamp, tool,
57
+ * action, target, dryRun, success) concatenated with the previous entry's hash.
58
+ * This creates a tamper-evident chain.
59
+ *
60
+ * @param entry - The entry to hash (hash field is excluded from the computation)
61
+ * @param previousHash - Hash of the previous entry, or "genesis" for the first entry
62
+ * @returns SHA-256 hex digest
63
+ */
64
+ export declare function computeEntryHash(entry: ChangeEntry, previousHash: string): string;
65
+ /**
66
+ * Verify the integrity of the changelog hash chain.
67
+ *
68
+ * Walks all entries with `hash` fields and checks that each hash matches
69
+ * the recomputed value from the previous entry's hash. Legacy entries
70
+ * without `hash` fields are skipped.
71
+ *
72
+ * @returns Object with `valid` boolean and details of any broken links
73
+ */
74
+ export declare function verifyChangelog(): {
75
+ valid: boolean;
76
+ totalEntries: number;
77
+ hashedEntries: number;
78
+ brokenLinks: Array<{
79
+ index: number;
80
+ entryId: string;
81
+ expected: string;
82
+ actual: string;
83
+ }>;
84
+ };
42
85
  /**
43
86
  * Creates a new ChangeEntry with auto-generated id and timestamp.
44
87
  */
@@ -47,6 +90,7 @@ export declare function createChangeEntry(partial: Omit<ChangeEntry, "id" | "tim
47
90
  * Appends a change entry to the changelog JSON file.
48
91
  * Creates the file and parent directories if they don't exist.
49
92
  * Rotates old entries when the file exceeds MAX_CHANGELOG_ENTRIES.
93
+ * Computes and attaches a hash-chain value for tamper evidence.
50
94
  * Fails silently (logs to stderr) to avoid disrupting tool execution.
51
95
  */
52
96
  export declare function logChange(entry: ChangeEntry): void;
@@ -1 +1 @@
1
- {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/core/changelog.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,MAAM,EAAE,OAAO,CAAC;IAChB,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,MAAM,CAAC,GACtD,WAAW,CAOb;AA2BD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CA0BlD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAmB1D;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAU1E"}
1
+ {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/core/changelog.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,MAAM,EAAE,OAAO,CAAC;IAChB,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAcjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1F,CAuCA;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,GAAG,MAAM,CAAC,GACtD,WAAW,CAOb;AA2BD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAiClD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAmB1D;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAU1E"}
@@ -2,9 +2,77 @@ import { readFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { userInfo } from "node:os";
4
4
  import { secureWriteFileSync, secureMkdirSync, secureCopyFileSync } from "./secure-fs.js";
5
- import { randomUUID } from "node:crypto";
5
+ import { randomUUID, createHash } from "node:crypto";
6
6
  import { getConfig } from "./config.js";
7
7
  import { BackupManager } from "./backup-manager.js";
8
+ // ── Hash-Chain Integrity ─────────────────────────────────────────────────────
9
+ /**
10
+ * Compute the SHA-256 hash-chain value for a changelog entry.
11
+ *
12
+ * The hash is computed over the entry's immutable fields (id, timestamp, tool,
13
+ * action, target, dryRun, success) concatenated with the previous entry's hash.
14
+ * This creates a tamper-evident chain.
15
+ *
16
+ * @param entry - The entry to hash (hash field is excluded from the computation)
17
+ * @param previousHash - Hash of the previous entry, or "genesis" for the first entry
18
+ * @returns SHA-256 hex digest
19
+ */
20
+ export function computeEntryHash(entry, previousHash) {
21
+ const payload = [
22
+ entry.id,
23
+ entry.timestamp,
24
+ entry.tool,
25
+ entry.action,
26
+ entry.target,
27
+ String(entry.dryRun),
28
+ String(entry.success),
29
+ entry.error ?? "",
30
+ previousHash,
31
+ ].join("|");
32
+ return createHash("sha256").update(payload).digest("hex");
33
+ }
34
+ /**
35
+ * Verify the integrity of the changelog hash chain.
36
+ *
37
+ * Walks all entries with `hash` fields and checks that each hash matches
38
+ * the recomputed value from the previous entry's hash. Legacy entries
39
+ * without `hash` fields are skipped.
40
+ *
41
+ * @returns Object with `valid` boolean and details of any broken links
42
+ */
43
+ export function verifyChangelog() {
44
+ const config = getConfig();
45
+ const state = loadChangelogState(config.changelogPath);
46
+ const entries = state.entries;
47
+ let previousHash = "genesis";
48
+ const brokenLinks = [];
49
+ let hashedEntries = 0;
50
+ for (let i = 0; i < entries.length; i++) {
51
+ const entry = entries[i];
52
+ if (!entry.hash) {
53
+ // Legacy entry without hash — skip but DON'T reset the chain.
54
+ // The next hashed entry will chain from the last known hash.
55
+ continue;
56
+ }
57
+ hashedEntries++;
58
+ const expected = computeEntryHash(entry, previousHash);
59
+ if (entry.hash !== expected) {
60
+ brokenLinks.push({
61
+ index: i,
62
+ entryId: entry.id,
63
+ expected,
64
+ actual: entry.hash,
65
+ });
66
+ }
67
+ previousHash = entry.hash;
68
+ }
69
+ return {
70
+ valid: brokenLinks.length === 0,
71
+ totalEntries: entries.length,
72
+ hashedEntries,
73
+ brokenLinks,
74
+ };
75
+ }
8
76
  /**
9
77
  * Creates a new ChangeEntry with auto-generated id and timestamp.
10
78
  */
@@ -49,6 +117,7 @@ function loadChangelogState(changelogPath) {
49
117
  * Appends a change entry to the changelog JSON file.
50
118
  * Creates the file and parent directories if they don't exist.
51
119
  * Rotates old entries when the file exceeds MAX_CHANGELOG_ENTRIES.
120
+ * Computes and attaches a hash-chain value for tamper evidence.
52
121
  * Fails silently (logs to stderr) to avoid disrupting tool execution.
53
122
  */
54
123
  export function logChange(entry) {
@@ -60,6 +129,12 @@ export function logChange(entry) {
60
129
  secureMkdirSync(dir);
61
130
  // Read existing entries (with migration)
62
131
  const state = loadChangelogState(changelogPath);
132
+ // Compute hash-chain value
133
+ const lastEntry = state.entries.length > 0
134
+ ? state.entries[state.entries.length - 1]
135
+ : null;
136
+ const previousHash = lastEntry?.hash ?? "genesis";
137
+ entry.hash = computeEntryHash(entry, previousHash);
63
138
  // Append new entry
64
139
  state.entries.push(entry);
65
140
  // Rotate: keep only the most recent entries to prevent unbounded growth
@@ -1 +1 @@
1
- {"version":3,"file":"command-allowlist.d.ts","sourceRoot":"","sources":["../../src/core/command-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAeH,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,sDAAsD;AACtD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AA8WD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAkD1C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsDtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CActD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CA6CA;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAE7E;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;GAEG;AACH,wBAAgB,gCAAgC,IAAI,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEjE;AA8CD;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAE9E;AAeD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,eAAe,CAAC,EAAE,MAAM,GACvB,wBAAwB,CA6G1B;AAiHD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,wBAAwB,EAAE,CAAC,CA6C7E"}
1
+ {"version":3,"file":"command-allowlist.d.ts","sourceRoot":"","sources":["../../src/core/command-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAeH,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,sDAAsD;AACtD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AA+WD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAkD1C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsDtD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CActD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CA6CA;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAE7E;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;GAEG;AACH,wBAAgB,gCAAgC,IAAI,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEjE;AA8CD;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAE9E;AAeD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,eAAe,CAAC,EAAE,MAAM,GACvB,wBAAwB,CA6G1B;AAiHD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,wBAAwB,EAAE,CAAC,CA6C7E"}
@@ -241,6 +241,7 @@ const ALLOWLIST_DEFINITIONS = [
241
241
  // ── GUI askpass helpers (sudo-management.ts) ───────────────────────────
242
242
  { binary: "zenity", candidates: ["/usr/bin/zenity"] },
243
243
  { binary: "kdialog", candidates: ["/usr/bin/kdialog"] },
244
+ { binary: "setsid", candidates: ["/usr/bin/setsid", "/usr/sbin/setsid"] },
244
245
  { binary: "ksshaskpass", candidates: ["/usr/bin/ksshaskpass"] },
245
246
  { binary: "lxqt-sudo", candidates: ["/usr/bin/lxqt-sudo"] },
246
247
  // ── DNS security ───────────────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-validator.d.ts","sourceRoot":"","sources":["../../src/core/dependency-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgCH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gCAAgC;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,sCAAsC;IACtC,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,+CAA+C;IAC/C,eAAe,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxE,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,qDAAqD;IACrD,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gCAAgC;IAChC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,0DAA0D;IAC1D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,wCAAwC;IACxC,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAsCD;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AA+CD;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAqJzE;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CA0EvB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIxE;AAID;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAmDvE"}
1
+ {"version":3,"file":"dependency-validator.d.ts","sourceRoot":"","sources":["../../src/core/dependency-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgCH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gCAAgC;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,sCAAsC;IACtC,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,+CAA+C;IAC/C,eAAe,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxE,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,qDAAqD;IACrD,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gCAAgC;IAChC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,0DAA0D;IAC1D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,wCAAwC;IACxC,aAAa,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAsCD;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AA+CD;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAsJzE;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,CA0EvB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIxE;AAID;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAmDvE"}
@@ -145,6 +145,7 @@ export async function validateAllDependencies() {
145
145
  const updateCmd = pkgMgr.updateCmd();
146
146
  console.error(`[dep-validator] Updating package lists via ${distro.packageManager}...`);
147
147
  await executeCommand({
148
+ toolName: "_internal",
148
149
  command: "sudo",
149
150
  args: updateCmd,
150
151
  timeout: 120_000,
@@ -396,7 +396,7 @@ function buildFirewallPersistenceConfig(distro) {
396
396
  return {
397
397
  packageName: "iptables-persistent",
398
398
  checkInstalledCmd: ["dpkg", "-l", "iptables-persistent"],
399
- installCmd: ["DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y", "iptables-persistent"],
399
+ installCmd: ["apt-get", "install", "-y", "iptables-persistent"],
400
400
  serviceName: "netfilter-persistent",
401
401
  enableCmd: ["systemctl", "enable", "netfilter-persistent"],
402
402
  saveCmd: ["netfilter-persistent", "save"],
@@ -21,7 +21,7 @@ function parseOsRelease(content) {
21
21
  return result;
22
22
  }
23
23
  async function binaryExists(name) {
24
- const result = await executeCommand({ command: "which", args: [name], timeout: 5000 });
24
+ const result = await executeCommand({ toolName: "_internal", command: "which", args: [name], timeout: 5000 });
25
25
  return result.exitCode === 0;
26
26
  }
27
27
  async function detectWsl() {
@@ -95,8 +95,8 @@ export async function detectDistro() {
95
95
  if (process.platform === "darwin") {
96
96
  osFamily = "darwin";
97
97
  try {
98
- const productResult = await executeCommand({ command: "sw_vers", args: ["-productName"], timeout: 5000 });
99
- const versionResult = await executeCommand({ command: "sw_vers", args: ["-productVersion"], timeout: 5000 });
98
+ const productResult = await executeCommand({ toolName: "_internal", command: "sw_vers", args: ["-productName"], timeout: 5000 });
99
+ const versionResult = await executeCommand({ toolName: "_internal", command: "sw_vers", args: ["-productVersion"], timeout: 5000 });
100
100
  if (productResult.exitCode === 0) {
101
101
  id = "macos";
102
102
  name = productResult.stdout.trim();
@@ -130,7 +130,7 @@ export async function detectDistro() {
130
130
  // lsb_release
131
131
  if (id === "unknown") {
132
132
  try {
133
- const result = await executeCommand({ command: "lsb_release", args: ["-a"], timeout: 5000 });
133
+ const result = await executeCommand({ toolName: "_internal", command: "lsb_release", args: ["-a"], timeout: 5000 });
134
134
  if (result.exitCode === 0) {
135
135
  for (const line of result.stdout.split("\n")) {
136
136
  if (line.startsWith("Distributor ID:"))
@@ -442,7 +442,7 @@ export async function hasTPM() {
442
442
  }
443
443
  export async function hasSecureBoot() {
444
444
  return safeCap(async () => {
445
- const r = await executeCommand({ command: "mokutil", args: ["--sb-state"], timeout: 5000 });
445
+ const r = await executeCommand({ toolName: "_internal", command: "mokutil", args: ["--sb-state"], timeout: 5000 });
446
446
  return r.exitCode === 0 && r.stdout.toLowerCase().includes("secureboot enabled");
447
447
  });
448
448
  }
@@ -16,8 +16,8 @@ export interface ExecuteOptions {
16
16
  stdin?: string | Buffer;
17
17
  /** Maximum output buffer size in bytes */
18
18
  maxBuffer?: number;
19
- /** Tool name for timeout lookup */
20
- toolName?: string;
19
+ /** Tool name for rate-limiting and timeout lookup (REQUIRED) */
20
+ toolName: string;
21
21
  /** Skip automatic sudo credential injection (used internally) */
22
22
  skipSudoInjection?: boolean;
23
23
  }
@@ -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,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;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,CAmNxB"}
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;CAC7B;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,CAmNxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/core/installer.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,QAAQ,GACR,gBAAgB,GAChB,YAAY,GACZ,WAAW,GACX,SAAS,GACT,WAAW,GACX,WAAW,GACX,YAAY,GACZ,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,EAAE,YAAY,CAAC;IACvB,2BAA2B;IAC3B,QAAQ,EAAE,YAAY,CAAC;IACvB,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,eAAe,EAk6B5C,CAAC;AAYF;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8ClE;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC,CAkB5B;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,aAAa,CAAC,CAwDxB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,QAAQ,CAAC,EAAE,YAAY,EACvB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,aAAa,EAAE,CAAC,CAgC1B"}
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/core/installer.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,QAAQ,GACR,gBAAgB,GAChB,YAAY,GACZ,WAAW,GACX,SAAS,GACT,WAAW,GACX,WAAW,GACX,YAAY,GACZ,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,EAAE,YAAY,CAAC;IACvB,2BAA2B;IAC3B,QAAQ,EAAE,YAAY,CAAC;IACvB,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,eAAe,EAk6B5C,CAAC;AAYF;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgDlE;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC,CAkB5B;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,aAAa,CAAC,CA0DxB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,QAAQ,CAAC,EAAE,YAAY,EACvB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,aAAa,EAAE,CAAC,CAgC1B"}
@@ -964,6 +964,7 @@ export async function checkTool(binary) {
964
964
  // Try to get version
965
965
  let version;
966
966
  const versionResult = await executeCommand({
967
+ toolName: "_internal",
967
968
  command: binary,
968
969
  args: ["--version"],
969
970
  timeout: 5000,
@@ -975,6 +976,7 @@ export async function checkTool(binary) {
975
976
  else {
976
977
  // Some tools use -v or -V instead
977
978
  const altResult = await executeCommand({
979
+ toolName: "_internal",
978
980
  command: binary,
979
981
  args: ["-V"],
980
982
  timeout: 5000,
@@ -1037,6 +1039,7 @@ export async function installTool(tool) {
1037
1039
  // Run package manager update first
1038
1040
  const updateCmd = getUpdateCommand(pkgManager);
1039
1041
  await executeCommand({
1042
+ toolName: "_internal",
1040
1043
  command: updateCmd[0],
1041
1044
  args: updateCmd.slice(1),
1042
1045
  timeout: 120_000,
@@ -1044,6 +1047,7 @@ export async function installTool(tool) {
1044
1047
  // Install the package
1045
1048
  const installCmd = getInstallCommand(pkgManager, pkgName);
1046
1049
  const result = await executeCommand({
1050
+ toolName: "_internal",
1047
1051
  command: installCmd[0],
1048
1052
  args: installCmd.slice(1),
1049
1053
  timeout: 300_000,
@@ -7,6 +7,11 @@
7
7
  * Supports standard log levels plus a `security` level for security-relevant
8
8
  * events (authentication, privilege escalation, policy violations).
9
9
  *
10
+ * Optional file-based logging with size-based rotation via:
11
+ * DEFENSE_MCP_LOG_FILE=/path/to/logfile.json
12
+ * DEFENSE_MCP_LOG_MAX_SIZE=10485760 (10 MB default)
13
+ * DEFENSE_MCP_LOG_MAX_FILES=5 (keep 5 rotated files)
14
+ *
10
15
  * @module logger
11
16
  * @see CICD-027
12
17
  */
@@ -33,6 +38,9 @@ export interface LogEntry {
33
38
  * Uses stderr so log output doesn't interfere with MCP protocol messages
34
39
  * on stdout (StdioServerTransport).
35
40
  *
41
+ * Optionally writes to a file with automatic size-based rotation when
42
+ * DEFENSE_MCP_LOG_FILE is set.
43
+ *
36
44
  * Usage:
37
45
  * ```typescript
38
46
  * import { logger } from './logger.js';
@@ -43,6 +51,9 @@ export interface LogEntry {
43
51
  */
44
52
  export declare class Logger {
45
53
  private minLevel;
54
+ private logFile;
55
+ private maxFileSize;
56
+ private maxFiles;
46
57
  constructor(minLevel?: LogLevel);
47
58
  /**
48
59
  * Read the minimum log level from `DEFENSE_MCP_LOG_LEVEL` env var.
@@ -51,6 +62,11 @@ export declare class Logger {
51
62
  private parseEnvLevel;
52
63
  /** Check whether a message at `level` should be emitted. */
53
64
  private shouldLog;
65
+ /**
66
+ * Write a log line to the file, with size-based rotation.
67
+ * This is best-effort — file write failures don't throw.
68
+ */
69
+ private writeToFile;
54
70
  /**
55
71
  * Emit a structured log entry as a single JSON line to stderr.
56
72
  *
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,qEAAqE;AACrE,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAYxE,wDAAwD;AACxD,MAAM,WAAW,QAAQ;IACvB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,KAAK,EAAE,QAAQ,CAAC;IAChB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAID;;;;;;;;;;;;;GAaG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAW;gBAEf,QAAQ,CAAC,EAAE,QAAQ;IAI/B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAQrB,4DAA4D;IAC5D,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,GAAG,CACD,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAgBP,iCAAiC;IACjC,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP,iCAAiC;IACjC,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP,mCAAmC;IACnC,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP,kCAAkC;IAClC,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP;;;;;;;;;;OAUG;IACH,QAAQ,CACN,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B,yCAAyC;IACzC,QAAQ,IAAI,QAAQ;CAGrB;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,QAAe,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,qEAAqE;AACrE,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAYxE,wDAAwD;AACxD,MAAM,WAAW,QAAQ;IACvB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,KAAK,EAAE,QAAQ,CAAC;IAChB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AA6CD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,CAAC,EAAE,QAAQ;IAmB/B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAQrB,4DAA4D;IAC5D,OAAO,CAAC,SAAS;IAIjB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAkBnB;;;;;;;;OAQG;IACH,GAAG,CACD,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAoBP,iCAAiC;IACjC,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP,iCAAiC;IACjC,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP,mCAAmC;IACnC,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP,kCAAkC;IAClC,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP;;;;;;;;;;OAUG;IACH,QAAQ,CACN,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI;IAIP;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B,yCAAyC;IACzC,QAAQ,IAAI,QAAQ;CAGrB;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,QAAe,CAAC"}
@@ -7,9 +7,16 @@
7
7
  * Supports standard log levels plus a `security` level for security-relevant
8
8
  * events (authentication, privilege escalation, policy violations).
9
9
  *
10
+ * Optional file-based logging with size-based rotation via:
11
+ * DEFENSE_MCP_LOG_FILE=/path/to/logfile.json
12
+ * DEFENSE_MCP_LOG_MAX_SIZE=10485760 (10 MB default)
13
+ * DEFENSE_MCP_LOG_MAX_FILES=5 (keep 5 rotated files)
14
+ *
10
15
  * @module logger
11
16
  * @see CICD-027
12
17
  */
18
+ import { appendFileSync, statSync, renameSync, existsSync, unlinkSync, mkdirSync } from "node:fs";
19
+ import { dirname } from "node:path";
13
20
  /** Numeric severity for each log level (used for filtering). */
14
21
  const LOG_LEVEL_SEVERITY = {
15
22
  debug: 0,
@@ -19,6 +26,43 @@ const LOG_LEVEL_SEVERITY = {
19
26
  /** Security events always log regardless of level (severity 999). */
20
27
  security: 999,
21
28
  };
29
+ // ── Log Rotation ─────────────────────────────────────────────────────────────
30
+ /** Default max log file size: 10 MB */
31
+ const DEFAULT_MAX_SIZE = 10 * 1024 * 1024;
32
+ /** Default number of rotated files to keep */
33
+ const DEFAULT_MAX_FILES = 5;
34
+ /**
35
+ * Rotate a log file using numbered suffixes: log.json → log.json.1 → log.json.2 etc.
36
+ * Oldest files beyond maxFiles are deleted.
37
+ *
38
+ * @param filePath - The active log file path
39
+ * @param maxFiles - Maximum number of rotated files to keep
40
+ */
41
+ function rotateLogFile(filePath, maxFiles) {
42
+ try {
43
+ // Delete the oldest file if it would exceed maxFiles
44
+ const oldest = `${filePath}.${maxFiles}`;
45
+ if (existsSync(oldest)) {
46
+ unlinkSync(oldest);
47
+ }
48
+ // Shift existing rotated files: .4 → .5, .3 → .4, etc.
49
+ for (let i = maxFiles - 1; i >= 1; i--) {
50
+ const src = `${filePath}.${i}`;
51
+ const dst = `${filePath}.${i + 1}`;
52
+ if (existsSync(src)) {
53
+ renameSync(src, dst);
54
+ }
55
+ }
56
+ // Move the current log file to .1
57
+ if (existsSync(filePath)) {
58
+ renameSync(filePath, `${filePath}.1`);
59
+ }
60
+ }
61
+ catch {
62
+ // Best-effort rotation — don't crash the server if rotation fails
63
+ process.stderr.write(`[logger] WARNING: Log rotation failed for ${filePath}\n`);
64
+ }
65
+ }
22
66
  // ── Logger Class ─────────────────────────────────────────────────────────────
23
67
  /**
24
68
  * Structured logger that outputs JSON to stderr.
@@ -26,6 +70,9 @@ const LOG_LEVEL_SEVERITY = {
26
70
  * Uses stderr so log output doesn't interfere with MCP protocol messages
27
71
  * on stdout (StdioServerTransport).
28
72
  *
73
+ * Optionally writes to a file with automatic size-based rotation when
74
+ * DEFENSE_MCP_LOG_FILE is set.
75
+ *
29
76
  * Usage:
30
77
  * ```typescript
31
78
  * import { logger } from './logger.js';
@@ -36,8 +83,26 @@ const LOG_LEVEL_SEVERITY = {
36
83
  */
37
84
  export class Logger {
38
85
  minLevel;
86
+ logFile;
87
+ maxFileSize;
88
+ maxFiles;
39
89
  constructor(minLevel) {
40
90
  this.minLevel = minLevel ?? this.parseEnvLevel();
91
+ this.logFile = process.env.DEFENSE_MCP_LOG_FILE || null;
92
+ this.maxFileSize = parseInt(process.env.DEFENSE_MCP_LOG_MAX_SIZE || "", 10) || DEFAULT_MAX_SIZE;
93
+ this.maxFiles = parseInt(process.env.DEFENSE_MCP_LOG_MAX_FILES || "", 10) || DEFAULT_MAX_FILES;
94
+ // Ensure log directory exists if file logging is enabled
95
+ if (this.logFile) {
96
+ try {
97
+ const dir = dirname(this.logFile);
98
+ mkdirSync(dir, { recursive: true });
99
+ }
100
+ catch {
101
+ // Fall back to stderr-only if directory creation fails
102
+ process.stderr.write(`[logger] WARNING: Cannot create log directory for ${this.logFile}, falling back to stderr-only\n`);
103
+ this.logFile = null;
104
+ }
105
+ }
41
106
  }
42
107
  /**
43
108
  * Read the minimum log level from `DEFENSE_MCP_LOG_LEVEL` env var.
@@ -54,6 +119,27 @@ export class Logger {
54
119
  shouldLog(level) {
55
120
  return LOG_LEVEL_SEVERITY[level] >= LOG_LEVEL_SEVERITY[this.minLevel];
56
121
  }
122
+ /**
123
+ * Write a log line to the file, with size-based rotation.
124
+ * This is best-effort — file write failures don't throw.
125
+ */
126
+ writeToFile(line) {
127
+ if (!this.logFile)
128
+ return;
129
+ try {
130
+ // Check if rotation is needed before writing
131
+ if (existsSync(this.logFile)) {
132
+ const stats = statSync(this.logFile);
133
+ if (stats.size >= this.maxFileSize) {
134
+ rotateLogFile(this.logFile, this.maxFiles);
135
+ }
136
+ }
137
+ appendFileSync(this.logFile, line, { encoding: "utf-8" });
138
+ }
139
+ catch {
140
+ // Best-effort — don't crash the server on write failure
141
+ }
142
+ }
57
143
  /**
58
144
  * Emit a structured log entry as a single JSON line to stderr.
59
145
  *
@@ -75,7 +161,10 @@ export class Logger {
75
161
  ...(details !== undefined ? { details } : {}),
76
162
  };
77
163
  // Single-line JSON to stderr — safe for MCP stdio transport
78
- process.stderr.write(JSON.stringify(entry) + "\n");
164
+ const line = JSON.stringify(entry) + "\n";
165
+ process.stderr.write(line);
166
+ // Also write to file if configured (with rotation)
167
+ this.writeToFile(line);
79
168
  }
80
169
  /** Log a debug-level message. */
81
170
  debug(component, action, message, details) {