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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RollbackManager — singleton that tracks system changes and provides
|
|
3
|
+
* rollback capability for file, sysctl, service, and firewall modifications.
|
|
4
|
+
*/
|
|
5
|
+
export type ChangeType = "file" | "sysctl" | "service" | "firewall";
|
|
6
|
+
/**
|
|
7
|
+
* Structured rollback command — stores the command and args as separate fields
|
|
8
|
+
* to avoid reconstructing them from a string (which is an injection risk).
|
|
9
|
+
*/
|
|
10
|
+
export interface RollbackCommand {
|
|
11
|
+
command: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ChangeRecord {
|
|
15
|
+
id: string;
|
|
16
|
+
operationId: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
type: ChangeType;
|
|
19
|
+
target: string;
|
|
20
|
+
originalValue: string;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
rolledBack: boolean;
|
|
23
|
+
/** Optional reference to a changelog entry ID for cross-referencing */
|
|
24
|
+
changelogRef?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Structured rollback command for firewall-type changes.
|
|
27
|
+
* Preferred over parsing originalValue via string splitting.
|
|
28
|
+
* When present, this is used instead of originalValue for rollback execution.
|
|
29
|
+
*/
|
|
30
|
+
rollbackCommand?: RollbackCommand;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Versioned rollback state file format.
|
|
34
|
+
* Old files stored a bare array; new files use this envelope.
|
|
35
|
+
*/
|
|
36
|
+
export interface RollbackState {
|
|
37
|
+
version: 1;
|
|
38
|
+
changes: ChangeRecord[];
|
|
39
|
+
}
|
|
40
|
+
export declare class RollbackManager {
|
|
41
|
+
private static instance;
|
|
42
|
+
private readonly storePath;
|
|
43
|
+
private readonly sessionId;
|
|
44
|
+
private changes;
|
|
45
|
+
private constructor();
|
|
46
|
+
/** Get the singleton instance. */
|
|
47
|
+
static getInstance(): RollbackManager;
|
|
48
|
+
/** Persist state to disk in versioned format. */
|
|
49
|
+
private save;
|
|
50
|
+
/**
|
|
51
|
+
* Track a change for later rollback.
|
|
52
|
+
* @param changelogRef Optional reference to a changelog entry ID
|
|
53
|
+
* @param rollbackCmd Optional structured rollback command (preferred for firewall changes)
|
|
54
|
+
*/
|
|
55
|
+
trackChange(operationId: string, type: ChangeType, target: string, originalValue: string, changelogRef?: string, rollbackCmd?: RollbackCommand): void;
|
|
56
|
+
/**
|
|
57
|
+
* Rollback a single operation by its operation ID.
|
|
58
|
+
*/
|
|
59
|
+
rollback(operationId: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Rollback all changes from the current session.
|
|
62
|
+
*/
|
|
63
|
+
rollbackSession(sessionId: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* List all tracked changes.
|
|
66
|
+
*/
|
|
67
|
+
listChanges(): ChangeRecord[];
|
|
68
|
+
/** Get the current session ID. */
|
|
69
|
+
getSessionId(): string;
|
|
70
|
+
/** Rollback a single change record. */
|
|
71
|
+
private rollbackRecord;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=rollback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../../src/core/rollback.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+EH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAEpE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAID,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,OAAO,CAAsB;IAErC,OAAO;IAwBP,kCAAkC;IAClC,MAAM,CAAC,WAAW,IAAI,eAAe;IAOrC,iDAAiD;IACjD,OAAO,CAAC,IAAI;IAYZ;;;;OAIG;IACH,WAAW,CACT,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,eAAe,GAC5B,IAAI;IAmBP;;OAEG;IACG,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBlD;;OAEG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvD;;OAEG;IACH,WAAW,IAAI,YAAY,EAAE;IAM7B,kCAAkC;IAClC,YAAY,IAAI,MAAM;IAItB,uCAAuC;YACzB,cAAc;CA2G7B"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RollbackManager — singleton that tracks system changes and provides
|
|
3
|
+
* rollback capability for file, sysctl, service, and firewall modifications.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join, dirname } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { secureWriteFileSync, secureMkdirSync, secureCopyFileSync } from "./secure-fs.js";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { executeCommand } from "./executor.js";
|
|
11
|
+
import { isAllowlisted } from "./command-allowlist.js";
|
|
12
|
+
// ── Validation Constants ─────────────────────────────────────────────────────
|
|
13
|
+
/** Control characters regex — matches dangerous non-printable characters */
|
|
14
|
+
const CONTROL_CHAR_RE = /[\x00-\x08\x0e-\x1f\x7f]/;
|
|
15
|
+
/**
|
|
16
|
+
* Safe pattern for service names (systemd unit names).
|
|
17
|
+
* Allows alphanumeric, @, dots, underscores, hyphens, colons.
|
|
18
|
+
*/
|
|
19
|
+
const SAFE_SERVICE_NAME_RE = /^[a-zA-Z0-9@._:-]+$/;
|
|
20
|
+
/**
|
|
21
|
+
* Allowed commands for firewall rollback.
|
|
22
|
+
* Only specific firewall binaries are permitted — never shell interpreters.
|
|
23
|
+
* This is defense-in-depth on top of the general command allowlist.
|
|
24
|
+
*/
|
|
25
|
+
const ALLOWED_FIREWALL_COMMANDS = new Set([
|
|
26
|
+
"iptables", "ip6tables", "iptables-restore", "ip6tables-restore",
|
|
27
|
+
"iptables-save", "ip6tables-save", "nft", "ufw", "netfilter-persistent",
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* Validate a rollback argument for injection safety.
|
|
31
|
+
* Rejects null bytes, control characters, and excessively long values.
|
|
32
|
+
*
|
|
33
|
+
* @param arg The argument string to validate
|
|
34
|
+
* @param index The argument index (for error messages)
|
|
35
|
+
* @param label Human-readable label (e.g., "Firewall rollback")
|
|
36
|
+
* @throws {Error} If validation fails
|
|
37
|
+
*/
|
|
38
|
+
function validateRollbackArg(arg, index, label) {
|
|
39
|
+
if (typeof arg !== "string") {
|
|
40
|
+
throw new Error(`[rollback] ${label} argument at index ${index} is not a string`);
|
|
41
|
+
}
|
|
42
|
+
if (arg.length > 512) {
|
|
43
|
+
throw new Error(`[rollback] ${label} argument at index ${index} is too long (${arg.length} chars, max 512)`);
|
|
44
|
+
}
|
|
45
|
+
if (arg.includes("\0")) {
|
|
46
|
+
throw new Error(`[rollback] ${label} argument at index ${index} contains null bytes`);
|
|
47
|
+
}
|
|
48
|
+
if (CONTROL_CHAR_RE.test(arg)) {
|
|
49
|
+
throw new Error(`[rollback] ${label} argument at index ${index} contains control characters`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validate that a command is a recognized firewall tool.
|
|
54
|
+
* Extracts the bare binary name from an absolute path if needed.
|
|
55
|
+
*
|
|
56
|
+
* @param command The command (bare name or absolute path)
|
|
57
|
+
* @throws {Error} If the command is not a recognized firewall tool
|
|
58
|
+
*/
|
|
59
|
+
function validateFirewallCommand(command) {
|
|
60
|
+
const bareCommand = command.startsWith("/")
|
|
61
|
+
? (command.split("/").pop() ?? command)
|
|
62
|
+
: command;
|
|
63
|
+
if (!ALLOWED_FIREWALL_COMMANDS.has(bareCommand)) {
|
|
64
|
+
throw new Error(`[rollback] '${command}' is not a recognized firewall command for rollback`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ── RollbackManager ──────────────────────────────────────────────────────────
|
|
68
|
+
export class RollbackManager {
|
|
69
|
+
static instance = null;
|
|
70
|
+
storePath;
|
|
71
|
+
sessionId;
|
|
72
|
+
changes = [];
|
|
73
|
+
constructor() {
|
|
74
|
+
this.sessionId = randomUUID();
|
|
75
|
+
const storeDir = join(homedir(), ".kali-defense");
|
|
76
|
+
this.storePath = join(storeDir, "rollback-state.json");
|
|
77
|
+
// Load existing state (with migration from old bare-array format)
|
|
78
|
+
try {
|
|
79
|
+
if (existsSync(this.storePath)) {
|
|
80
|
+
const raw = readFileSync(this.storePath, "utf-8");
|
|
81
|
+
const parsed = JSON.parse(raw);
|
|
82
|
+
// Handle old format (bare array)
|
|
83
|
+
if (Array.isArray(parsed)) {
|
|
84
|
+
this.changes = parsed;
|
|
85
|
+
}
|
|
86
|
+
// Handle versioned format
|
|
87
|
+
else if (parsed && typeof parsed === "object" && Array.isArray(parsed.changes)) {
|
|
88
|
+
this.changes = parsed.changes;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
this.changes = [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Get the singleton instance. */
|
|
97
|
+
static getInstance() {
|
|
98
|
+
if (!RollbackManager.instance) {
|
|
99
|
+
RollbackManager.instance = new RollbackManager();
|
|
100
|
+
}
|
|
101
|
+
return RollbackManager.instance;
|
|
102
|
+
}
|
|
103
|
+
/** Persist state to disk in versioned format. */
|
|
104
|
+
save() {
|
|
105
|
+
try {
|
|
106
|
+
const state = {
|
|
107
|
+
version: 1,
|
|
108
|
+
changes: this.changes,
|
|
109
|
+
};
|
|
110
|
+
secureWriteFileSync(this.storePath, JSON.stringify(state, null, 2), "utf-8");
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.error(`[rollback] Failed to save state: ${err instanceof Error ? err.message : String(err)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Track a change for later rollback.
|
|
118
|
+
* @param changelogRef Optional reference to a changelog entry ID
|
|
119
|
+
* @param rollbackCmd Optional structured rollback command (preferred for firewall changes)
|
|
120
|
+
*/
|
|
121
|
+
trackChange(operationId, type, target, originalValue, changelogRef, rollbackCmd) {
|
|
122
|
+
const record = {
|
|
123
|
+
id: randomUUID(),
|
|
124
|
+
operationId,
|
|
125
|
+
sessionId: this.sessionId,
|
|
126
|
+
type,
|
|
127
|
+
target,
|
|
128
|
+
originalValue,
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
rolledBack: false,
|
|
131
|
+
...(changelogRef !== undefined ? { changelogRef } : {}),
|
|
132
|
+
...(rollbackCmd !== undefined ? { rollbackCommand: rollbackCmd } : {}),
|
|
133
|
+
};
|
|
134
|
+
this.changes.push(record);
|
|
135
|
+
this.save();
|
|
136
|
+
console.error(`[rollback] Tracked ${type} change on ${target} (op: ${operationId})`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Rollback a single operation by its operation ID.
|
|
140
|
+
*/
|
|
141
|
+
async rollback(operationId) {
|
|
142
|
+
const records = this.changes.filter((c) => c.operationId === operationId && !c.rolledBack);
|
|
143
|
+
if (records.length === 0) {
|
|
144
|
+
throw new Error(`No pending changes found for operation: ${operationId}`);
|
|
145
|
+
}
|
|
146
|
+
// Rollback in reverse order
|
|
147
|
+
for (const record of records.reverse()) {
|
|
148
|
+
await this.rollbackRecord(record);
|
|
149
|
+
}
|
|
150
|
+
this.save();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Rollback all changes from the current session.
|
|
154
|
+
*/
|
|
155
|
+
async rollbackSession(sessionId) {
|
|
156
|
+
const records = this.changes.filter((c) => c.sessionId === sessionId && !c.rolledBack);
|
|
157
|
+
if (records.length === 0) {
|
|
158
|
+
throw new Error(`No pending changes found for session: ${sessionId}`);
|
|
159
|
+
}
|
|
160
|
+
for (const record of records.reverse()) {
|
|
161
|
+
await this.rollbackRecord(record);
|
|
162
|
+
}
|
|
163
|
+
this.save();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* List all tracked changes.
|
|
167
|
+
*/
|
|
168
|
+
listChanges() {
|
|
169
|
+
return [...this.changes].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
170
|
+
}
|
|
171
|
+
/** Get the current session ID. */
|
|
172
|
+
getSessionId() {
|
|
173
|
+
return this.sessionId;
|
|
174
|
+
}
|
|
175
|
+
/** Rollback a single change record. */
|
|
176
|
+
async rollbackRecord(record) {
|
|
177
|
+
try {
|
|
178
|
+
switch (record.type) {
|
|
179
|
+
case "file": {
|
|
180
|
+
// originalValue is the backup path
|
|
181
|
+
if (existsSync(record.originalValue)) {
|
|
182
|
+
secureMkdirSync(dirname(record.target));
|
|
183
|
+
secureCopyFileSync(record.originalValue, record.target);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
console.error(`[rollback] Backup file missing: ${record.originalValue}`);
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case "sysctl": {
|
|
191
|
+
// Validate that sysctl is in the allowlist before executing
|
|
192
|
+
if (!isAllowlisted("sysctl")) {
|
|
193
|
+
throw new Error("[rollback] sysctl is not in the command allowlist — refusing to execute");
|
|
194
|
+
}
|
|
195
|
+
// Validate sysctl key: must be dotted identifiers (e.g. net.ipv4.ip_forward)
|
|
196
|
+
if (!/^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)+$/.test(record.target)) {
|
|
197
|
+
throw new Error(`[rollback] Invalid sysctl key: ${record.target}`);
|
|
198
|
+
}
|
|
199
|
+
// Validate sysctl value: must not contain shell metacharacters
|
|
200
|
+
if (/[;&|`$(){}[\]\\<>!#~]/.test(record.originalValue)) {
|
|
201
|
+
throw new Error(`[rollback] Invalid sysctl value (contains shell metacharacters): ${record.originalValue}`);
|
|
202
|
+
}
|
|
203
|
+
await executeCommand({
|
|
204
|
+
command: "sysctl",
|
|
205
|
+
args: ["-w", `${record.target}=${record.originalValue}`],
|
|
206
|
+
timeout: 10000,
|
|
207
|
+
});
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "service": {
|
|
211
|
+
// Validate service name against safe pattern
|
|
212
|
+
if (!SAFE_SERVICE_NAME_RE.test(record.target)) {
|
|
213
|
+
throw new Error(`[rollback] Invalid service name: ${record.target}`);
|
|
214
|
+
}
|
|
215
|
+
// originalValue is the previous state (e.g., "active", "inactive")
|
|
216
|
+
const action = record.originalValue === "active" ? "start" : "stop";
|
|
217
|
+
await executeCommand({
|
|
218
|
+
command: "systemctl",
|
|
219
|
+
args: [action, record.target],
|
|
220
|
+
timeout: 30000,
|
|
221
|
+
});
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "firewall": {
|
|
225
|
+
// Prefer structured rollbackCommand if available (CORE-003 remediation)
|
|
226
|
+
if (record.rollbackCommand) {
|
|
227
|
+
const { command, args } = record.rollbackCommand;
|
|
228
|
+
// Validate command is in the general allowlist
|
|
229
|
+
if (!isAllowlisted(command)) {
|
|
230
|
+
throw new Error(`[rollback] Firewall rollback command '${command}' is not in the command allowlist — refusing to execute`);
|
|
231
|
+
}
|
|
232
|
+
// Defense-in-depth: only allow known firewall commands
|
|
233
|
+
validateFirewallCommand(command);
|
|
234
|
+
// Validate each argument for injection safety
|
|
235
|
+
for (let i = 0; i < args.length; i++) {
|
|
236
|
+
validateRollbackArg(args[i], i, "Firewall rollback");
|
|
237
|
+
}
|
|
238
|
+
await executeCommand({
|
|
239
|
+
command,
|
|
240
|
+
args,
|
|
241
|
+
timeout: 10000,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Legacy fallback: reconstruct from originalValue string splitting
|
|
246
|
+
// Validate the command against the allowlist before executing
|
|
247
|
+
const parts = record.originalValue.split(/\s+/).filter(Boolean);
|
|
248
|
+
if (parts.length < 2) {
|
|
249
|
+
throw new Error(`[rollback] Firewall rollback command too short: '${record.originalValue}'`);
|
|
250
|
+
}
|
|
251
|
+
const command = parts[0];
|
|
252
|
+
if (!isAllowlisted(command)) {
|
|
253
|
+
throw new Error(`[rollback] Firewall rollback command '${command}' is not in the command allowlist — refusing to execute`);
|
|
254
|
+
}
|
|
255
|
+
// Defense-in-depth: only allow known firewall commands
|
|
256
|
+
validateFirewallCommand(command);
|
|
257
|
+
// Validate each argument for injection safety
|
|
258
|
+
const args = parts.slice(1);
|
|
259
|
+
for (let i = 0; i < args.length; i++) {
|
|
260
|
+
validateRollbackArg(args[i], i, "Firewall rollback (legacy)");
|
|
261
|
+
}
|
|
262
|
+
await executeCommand({
|
|
263
|
+
command,
|
|
264
|
+
args,
|
|
265
|
+
timeout: 10000,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
record.rolledBack = true;
|
|
272
|
+
console.error(`[rollback] Rolled back ${record.type} change on ${record.target}`);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
console.error(`[rollback] Failed to rollback ${record.type} on ${record.target}: ${err instanceof Error ? err.message : String(err)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SafeguardRegistry — singleton that detects running applications and
|
|
3
|
+
* environmental state so defensive operations can be evaluated for safety.
|
|
4
|
+
*
|
|
5
|
+
* Detection domains:
|
|
6
|
+
* - VS Code (editor process, config dir, IPC sockets)
|
|
7
|
+
* - Docker (daemon socket, running containers)
|
|
8
|
+
* - MCP servers (workspace config + node processes)
|
|
9
|
+
* - Databases (TCP port probing: PostgreSQL, MySQL, MongoDB, Redis)
|
|
10
|
+
* - Web servers (nginx, apache2, httpd process detection)
|
|
11
|
+
*/
|
|
12
|
+
export interface SafetyResult {
|
|
13
|
+
safe: boolean;
|
|
14
|
+
warnings: string[];
|
|
15
|
+
blockers: string[];
|
|
16
|
+
impactedApps: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface DetectedApp {
|
|
19
|
+
category: string;
|
|
20
|
+
detected: boolean;
|
|
21
|
+
detail: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SafeguardReport {
|
|
24
|
+
timestamp: string;
|
|
25
|
+
detectedApps: DetectedApp[];
|
|
26
|
+
summary: string;
|
|
27
|
+
overallSafe: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Check if the current session is over SSH. */
|
|
30
|
+
export declare function isSSHSession(): boolean;
|
|
31
|
+
/** Check if the current user has a non-empty authorized_keys file. */
|
|
32
|
+
export declare function hasAuthorizedKeys(): boolean;
|
|
33
|
+
export declare class SafeguardRegistry {
|
|
34
|
+
/** Cached detection results with TTL to avoid re-running on every tool call */
|
|
35
|
+
private detectionCache;
|
|
36
|
+
private readonly DETECTION_CACHE_TTL;
|
|
37
|
+
private constructor();
|
|
38
|
+
/** Get the singleton instance. */
|
|
39
|
+
static getInstance(): SafeguardRegistry;
|
|
40
|
+
/** Detect VS Code editor presence. */
|
|
41
|
+
detectVSCode(): Promise<DetectedApp>;
|
|
42
|
+
/** Detect Docker daemon and running containers. */
|
|
43
|
+
detectDocker(): Promise<DetectedApp>;
|
|
44
|
+
/** Detect configured MCP servers. */
|
|
45
|
+
detectMCPServers(): Promise<DetectedApp>;
|
|
46
|
+
/** Detect databases via TCP port probing. */
|
|
47
|
+
detectDatabases(): Promise<DetectedApp>;
|
|
48
|
+
/** Detect web server processes. */
|
|
49
|
+
detectWebServers(): Promise<DetectedApp>;
|
|
50
|
+
/**
|
|
51
|
+
* Check whether an operation is safe given current system state.
|
|
52
|
+
* Returns warnings (non-blocking) and blockers (operation should not proceed).
|
|
53
|
+
*/
|
|
54
|
+
checkSafety(operation: string, params: Record<string, unknown>): Promise<SafetyResult>;
|
|
55
|
+
/** Generate a full safety report of all detected applications. */
|
|
56
|
+
appSafetyReport(): Promise<SafeguardReport>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=safeguards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeguards.d.ts","sourceRoot":"","sources":["../../src/core/safeguards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AA0DD,gDAAgD;AAChD,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,IAAI,OAAO,CAS3C;AA0BD,qBAAa,iBAAiB;IAC5B,+EAA+E;IAC/E,OAAO,CAAC,cAAc,CAGa;IAEnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAU;IAE9C,OAAO;IAEP,kCAAkC;IAClC,MAAM,CAAC,WAAW,IAAI,iBAAiB;IAOvC,sCAAsC;IAChC,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC;IA0C1C,mDAAmD;IAC7C,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC;IAkC1C,qCAAqC;IAC/B,gBAAgB,IAAI,OAAO,CAAC,WAAW,CAAC;IAwC9C,6CAA6C;IACvC,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC;IA0B7C,mCAAmC;IAC7B,gBAAgB,IAAI,OAAO,CAAC,WAAW,CAAC;IAmB9C;;;OAGG;IACG,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,YAAY,CAAC;IAuLxB,kEAAkE;IAC5D,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC;CA2BlD"}
|