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.
- package/README.md +1 -1
- package/build/core/auto-installer.d.ts.map +1 -1
- package/build/core/auto-installer.js +51 -0
- package/build/core/changelog.d.ts +44 -0
- package/build/core/changelog.d.ts.map +1 -1
- package/build/core/changelog.js +76 -1
- package/build/core/command-allowlist.d.ts.map +1 -1
- package/build/core/command-allowlist.js +1 -0
- package/build/core/dependency-validator.d.ts.map +1 -1
- package/build/core/dependency-validator.js +1 -0
- package/build/core/distro-adapter.js +1 -1
- package/build/core/distro.js +5 -5
- package/build/core/executor.d.ts +2 -2
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/installer.d.ts.map +1 -1
- package/build/core/installer.js +4 -0
- package/build/core/logger.d.ts +16 -0
- package/build/core/logger.d.ts.map +1 -1
- package/build/core/logger.js +90 -1
- package/build/core/metrics.d.ts +74 -0
- package/build/core/metrics.d.ts.map +1 -0
- package/build/core/metrics.js +97 -0
- package/build/core/policy-engine.d.ts.map +1 -1
- package/build/core/policy-engine.js +1 -0
- package/build/core/rollback.d.ts.map +1 -1
- package/build/core/rollback.js +4 -0
- package/build/core/safeguards.d.ts.map +1 -1
- package/build/core/safeguards.js +3 -1
- package/build/core/sanitizer.d.ts.map +1 -1
- package/build/core/sanitizer.js +22 -0
- package/build/core/sudo-guard.js +3 -3
- package/build/core/tool-dependencies.d.ts.map +1 -1
- package/build/core/tool-dependencies.js +2 -1
- package/build/index.js +9 -10
- package/build/tools/app-hardening.d.ts.map +1 -1
- package/build/tools/app-hardening.js +8 -3
- package/build/tools/compliance.d.ts.map +1 -1
- package/build/tools/compliance.js +42 -8
- package/build/tools/container-security.d.ts +1 -0
- package/build/tools/container-security.d.ts.map +1 -1
- package/build/tools/container-security.js +89 -16
- package/build/tools/drift-detection.d.ts +6 -2
- package/build/tools/drift-detection.d.ts.map +1 -1
- package/build/tools/drift-detection.js +7 -18
- package/build/tools/ebpf-security.d.ts.map +1 -1
- package/build/tools/ebpf-security.js +11 -3
- package/build/tools/firewall.d.ts.map +1 -1
- package/build/tools/firewall.js +28 -6
- package/build/tools/hardening.d.ts.map +1 -1
- package/build/tools/hardening.js +18 -3
- package/build/tools/integrity.d.ts.map +1 -1
- package/build/tools/integrity.js +5 -0
- package/build/tools/meta.d.ts.map +1 -1
- package/build/tools/meta.js +58 -24
- package/build/tools/patch-management.js +9 -9
- package/build/tools/reporting.d.ts +6 -2
- package/build/tools/reporting.d.ts.map +1 -1
- package/build/tools/reporting.js +7 -18
- package/build/tools/secrets.d.ts.map +1 -1
- package/build/tools/secrets.js +4 -2
- package/build/tools/siem-integration.d.ts +5 -2
- package/build/tools/siem-integration.d.ts.map +1 -1
- package/build/tools/siem-integration.js +6 -18
- package/build/tools/sudo-management.d.ts.map +1 -1
- package/build/tools/sudo-management.js +123 -147
- package/build/tools/supply-chain-security.d.ts.map +1 -1
- package/build/tools/supply-chain-security.js +10 -4
- package/build/tools/threat-intel.d.ts.map +1 -1
- package/build/tools/threat-intel.js +19 -2
- package/build/tools/zero-trust-network.d.ts.map +1 -1
- package/build/tools/zero-trust-network.js +15 -6
- package/package.json +11 -3
- package/build/tools/ids.d.ts +0 -2
- package/build/tools/ids.d.ts.map +0 -1
- 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 **
|
|
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;
|
|
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;
|
|
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"}
|
package/build/core/changelog.js
CHANGED
|
@@ -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;
|
|
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,
|
|
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: ["
|
|
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"],
|
package/build/core/distro.js
CHANGED
|
@@ -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
|
}
|
package/build/core/executor.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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"}
|
package/build/core/installer.js
CHANGED
|
@@ -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,
|
package/build/core/logger.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/build/core/logger.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|