defense-mcp-server 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +471 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/build/core/auto-installer.d.ts +102 -0
- package/build/core/auto-installer.d.ts.map +1 -0
- package/build/core/auto-installer.js +833 -0
- package/build/core/backup-manager.d.ts +63 -0
- package/build/core/backup-manager.d.ts.map +1 -0
- package/build/core/backup-manager.js +189 -0
- package/build/core/changelog.d.ts +75 -0
- package/build/core/changelog.d.ts.map +1 -0
- package/build/core/changelog.js +123 -0
- package/build/core/command-allowlist.d.ts +129 -0
- package/build/core/command-allowlist.d.ts.map +1 -0
- package/build/core/command-allowlist.js +849 -0
- package/build/core/config.d.ts +79 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +193 -0
- package/build/core/dependency-validator.d.ts +106 -0
- package/build/core/dependency-validator.d.ts.map +1 -0
- package/build/core/dependency-validator.js +405 -0
- package/build/core/distro-adapter.d.ts +177 -0
- package/build/core/distro-adapter.d.ts.map +1 -0
- package/build/core/distro-adapter.js +481 -0
- package/build/core/distro.d.ts +68 -0
- package/build/core/distro.d.ts.map +1 -0
- package/build/core/distro.js +457 -0
- package/build/core/encrypted-state.d.ts +76 -0
- package/build/core/encrypted-state.d.ts.map +1 -0
- package/build/core/encrypted-state.js +209 -0
- package/build/core/executor.d.ts +56 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +350 -0
- package/build/core/installer.d.ts +92 -0
- package/build/core/installer.d.ts.map +1 -0
- package/build/core/installer.js +1072 -0
- package/build/core/logger.d.ts +102 -0
- package/build/core/logger.d.ts.map +1 -0
- package/build/core/logger.js +132 -0
- package/build/core/parsers.d.ts +151 -0
- package/build/core/parsers.d.ts.map +1 -0
- package/build/core/parsers.js +479 -0
- package/build/core/policy-engine.d.ts +170 -0
- package/build/core/policy-engine.d.ts.map +1 -0
- package/build/core/policy-engine.js +656 -0
- package/build/core/preflight.d.ts +157 -0
- package/build/core/preflight.d.ts.map +1 -0
- package/build/core/preflight.js +638 -0
- package/build/core/privilege-manager.d.ts +108 -0
- package/build/core/privilege-manager.d.ts.map +1 -0
- package/build/core/privilege-manager.js +363 -0
- package/build/core/rate-limiter.d.ts +67 -0
- package/build/core/rate-limiter.d.ts.map +1 -0
- package/build/core/rate-limiter.js +129 -0
- package/build/core/rollback.d.ts +73 -0
- package/build/core/rollback.d.ts.map +1 -0
- package/build/core/rollback.js +278 -0
- package/build/core/safeguards.d.ts +58 -0
- package/build/core/safeguards.d.ts.map +1 -0
- package/build/core/safeguards.js +448 -0
- package/build/core/sanitizer.d.ts +118 -0
- package/build/core/sanitizer.d.ts.map +1 -0
- package/build/core/sanitizer.js +459 -0
- package/build/core/secure-fs.d.ts +67 -0
- package/build/core/secure-fs.d.ts.map +1 -0
- package/build/core/secure-fs.js +143 -0
- package/build/core/spawn-safe.d.ts +55 -0
- package/build/core/spawn-safe.d.ts.map +1 -0
- package/build/core/spawn-safe.js +146 -0
- package/build/core/sudo-guard.d.ts +145 -0
- package/build/core/sudo-guard.d.ts.map +1 -0
- package/build/core/sudo-guard.js +349 -0
- package/build/core/sudo-session.d.ts +100 -0
- package/build/core/sudo-session.d.ts.map +1 -0
- package/build/core/sudo-session.js +319 -0
- package/build/core/tool-dependencies.d.ts +61 -0
- package/build/core/tool-dependencies.d.ts.map +1 -0
- package/build/core/tool-dependencies.js +571 -0
- package/build/core/tool-registry.d.ts +111 -0
- package/build/core/tool-registry.d.ts.map +1 -0
- package/build/core/tool-registry.js +656 -0
- package/build/core/tool-wrapper.d.ts +73 -0
- package/build/core/tool-wrapper.d.ts.map +1 -0
- package/build/core/tool-wrapper.js +296 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +247 -0
- package/build/tools/access-control.d.ts +9 -0
- package/build/tools/access-control.d.ts.map +1 -0
- package/build/tools/access-control.js +1818 -0
- package/build/tools/api-security.d.ts +12 -0
- package/build/tools/api-security.d.ts.map +1 -0
- package/build/tools/api-security.js +901 -0
- package/build/tools/app-hardening.d.ts +11 -0
- package/build/tools/app-hardening.d.ts.map +1 -0
- package/build/tools/app-hardening.js +768 -0
- package/build/tools/backup.d.ts +8 -0
- package/build/tools/backup.d.ts.map +1 -0
- package/build/tools/backup.js +381 -0
- package/build/tools/cloud-security.d.ts +17 -0
- package/build/tools/cloud-security.d.ts.map +1 -0
- package/build/tools/cloud-security.js +739 -0
- package/build/tools/compliance.d.ts +10 -0
- package/build/tools/compliance.d.ts.map +1 -0
- package/build/tools/compliance.js +1225 -0
- package/build/tools/container-security.d.ts +14 -0
- package/build/tools/container-security.d.ts.map +1 -0
- package/build/tools/container-security.js +788 -0
- package/build/tools/deception.d.ts +13 -0
- package/build/tools/deception.d.ts.map +1 -0
- package/build/tools/deception.js +763 -0
- package/build/tools/dns-security.d.ts +93 -0
- package/build/tools/dns-security.d.ts.map +1 -0
- package/build/tools/dns-security.js +745 -0
- package/build/tools/drift-detection.d.ts +8 -0
- package/build/tools/drift-detection.d.ts.map +1 -0
- package/build/tools/drift-detection.js +326 -0
- package/build/tools/ebpf-security.d.ts +15 -0
- package/build/tools/ebpf-security.d.ts.map +1 -0
- package/build/tools/ebpf-security.js +294 -0
- package/build/tools/encryption.d.ts +9 -0
- package/build/tools/encryption.d.ts.map +1 -0
- package/build/tools/encryption.js +1667 -0
- package/build/tools/firewall.d.ts +9 -0
- package/build/tools/firewall.d.ts.map +1 -0
- package/build/tools/firewall.js +1398 -0
- package/build/tools/hardening.d.ts +10 -0
- package/build/tools/hardening.d.ts.map +1 -0
- package/build/tools/hardening.js +2654 -0
- package/build/tools/ids.d.ts +9 -0
- package/build/tools/ids.d.ts.map +1 -0
- package/build/tools/ids.js +624 -0
- package/build/tools/incident-response.d.ts +10 -0
- package/build/tools/incident-response.d.ts.map +1 -0
- package/build/tools/incident-response.js +1180 -0
- package/build/tools/logging.d.ts +12 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +454 -0
- package/build/tools/malware.d.ts +10 -0
- package/build/tools/malware.d.ts.map +1 -0
- package/build/tools/malware.js +532 -0
- package/build/tools/meta.d.ts +11 -0
- package/build/tools/meta.d.ts.map +1 -0
- package/build/tools/meta.js +2278 -0
- package/build/tools/network-defense.d.ts +12 -0
- package/build/tools/network-defense.d.ts.map +1 -0
- package/build/tools/network-defense.js +760 -0
- package/build/tools/patch-management.d.ts +3 -0
- package/build/tools/patch-management.d.ts.map +1 -0
- package/build/tools/patch-management.js +708 -0
- package/build/tools/process-security.d.ts +12 -0
- package/build/tools/process-security.d.ts.map +1 -0
- package/build/tools/process-security.js +784 -0
- package/build/tools/reporting.d.ts +11 -0
- package/build/tools/reporting.d.ts.map +1 -0
- package/build/tools/reporting.js +559 -0
- package/build/tools/secrets.d.ts +9 -0
- package/build/tools/secrets.d.ts.map +1 -0
- package/build/tools/secrets.js +596 -0
- package/build/tools/siem-integration.d.ts +18 -0
- package/build/tools/siem-integration.d.ts.map +1 -0
- package/build/tools/siem-integration.js +754 -0
- package/build/tools/sudo-management.d.ts +18 -0
- package/build/tools/sudo-management.d.ts.map +1 -0
- package/build/tools/sudo-management.js +737 -0
- package/build/tools/supply-chain-security.d.ts +8 -0
- package/build/tools/supply-chain-security.d.ts.map +1 -0
- package/build/tools/supply-chain-security.js +256 -0
- package/build/tools/threat-intel.d.ts +22 -0
- package/build/tools/threat-intel.d.ts.map +1 -0
- package/build/tools/threat-intel.js +749 -0
- package/build/tools/vulnerability-management.d.ts +11 -0
- package/build/tools/vulnerability-management.d.ts.map +1 -0
- package/build/tools/vulnerability-management.js +667 -0
- package/build/tools/waf.d.ts +12 -0
- package/build/tools/waf.d.ts.map +1 -0
- package/build/tools/waf.js +843 -0
- package/build/tools/wireless-security.d.ts +19 -0
- package/build/tools/wireless-security.d.ts.map +1 -0
- package/build/tools/wireless-security.js +826 -0
- package/build/tools/zero-trust-network.d.ts +8 -0
- package/build/tools/zero-trust-network.d.ts.map +1 -0
- package/build/tools/zero-trust-network.js +367 -0
- package/docs/SAFEGUARDS.md +518 -0
- package/docs/TOOLS-REFERENCE.md +665 -0
- package/package.json +87 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level safe process spawning for defense-mcp-server.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the foundational child process creation layer.
|
|
5
|
+
* It has NO dependencies on executor.ts, sudo-session.ts, or any module
|
|
6
|
+
* that could create circular imports.
|
|
7
|
+
*
|
|
8
|
+
* Dependencies: node:child_process, ./command-allowlist.js
|
|
9
|
+
*
|
|
10
|
+
* All child process creation outside of executor.ts should go through
|
|
11
|
+
* this module to ensure:
|
|
12
|
+
* 1. Command allowlist enforcement
|
|
13
|
+
* 2. shell: false always
|
|
14
|
+
* 3. Audit logging to stderr
|
|
15
|
+
*/
|
|
16
|
+
import { type SpawnOptions, type ExecFileSyncOptions, type ChildProcess } from "node:child_process";
|
|
17
|
+
export interface SpawnSafeOptions extends SpawnOptions {
|
|
18
|
+
}
|
|
19
|
+
export interface ExecFileSafeOptions extends ExecFileSyncOptions {
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Redact sensitive arguments from a command's argument array for safe logging.
|
|
23
|
+
*
|
|
24
|
+
* Rules:
|
|
25
|
+
* 1. If the command is "sudo", redact any argument immediately after `-S` (stdin password flag)
|
|
26
|
+
* 2. If any argument matches sensitive flag patterns (--password, --token, --key, --secret),
|
|
27
|
+
* the NEXT argument is replaced with `[REDACTED]`
|
|
28
|
+
* 3. If a sensitive flag uses `=` syntax (e.g. `--password=foo`), the value portion is redacted
|
|
29
|
+
*
|
|
30
|
+
* @param command - The command being executed
|
|
31
|
+
* @param args - The original arguments array
|
|
32
|
+
* @returns A new array safe for logging (original is not mutated)
|
|
33
|
+
*/
|
|
34
|
+
export declare function redactArgs(command: string, args: readonly string[]): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Spawn a child process safely with allowlist enforcement and shell: false.
|
|
37
|
+
* Returns a ChildProcess (async — listen on events for output).
|
|
38
|
+
*
|
|
39
|
+
* @param command - Bare binary name (e.g. "sudo") or absolute path
|
|
40
|
+
* @param args - Arguments to pass to the command
|
|
41
|
+
* @param options - SpawnOptions (shell is always forced to false)
|
|
42
|
+
* @throws {Error} If the command is not in the allowlist
|
|
43
|
+
*/
|
|
44
|
+
export declare function spawnSafe(command: string, args: string[], options?: SpawnSafeOptions): ChildProcess;
|
|
45
|
+
/**
|
|
46
|
+
* Execute a file synchronously with allowlist enforcement and shell: false.
|
|
47
|
+
*
|
|
48
|
+
* @param command - Bare binary name (e.g. "iptables") or absolute path
|
|
49
|
+
* @param args - Arguments to pass to the command
|
|
50
|
+
* @param options - ExecFileSyncOptions (shell is always forced to false)
|
|
51
|
+
* @returns stdout as Buffer (no encoding) or string (with encoding option)
|
|
52
|
+
* @throws {Error} If the command is not in the allowlist or the process exits non-zero
|
|
53
|
+
*/
|
|
54
|
+
export declare function execFileSafe(command: string, args: string[], options?: ExecFileSafeOptions): Buffer | string;
|
|
55
|
+
//# sourceMappingURL=spawn-safe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-safe.d.ts","sourceRoot":"","sources":["../../src/core/spawn-safe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAK5B,MAAM,WAAW,gBAAiB,SAAQ,YAAY;CAAG;AAEzD,MAAM,WAAW,mBAAoB,SAAQ,mBAAmB;CAAG;AAUnE;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAgC7E;AAID;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,gBAAgB,GACzB,YAAY,CAUd;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,GAAG,MAAM,CAiCjB"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level safe process spawning for defense-mcp-server.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the foundational child process creation layer.
|
|
5
|
+
* It has NO dependencies on executor.ts, sudo-session.ts, or any module
|
|
6
|
+
* that could create circular imports.
|
|
7
|
+
*
|
|
8
|
+
* Dependencies: node:child_process, ./command-allowlist.js
|
|
9
|
+
*
|
|
10
|
+
* All child process creation outside of executor.ts should go through
|
|
11
|
+
* this module to ensure:
|
|
12
|
+
* 1. Command allowlist enforcement
|
|
13
|
+
* 2. shell: false always
|
|
14
|
+
* 3. Audit logging to stderr
|
|
15
|
+
*/
|
|
16
|
+
import { spawn as nodeSpawn, execFileSync as nodeExecFileSync, } from "node:child_process";
|
|
17
|
+
import { resolveCommand } from "./command-allowlist.js";
|
|
18
|
+
// ── Argument Redaction ───────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* SECURITY (CORE-018): Patterns whose NEXT argument should be redacted in logs.
|
|
21
|
+
* Matches flags like --password, --token, --key, --secret (with = or space-separated values).
|
|
22
|
+
*/
|
|
23
|
+
const SENSITIVE_FLAG_RE = /^--?(password|token|key|secret|passphrase|credential|auth)$/i;
|
|
24
|
+
/**
|
|
25
|
+
* Redact sensitive arguments from a command's argument array for safe logging.
|
|
26
|
+
*
|
|
27
|
+
* Rules:
|
|
28
|
+
* 1. If the command is "sudo", redact any argument immediately after `-S` (stdin password flag)
|
|
29
|
+
* 2. If any argument matches sensitive flag patterns (--password, --token, --key, --secret),
|
|
30
|
+
* the NEXT argument is replaced with `[REDACTED]`
|
|
31
|
+
* 3. If a sensitive flag uses `=` syntax (e.g. `--password=foo`), the value portion is redacted
|
|
32
|
+
*
|
|
33
|
+
* @param command - The command being executed
|
|
34
|
+
* @param args - The original arguments array
|
|
35
|
+
* @returns A new array safe for logging (original is not mutated)
|
|
36
|
+
*/
|
|
37
|
+
export function redactArgs(command, args) {
|
|
38
|
+
const redacted = [...args];
|
|
39
|
+
const isSudo = command === "sudo" || command.endsWith("/sudo");
|
|
40
|
+
for (let i = 0; i < redacted.length; i++) {
|
|
41
|
+
const arg = redacted[i];
|
|
42
|
+
// Rule 1: For sudo, redact the argument after -S (stdin password)
|
|
43
|
+
if (isSudo && arg === "-S" && i + 1 < redacted.length) {
|
|
44
|
+
// Don't redact known sudo flags that follow -S
|
|
45
|
+
const next = redacted[i + 1];
|
|
46
|
+
if (next && !next.startsWith("-")) {
|
|
47
|
+
redacted[i + 1] = "[REDACTED]";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Rule 2: Redact argument after sensitive flags
|
|
51
|
+
if (SENSITIVE_FLAG_RE.test(arg) && i + 1 < redacted.length) {
|
|
52
|
+
redacted[i + 1] = "[REDACTED]";
|
|
53
|
+
}
|
|
54
|
+
// Rule 3: Redact value in --flag=value style for sensitive flags
|
|
55
|
+
const eqIdx = arg.indexOf("=");
|
|
56
|
+
if (eqIdx > 0) {
|
|
57
|
+
const flagPart = arg.substring(0, eqIdx);
|
|
58
|
+
if (SENSITIVE_FLAG_RE.test(flagPart)) {
|
|
59
|
+
redacted[i] = `${flagPart}=[REDACTED]`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return redacted;
|
|
64
|
+
}
|
|
65
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* Spawn a child process safely with allowlist enforcement and shell: false.
|
|
68
|
+
* Returns a ChildProcess (async — listen on events for output).
|
|
69
|
+
*
|
|
70
|
+
* @param command - Bare binary name (e.g. "sudo") or absolute path
|
|
71
|
+
* @param args - Arguments to pass to the command
|
|
72
|
+
* @param options - SpawnOptions (shell is always forced to false)
|
|
73
|
+
* @throws {Error} If the command is not in the allowlist
|
|
74
|
+
*/
|
|
75
|
+
export function spawnSafe(command, args, options) {
|
|
76
|
+
const resolvedCommand = resolveCommandSafe(command);
|
|
77
|
+
const safeOptions = {
|
|
78
|
+
...options,
|
|
79
|
+
shell: false, // ALWAYS false — non-negotiable
|
|
80
|
+
};
|
|
81
|
+
console.error(`[spawn-safe] ${resolvedCommand} ${redactArgs(command, args).join(" ")}`);
|
|
82
|
+
return nodeSpawn(resolvedCommand, args, safeOptions);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Execute a file synchronously with allowlist enforcement and shell: false.
|
|
86
|
+
*
|
|
87
|
+
* @param command - Bare binary name (e.g. "iptables") or absolute path
|
|
88
|
+
* @param args - Arguments to pass to the command
|
|
89
|
+
* @param options - ExecFileSyncOptions (shell is always forced to false)
|
|
90
|
+
* @returns stdout as Buffer (no encoding) or string (with encoding option)
|
|
91
|
+
* @throws {Error} If the command is not in the allowlist or the process exits non-zero
|
|
92
|
+
*/
|
|
93
|
+
export function execFileSafe(command, args, options) {
|
|
94
|
+
const resolvedCommand = resolveCommandSafe(command);
|
|
95
|
+
// SECURITY (CORE-011): Capture reference to any stdin input buffer for cleanup
|
|
96
|
+
const inputBuffer = options?.input && Buffer.isBuffer(options.input) ? options.input : null;
|
|
97
|
+
const safeOptions = {
|
|
98
|
+
...options,
|
|
99
|
+
shell: false, // ALWAYS false
|
|
100
|
+
timeout: options?.timeout ?? 120_000, // 120 second default for sync operations
|
|
101
|
+
};
|
|
102
|
+
console.error(`[spawn-safe] ${resolvedCommand} ${redactArgs(command, args).join(" ")}`);
|
|
103
|
+
try {
|
|
104
|
+
return nodeExecFileSync(resolvedCommand, args, safeOptions);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
// Provide user-friendly timeout message
|
|
108
|
+
if (err instanceof Error && "killed" in err && err.code === "ETIMEDOUT") {
|
|
109
|
+
const timeoutSec = Math.round(safeOptions.timeout / 1000);
|
|
110
|
+
throw new Error(`Command timed out after ${timeoutSec} seconds. ` +
|
|
111
|
+
`The target may be unreachable or the operation is taking too long. ` +
|
|
112
|
+
`Consider increasing KALI_DEFENSE_COMMAND_TIMEOUT (current: ${timeoutSec}s).`);
|
|
113
|
+
}
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
// SECURITY (CORE-011): Zero any stdin buffer that may contain sensitive data
|
|
118
|
+
// (e.g., passwords piped to commands). Guaranteed cleanup on all paths.
|
|
119
|
+
if (inputBuffer) {
|
|
120
|
+
inputBuffer.fill(0);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ── Internal helper ──────────────────────────────────────────────────────────
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a command through the allowlist, or throw.
|
|
127
|
+
*
|
|
128
|
+
* Every command MUST pass through the allowlist — there is no bypass mechanism.
|
|
129
|
+
*
|
|
130
|
+
* If `resolveCommand()` throws (e.g. allowlist not yet initialized at startup),
|
|
131
|
+
* falls back to checking `isAllowlisted()` which works even before
|
|
132
|
+
* `initializeAllowlist()` has been called.
|
|
133
|
+
*/
|
|
134
|
+
function resolveCommandSafe(command) {
|
|
135
|
+
try {
|
|
136
|
+
return resolveCommand(command);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// SECURITY (CORE-014): Do NOT fall back to bare command name when resolution fails.
|
|
140
|
+
// Using bare names allows PATH manipulation attacks. If resolveCommand() failed,
|
|
141
|
+
// the binary either isn't on the system or the allowlist isn't initialized.
|
|
142
|
+
// In either case, refuse to proceed.
|
|
143
|
+
console.error(`[spawn-safe] Command resolution failed for "${command}" — refusing bare-name fallback`);
|
|
144
|
+
throw new Error(`[spawn-safe] Command not in allowlist or not found on system: "${command}"`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SudoGuard — Central module for detecting permission failures and generating
|
|
3
|
+
* structured elevation prompts that instruct the AI client to ask the user
|
|
4
|
+
* for their sudo password.
|
|
5
|
+
*
|
|
6
|
+
* This module ensures that no MCP tool ever silently fails due to missing
|
|
7
|
+
* sudo privileges. Instead, failures are intercepted and converted into
|
|
8
|
+
* clear, actionable elevation prompts.
|
|
9
|
+
*
|
|
10
|
+
* ## Three Interception Layers
|
|
11
|
+
*
|
|
12
|
+
* 1. **Pre-flight** (tool-wrapper.ts): Tools with `sudo: "always"` are blocked
|
|
13
|
+
* before execution if no session is active. SudoGuard generates the prompt.
|
|
14
|
+
*
|
|
15
|
+
* 2. **Executor** (executor.ts): After command execution, the `permissionDenied`
|
|
16
|
+
* flag on {@link CommandResult} is set when stderr/exit code match known
|
|
17
|
+
* permission-denied patterns.
|
|
18
|
+
*
|
|
19
|
+
* 3. **Post-execution** (tool-wrapper.ts): If a tool handler's response
|
|
20
|
+
* indicates a permission error (detected via output text analysis),
|
|
21
|
+
* SudoGuard wraps it with an elevation prompt.
|
|
22
|
+
*
|
|
23
|
+
* ## Usage
|
|
24
|
+
*
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { SudoGuard } from './sudo-guard.js';
|
|
27
|
+
*
|
|
28
|
+
* // Check if command output indicates permission denied
|
|
29
|
+
* if (SudoGuard.isPermissionError(result.stderr, result.exitCode)) {
|
|
30
|
+
* return SudoGuard.createElevationPrompt('firewall_iptables_add');
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @module sudo-guard
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Structured MCP response content for an elevation prompt.
|
|
38
|
+
* Returned when a tool cannot proceed without sudo privileges.
|
|
39
|
+
*/
|
|
40
|
+
export interface ElevationPromptResponse {
|
|
41
|
+
content: Array<{
|
|
42
|
+
type: "text";
|
|
43
|
+
text: string;
|
|
44
|
+
}>;
|
|
45
|
+
isError: true;
|
|
46
|
+
_meta: {
|
|
47
|
+
/** Machine-readable tag for client-side detection */
|
|
48
|
+
elevationRequired: true;
|
|
49
|
+
/**
|
|
50
|
+
* Machine-readable flag that instructs the AI client to STOP its
|
|
51
|
+
* current workflow and ask the user for credentials before proceeding.
|
|
52
|
+
* The AI MUST NOT silently skip the tool or continue without elevation.
|
|
53
|
+
*/
|
|
54
|
+
haltWorkflow: true;
|
|
55
|
+
/** The tool that failed */
|
|
56
|
+
failedTool: string;
|
|
57
|
+
/** Why elevation is needed */
|
|
58
|
+
reason: string;
|
|
59
|
+
/** The tool to call for elevation */
|
|
60
|
+
elevationTool: "sudo_elevate";
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Static utility class for permission error detection and elevation prompt
|
|
65
|
+
* generation. All methods are stateless and can be called directly.
|
|
66
|
+
*/
|
|
67
|
+
export declare class SudoGuard {
|
|
68
|
+
/**
|
|
69
|
+
* Check whether command output (stderr and/or stdout) indicates a
|
|
70
|
+
* permission/privilege failure.
|
|
71
|
+
*
|
|
72
|
+
* Uses a combination of pattern matching against known error messages
|
|
73
|
+
* and exit code analysis. Pattern matching alone is authoritative —
|
|
74
|
+
* exit codes are used as supporting evidence only.
|
|
75
|
+
*
|
|
76
|
+
* @param output Combined stderr + stdout text to analyze
|
|
77
|
+
* @param exitCode The process exit code (optional, for confidence)
|
|
78
|
+
* @returns `true` if the output indicates a permission error
|
|
79
|
+
*/
|
|
80
|
+
static isPermissionError(output: string, exitCode?: number): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Create a structured MCP elevation prompt response.
|
|
83
|
+
*
|
|
84
|
+
* The response includes:
|
|
85
|
+
* - A clear human-readable message explaining what happened
|
|
86
|
+
* - Instructions to call `sudo_elevate` with the user's password
|
|
87
|
+
* - Machine-readable `_meta` for client-side automation
|
|
88
|
+
*
|
|
89
|
+
* @param toolName The tool that requires elevation
|
|
90
|
+
* @param reason Optional specific reason (from manifest or error output)
|
|
91
|
+
* @param originalError The original error message to include for context
|
|
92
|
+
*/
|
|
93
|
+
static createElevationPrompt(toolName: string, reason?: string, originalError?: string): ElevationPromptResponse;
|
|
94
|
+
/**
|
|
95
|
+
* Check if a tool handler's MCP response content indicates a permission
|
|
96
|
+
* error that occurred at runtime (after pre-flight passed).
|
|
97
|
+
*
|
|
98
|
+
* This catches `conditional` sudo tools and tools where the pre-flight
|
|
99
|
+
* check passed but the actual command still failed due to permissions.
|
|
100
|
+
*
|
|
101
|
+
* Examines the `content` array of the tool's response for text content
|
|
102
|
+
* matching permission error patterns.
|
|
103
|
+
*/
|
|
104
|
+
static isResponsePermissionError(response: Record<string, unknown> | undefined): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Extract the first text content string from an MCP response.
|
|
107
|
+
* Used to pass original error context to the elevation prompt.
|
|
108
|
+
*/
|
|
109
|
+
static extractResponseText(response: Record<string, unknown> | undefined): string | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* Check if the current process has an active sudo session that can
|
|
112
|
+
* be used for privileged operations.
|
|
113
|
+
*/
|
|
114
|
+
static hasActiveSession(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* SECURITY (CORE-006): Validate the SUDO_ASKPASS environment variable.
|
|
117
|
+
*
|
|
118
|
+
* Before trusting the SUDO_ASKPASS path, verify:
|
|
119
|
+
* 1. The file exists and is a regular file (not a symlink)
|
|
120
|
+
* 2. Ownership is root or the current user
|
|
121
|
+
* 3. Permissions are restrictive (0o700 or 0o500 — no world/group access)
|
|
122
|
+
*
|
|
123
|
+
* @returns `{ valid: true }` if safe, or `{ valid: false, reason: string }` if not
|
|
124
|
+
*/
|
|
125
|
+
static validateAskpass(): {
|
|
126
|
+
valid: boolean;
|
|
127
|
+
reason?: string;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* SECURITY (CORE-016): Validate an askpass helper path.
|
|
131
|
+
*
|
|
132
|
+
* Before trusting any askpass candidate, verify:
|
|
133
|
+
* 1. The file exists and is a regular file (not a symlink)
|
|
134
|
+
* 2. Ownership is root or the current user
|
|
135
|
+
* 3. Permissions are restrictive (no group/world access)
|
|
136
|
+
*
|
|
137
|
+
* @param askpassPath Absolute path to the askpass candidate
|
|
138
|
+
* @returns `{ valid: true }` if safe, or `{ valid: false, reason: string }` if not
|
|
139
|
+
*/
|
|
140
|
+
static validateAskpassPath(askpassPath: string): {
|
|
141
|
+
valid: boolean;
|
|
142
|
+
reason?: string;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=sudo-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sudo-guard.d.ts","sourceRoot":"","sources":["../../src/core/sudo-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAqFH;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,EAAE,IAAI,CAAC;IACd,KAAK,EAAE;QACL,qDAAqD;QACrD,iBAAiB,EAAE,IAAI,CAAC;QACxB;;;;WAIG;QACH,YAAY,EAAE,IAAI,CAAC;QACnB,2BAA2B;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,8BAA8B;QAC9B,MAAM,EAAE,MAAM,CAAC;QACf,qCAAqC;QACrC,aAAa,EAAE,cAAc,CAAC;KAC/B,CAAC;CACH;AAID;;;GAGG;AACH,qBAAa,SAAS;IACpB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAqBpE;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,qBAAqB,CAC1B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,uBAAuB;IAqE1B;;;;;;;;;OASG;IACH,MAAM,CAAC,yBAAyB,CAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC5C,OAAO;IA4BV;;;OAGG;IACH,MAAM,CAAC,mBAAmB,CACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC5C,MAAM,GAAG,SAAS;IAoBrB;;;OAGG;IACH,MAAM,CAAC,gBAAgB,IAAI,OAAO;IAIlC;;;;;;;;;OASG;IACH,MAAM,CAAC,eAAe,IAAI;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAU7D;;;;;;;;;;OAUG;IACH,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;CAiDrF"}
|