defense-mcp-server 0.8.4 → 0.9.1
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 +319 -55
- package/build/core/auto-installer.d.ts.map +1 -1
- package/build/core/auto-installer.js +0 -3
- package/build/core/backup-manager.d.ts.map +1 -1
- package/build/core/backup-manager.js +44 -33
- package/build/core/command-allowlist.d.ts.map +1 -1
- package/build/core/command-allowlist.js +14 -1
- package/build/core/dependency-validator.d.ts.map +1 -1
- package/build/core/distro-adapter.d.ts.map +1 -1
- package/build/core/distro.js +2 -2
- package/build/core/installer.d.ts.map +1 -1
- package/build/core/logger.d.ts.map +1 -1
- package/build/core/logger.js +7 -3
- package/build/core/pam-utils.d.ts.map +1 -1
- package/build/core/preflight.d.ts.map +1 -1
- package/build/core/safeguards.d.ts.map +1 -1
- package/build/core/safeguards.js +4 -3
- package/build/core/sanitizer.d.ts +1 -1
- package/build/core/sanitizer.d.ts.map +1 -1
- package/build/core/sanitizer.js +1 -1
- package/build/core/sudo-guard.d.ts +5 -0
- package/build/core/sudo-guard.d.ts.map +1 -1
- package/build/core/sudo-guard.js +0 -11
- package/build/core/third-party-installer.js +1 -1
- package/build/index.js +134 -7
- package/build/tools/access-control.d.ts.map +1 -1
- package/build/tools/access-control.js +22 -24
- package/build/tools/compliance.d.ts.map +1 -1
- package/build/tools/container-security.js +1 -1
- package/build/tools/deception.d.ts.map +1 -1
- package/build/tools/deception.js +3 -2
- package/build/tools/dns-security.d.ts.map +1 -1
- package/build/tools/ebpf-security.d.ts.map +1 -1
- package/build/tools/encryption.d.ts.map +1 -1
- package/build/tools/encryption.js +0 -18
- package/build/tools/firewall.d.ts.map +1 -1
- package/build/tools/firewall.js +0 -11
- package/build/tools/hardening.d.ts.map +1 -1
- package/build/tools/integrity.js +1 -1
- package/build/tools/logging.d.ts.map +1 -1
- package/build/tools/malware.d.ts.map +1 -1
- package/build/tools/malware.js +17 -7
- package/build/tools/meta.d.ts.map +1 -1
- package/build/tools/meta.js +9 -10
- package/build/tools/network-defense.d.ts.map +1 -1
- package/build/tools/network-defense.js +1 -30
- package/build/tools/sudo-management.js +12 -18
- package/build/tools/supply-chain-security.d.ts.map +1 -1
- package/build/tools/supply-chain-security.js +0 -20
- package/build/tools/threat-intel.d.ts.map +1 -1
- package/build/tools/threat-intel.js +0 -2
- package/build/tools/waf.js +1 -1
- package/build/tools/wireless-security.js +1 -1
- package/build/tools/zero-trust-network.d.ts.map +1 -1
- package/build/tools/zero-trust-network.js +0 -8
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
# Defense MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://smithery.ai/server/@bottobot/defense-mcp-server)
|
|
4
|
+
[](https://www.npmjs.com/package/defense-mcp-server)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/sponsors/bottobot)
|
|
7
|
+
[](https://ko-fi.com/bottobot)
|
|
4
8
|
|
|
5
|
-
|
|
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.
|
|
9
|
+
**31 defensive security tools. 250+ actions. One MCP server.**
|
|
7
10
|
|
|
11
|
+
A Model Context Protocol (MCP) server that gives AI assistants access to **31 defensive security tools** (with 250+ actions) on Linux. Connect it to Claude Desktop, Cursor, Smithery, or any MCP-compatible client to harden systems, manage firewalls, scan for vulnerabilities, and enforce compliance — all through natural language conversation.
|
|
12
|
+
|
|
13
|
+
### TLDR ###
|
|
14
|
+
It's an all-in-one easy to use MCP server that will help you secure your Linux OS and harden it to external attackers. It's not perfect by any stretch but if you need something that'll take the pain out of hardening a fresh OS install then give it a try. It's as simple as saying "give me a full security audit using the Defense MCP Server" to your favourite LLM agent.
|
|
15
|
+
|
|
16
|
+
## **The Story Of This Thing** ##
|
|
17
|
+
I started experimenting with Kali and I had a weird thought that if someone ever gained control of that system they could do some damage in my network. I found it came with all sorts of interesting defensive tools besides the obvious offensive ones and so I started messing around with automating the hardening process. Eventually I left Kali to the hackers and just switched to a Debian 13 vanilla type setup on my daily driver. All those hacker tools just made me nervous! However I have spent an incredible amount of time learning about system security on Linux building this thing and I think its ready to see the light of day for others to use.
|
|
18
|
+
|
|
19
|
+
Please don't hesitate to leave feedback or if you think there's a good tool I should include.
|
|
20
|
+
|
|
21
|
+
## Testing So Far ##
|
|
22
|
+
So my stack for this that I've tested is VS Codium on Kali and Debian 13 using Roo Code and Claude Code using Anthropic Models. I gotta say I'm pretty happy with how well it does. I've really tried to get it to be a simple all-in-one hardening tool that'll at least plug the major gaps in anyones system. If you're using API's then its really gonna chug down some tokens so be warned. It did go through a few rounds of token efficiency optimisation but it'll eat em up like their Girl Guides Thin Mint cookies.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g defense-mcp-server
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or via Smithery:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx -y @smithery/cli install @bottobot/defense-mcp-server --client claude
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
8
39
|
## So What It Does
|
|
9
40
|
|
|
10
|
-
This server exposes Linux security tools as MCP tools that an AI assistant can invoke on your behalf. Instead of memorizing command syntax for dozens of security utilities, you describe what you want in plain English and the assistant calls the right tool with the right parameters. Sounds pretty good right
|
|
41
|
+
This server exposes Linux security tools as MCP tools that an AI assistant can invoke on your behalf. Instead of memorizing command syntax for dozens of security utilities, you describe what you want in plain English and the assistant calls the right tool with the right parameters. Sounds pretty good right?
|
|
11
42
|
|
|
12
43
|
Here are the tools:
|
|
13
44
|
|
|
@@ -46,7 +77,10 @@ Here are the tools:
|
|
|
46
77
|
| **Deception/Honeypots** | Canary token deployment, honeyport listeners, trigger monitoring |
|
|
47
78
|
| **Wireless Security** | Bluetooth/WiFi auditing, rogue AP detection, interface disabling |
|
|
48
79
|
|
|
49
|
-
|
|
80
|
+
### Safety Guardrails
|
|
81
|
+
|
|
82
|
+
Every tool runs with safety guardrails — you won't blow up your box:
|
|
83
|
+
|
|
50
84
|
- **Dry-run by default** — tools preview what they would do before making changes
|
|
51
85
|
- **Command allowlist** — only pre-approved binaries can execute (no shell interpreters)
|
|
52
86
|
- **Input sanitization** — all parameters validated against injection attacks
|
|
@@ -81,7 +115,7 @@ DEFENSE_MCP_AUTO_INSTALL=false node build/index.js
|
|
|
81
115
|
## Requirements
|
|
82
116
|
|
|
83
117
|
- **Linux** (Kali, Debian, Ubuntu, RHEL, Arch, or any systemd-based distro)
|
|
84
|
-
- **Node.js
|
|
118
|
+
- **Node.js 22+**
|
|
85
119
|
- **npm 9+**
|
|
86
120
|
|
|
87
121
|
## System Dependencies
|
|
@@ -101,17 +135,105 @@ sudo apt-get install -y \
|
|
|
101
135
|
|
|
102
136
|
### Third-Party Tools
|
|
103
137
|
|
|
104
|
-
These are **not available** in standard Debian/Ubuntu repos and
|
|
138
|
+
These are **not available** in standard Debian/Ubuntu repos. The instructions below avoid piping remote scripts into a shell (`curl | sh`) — each binary is downloaded, verified, and installed as a discrete step.
|
|
139
|
+
|
|
140
|
+
> **Note:** The MCP server can auto-install these tools for you when `DEFENSE_MCP_AUTO_INSTALL=true` (the default). It uses GPG fingerprint and SHA256 verification internally. The manual steps below are for pre-installation or air-gapped environments.
|
|
141
|
+
|
|
142
|
+
#### Falco (eBPF runtime security)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Import GPG key and verify fingerprint
|
|
146
|
+
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc -o /tmp/falco.asc
|
|
147
|
+
gpg --show-keys /tmp/falco.asc # Verify: 478B 2FBB C75F 4237 B731 DA43 6510 6822 B35B 1B1F
|
|
148
|
+
sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg /tmp/falco.asc
|
|
149
|
+
rm /tmp/falco.asc
|
|
150
|
+
|
|
151
|
+
# Add signed repo and install
|
|
152
|
+
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" \
|
|
153
|
+
| sudo tee /etc/apt/sources.list.d/falcosecurity.list
|
|
154
|
+
sudo apt-get update && sudo apt-get install -y falco
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Trivy (container image scanning)
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Import GPG key and verify fingerprint
|
|
161
|
+
curl -fsSL https://aquasecurity.github.io/trivy-repo/deb/public.key -o /tmp/trivy.asc
|
|
162
|
+
gpg --show-keys /tmp/trivy.asc # Verify: 2E2D 3567 4616 32C8 4BB6 CD6F E9D0 A361 6276 FA6C
|
|
163
|
+
sudo gpg --dearmor -o /usr/share/keyrings/trivy-archive-keyring.gpg /tmp/trivy.asc
|
|
164
|
+
rm /tmp/trivy.asc
|
|
165
|
+
|
|
166
|
+
# Add signed repo and install
|
|
167
|
+
echo "deb [signed-by=/usr/share/keyrings/trivy-archive-keyring.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" \
|
|
168
|
+
| sudo tee /etc/apt/sources.list.d/trivy.list
|
|
169
|
+
sudo apt-get update && sudo apt-get install -y trivy
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### Grype (vulnerability scanning)
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
VERSION=v0.110.0
|
|
176
|
+
curl -fsSL -o /tmp/grype.tar.gz \
|
|
177
|
+
"https://github.com/anchore/grype/releases/download/${VERSION}/grype_${VERSION#v}_linux_amd64.tar.gz"
|
|
178
|
+
curl -fsSL -o /tmp/grype.tar.gz.sha256 \
|
|
179
|
+
"https://github.com/anchore/grype/releases/download/${VERSION}/grype_${VERSION#v}_checksums.txt"
|
|
180
|
+
|
|
181
|
+
# Verify checksum
|
|
182
|
+
cd /tmp && grep "linux_amd64.tar.gz" grype.tar.gz.sha256 | sha256sum -c -
|
|
183
|
+
tar xzf grype.tar.gz grype && sudo install grype /usr/local/bin/grype
|
|
184
|
+
rm -f /tmp/grype /tmp/grype.tar.gz /tmp/grype.tar.gz.sha256
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Syft (SBOM generation)
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
VERSION=v1.42.3
|
|
191
|
+
curl -fsSL -o /tmp/syft.tar.gz \
|
|
192
|
+
"https://github.com/anchore/syft/releases/download/${VERSION}/syft_${VERSION#v}_linux_amd64.tar.gz"
|
|
193
|
+
curl -fsSL -o /tmp/syft_checksums.txt \
|
|
194
|
+
"https://github.com/anchore/syft/releases/download/${VERSION}/syft_${VERSION#v}_checksums.txt"
|
|
195
|
+
|
|
196
|
+
# Verify checksum
|
|
197
|
+
cd /tmp && grep "linux_amd64.tar.gz" syft_checksums.txt | sha256sum -c -
|
|
198
|
+
tar xzf syft.tar.gz syft && sudo install syft /usr/local/bin/syft
|
|
199
|
+
rm -f /tmp/syft /tmp/syft.tar.gz /tmp/syft_checksums.txt
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### TruffleHog (secret scanning)
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
VERSION=v3.94.1
|
|
206
|
+
curl -fsSL -o /tmp/trufflehog.tar.gz \
|
|
207
|
+
"https://github.com/trufflesecurity/trufflehog/releases/download/${VERSION}/trufflehog_${VERSION#v}_linux_amd64.tar.gz"
|
|
208
|
+
curl -fsSL -o /tmp/trufflehog_checksums.txt \
|
|
209
|
+
"https://github.com/trufflesecurity/trufflehog/releases/download/${VERSION}/trufflehog_${VERSION#v}_checksums.txt"
|
|
210
|
+
|
|
211
|
+
# Verify checksum
|
|
212
|
+
cd /tmp && grep "linux_amd64.tar.gz" trufflehog_checksums.txt | sha256sum -c -
|
|
213
|
+
tar xzf trufflehog.tar.gz trufflehog && sudo install trufflehog /usr/local/bin/trufflehog
|
|
214
|
+
rm -f /tmp/trufflehog /tmp/trufflehog.tar.gz /tmp/trufflehog_checksums.txt
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### slsa-verifier (supply chain verification)
|
|
105
218
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
219
|
+
```bash
|
|
220
|
+
VERSION=v2.7.1
|
|
221
|
+
curl -fsSL -o /tmp/slsa-verifier \
|
|
222
|
+
"https://github.com/slsa-framework/slsa-verifier/releases/download/${VERSION}/slsa-verifier-linux-amd64"
|
|
223
|
+
curl -fsSL -o /tmp/slsa-verifier.sha256 \
|
|
224
|
+
"https://github.com/slsa-framework/slsa-verifier/releases/download/${VERSION}/slsa-verifier-linux-amd64.sha256"
|
|
225
|
+
|
|
226
|
+
# Verify checksum
|
|
227
|
+
cd /tmp && echo "$(cat slsa-verifier.sha256) slsa-verifier" | sha256sum -c -
|
|
228
|
+
sudo install slsa-verifier /usr/local/bin/slsa-verifier
|
|
229
|
+
rm -f /tmp/slsa-verifier /tmp/slsa-verifier.sha256
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### cdxgen (CycloneDX SBOM generation)
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm install -g @cyclonedx/cdxgen
|
|
236
|
+
```
|
|
115
237
|
|
|
116
238
|
### Important Notes
|
|
117
239
|
|
|
@@ -120,37 +242,48 @@ These are **not available** in standard Debian/Ubuntu repos and require manual i
|
|
|
120
242
|
- **`bpftool`**: On Debian Trixie, install the `bpftool` package directly (NOT `linux-tools-generic` which is Ubuntu-specific).
|
|
121
243
|
- **`pam_pwquality`**: This is a PAM module (`libpam-pwquality`), not a standalone binary. Install via `apt-get install libpam-pwquality`.
|
|
122
244
|
|
|
123
|
-
##
|
|
245
|
+
## Quick Start
|
|
246
|
+
|
|
247
|
+
### Step 1: Install the server
|
|
124
248
|
|
|
125
|
-
|
|
249
|
+
Pick one method:
|
|
126
250
|
|
|
251
|
+
**npm (recommended):**
|
|
127
252
|
```bash
|
|
128
253
|
npm install -g defense-mcp-server
|
|
129
254
|
```
|
|
130
255
|
|
|
131
|
-
|
|
256
|
+
**Or clone and build from source:**
|
|
257
|
+
```bash
|
|
258
|
+
git clone https://github.com/bottobot/defense-mcp-server.git
|
|
259
|
+
cd defense-mcp-server
|
|
260
|
+
npm install && npm run build
|
|
261
|
+
```
|
|
132
262
|
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
git clone https://github.com/bottobot/defense-mcp-server.git
|
|
136
|
-
cd defense-mcp-server
|
|
137
|
-
```
|
|
263
|
+
### Step 2: Connect to your AI client
|
|
138
264
|
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
npm install
|
|
142
|
-
```
|
|
265
|
+
The server supports any MCP client. Pick your client below and add the configuration:
|
|
143
266
|
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
npm run build
|
|
147
|
-
```
|
|
267
|
+
**Claude Code (CLI / VS Code / JetBrains):**
|
|
148
268
|
|
|
149
|
-
|
|
269
|
+
Create a `.mcp.json` file in your project root (or `~/.claude/` for global):
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"mcpServers": {
|
|
273
|
+
"defense-mcp-server": {
|
|
274
|
+
"command": "defense-mcp-server",
|
|
275
|
+
"env": {
|
|
276
|
+
"DEFENSE_MCP_DRY_RUN": "true",
|
|
277
|
+
"DEFENSE_MCP_ALLOWED_DIRS": "/tmp,/home,/var/log"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
150
283
|
|
|
151
|
-
|
|
284
|
+
**Claude Desktop:**
|
|
152
285
|
|
|
153
|
-
|
|
286
|
+
Edit `~/.config/claude/claude_desktop_config.json` (Linux) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
154
287
|
```json
|
|
155
288
|
{
|
|
156
289
|
"mcpServers": {
|
|
@@ -160,30 +293,50 @@ Add this to your Claude Desktop configuration file (`~/.config/claude/claude_des
|
|
|
160
293
|
}
|
|
161
294
|
}
|
|
162
295
|
```
|
|
296
|
+
Restart Claude Desktop. The server will appear in the MCP tools panel.
|
|
163
297
|
|
|
164
|
-
**
|
|
298
|
+
**Cursor / Other MCP clients:**
|
|
165
299
|
```json
|
|
166
300
|
{
|
|
167
301
|
"mcpServers": {
|
|
168
302
|
"defense-mcp-server": {
|
|
169
|
-
"command": "
|
|
170
|
-
"args": ["/path/to/defense-mcp-server/build/index.js"]
|
|
303
|
+
"command": "defense-mcp-server"
|
|
171
304
|
}
|
|
172
305
|
}
|
|
173
306
|
}
|
|
174
307
|
```
|
|
175
308
|
|
|
176
|
-
|
|
309
|
+
**If you cloned and built from source**, replace `"defense-mcp-server"` in the command field with:
|
|
310
|
+
```json
|
|
311
|
+
"command": "node",
|
|
312
|
+
"args": ["/path/to/defense-mcp-server/build/index.js"]
|
|
313
|
+
```
|
|
177
314
|
|
|
178
|
-
|
|
315
|
+
### Step 3: Verify it works
|
|
179
316
|
|
|
180
|
-
|
|
317
|
+
Open your AI client and ask:
|
|
181
318
|
|
|
182
|
-
|
|
319
|
+
> "Check my firewall status"
|
|
183
320
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
321
|
+
The assistant should call the `firewall` tool and return your iptables/UFW rules. If it does, you're all set.
|
|
322
|
+
|
|
323
|
+
### Step 4: Elevate when needed
|
|
324
|
+
|
|
325
|
+
Most audit tools (listing firewall rules, checking patches, reading logs) work without sudo. When a tool needs elevated privileges, the server will tell you. Elevate securely:
|
|
326
|
+
|
|
327
|
+
> "Elevate sudo with GUI dialog"
|
|
328
|
+
|
|
329
|
+
This opens a native password dialog (zenity/kdialog) — your password never passes through the AI conversation. The session auto-expires after 15 minutes, or you can drop it anytime:
|
|
330
|
+
|
|
331
|
+
> "Drop sudo privileges"
|
|
332
|
+
|
|
333
|
+
### What happens on first run
|
|
334
|
+
|
|
335
|
+
1. **Dry-run mode is on by default** — tools preview what they would do without changing anything
|
|
336
|
+
2. **Missing tools are auto-installed** — if you ask for a malware scan but ClamAV isn't installed, the server installs it via apt/dnf automatically
|
|
337
|
+
3. **All file access is restricted** — only `/tmp`, `/home`, and `/var/log` are accessible by default
|
|
338
|
+
|
|
339
|
+
To enable live changes (not just previews), set `DEFENSE_MCP_DRY_RUN=false` in the env config above.
|
|
187
340
|
|
|
188
341
|
## Usage Examples
|
|
189
342
|
|
|
@@ -227,6 +380,8 @@ Configuration is via environment variables. All have secure defaults:
|
|
|
227
380
|
| `DEFENSE_MCP_AUTO_INSTALL` | `true` | Auto-install missing tool dependencies |
|
|
228
381
|
| `DEFENSE_MCP_PREFLIGHT` | `true` | Enable pre-flight dependency checks |
|
|
229
382
|
| `DEFENSE_MCP_PREFLIGHT_BANNERS` | `true` | Show pre-flight status in tool output |
|
|
383
|
+
| `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` |
|
|
384
|
+
| `MCP_PORT` | `3100` | HTTP server port (when `MCP_TRANSPORT=http`) |
|
|
230
385
|
|
|
231
386
|
To apply changes for real (not just preview), set:
|
|
232
387
|
```bash
|
|
@@ -235,16 +390,125 @@ DEFENSE_MCP_DRY_RUN=false node build/index.js
|
|
|
235
390
|
|
|
236
391
|
## Security
|
|
237
392
|
|
|
238
|
-
This server
|
|
393
|
+
A security tool that isn't secure itself is worse than useless. This server implements defense-in-depth across 10 layers, from configuration defaults down to cryptographic verification.
|
|
394
|
+
|
|
395
|
+
### MCP Specification Compliance
|
|
396
|
+
|
|
397
|
+
The [MCP spec](https://modelcontextprotocol.io/) defines security requirements for servers. Here's how this project meets them:
|
|
398
|
+
|
|
399
|
+
| MCP Requirement | Implementation |
|
|
400
|
+
|----------------|---------------|
|
|
401
|
+
| **Validate all tool inputs** | Zod schemas on every parameter + 15 specialized validators (paths, IPs, ports, service names, etc.) |
|
|
402
|
+
| **Implement access controls** | 200+ entry command allowlist, `shell: false` enforced, sudo session management |
|
|
403
|
+
| **Rate limit tool invocations** | 30/tool/min, 100 global/min, auth failure throttling (5 attempts per 5 minutes) |
|
|
404
|
+
| **Sanitize tool outputs** | Error sanitization strips paths, stack traces, and truncates to 500 chars |
|
|
405
|
+
|
|
406
|
+
### Layer 1: Safe Defaults
|
|
407
|
+
|
|
408
|
+
Everything is locked down out of the box. You have to explicitly opt in to making changes:
|
|
409
|
+
|
|
410
|
+
- **Dry-run mode on by default** — every tool previews what it would do before touching anything
|
|
411
|
+
- **Backups before changes** — system state is backed up automatically before any modification
|
|
412
|
+
- **Confirmation required** — destructive actions need explicit confirmation
|
|
413
|
+
- **Restricted directories** — the server can only access explicitly allowed paths; root `/`, `/etc`, `/usr`, `/bin`, `/sbin` are blocked by default
|
|
414
|
+
- **Protected paths** — system-critical files are blocked from modification regardless of directory config
|
|
415
|
+
|
|
416
|
+
### Layer 2: Command Execution
|
|
417
|
+
|
|
418
|
+
No tool can run arbitrary commands. Every command goes through multiple gates:
|
|
419
|
+
|
|
420
|
+
- **Binary allowlist** — 200+ pre-approved binaries across 18 categories. If a binary isn't on the list, it doesn't run. Period.
|
|
421
|
+
- **Absolute path resolution** — binaries are resolved to absolute paths at startup via `fs.existsSync()`, never through `which` or PATH
|
|
422
|
+
- **`shell: false` enforced** — hardcoded, cannot be overridden. Shell metacharacters (`;`, `|`, `&`, `` ` ``, `$`, etc.) have no effect
|
|
423
|
+
- **TOCTOU detection** — binary inodes are recorded at startup and verified before execution to detect replacement
|
|
424
|
+
- **No fallback** — if a binary can't be resolved to a known path, execution is refused entirely
|
|
425
|
+
|
|
426
|
+
### Layer 3: Input Validation
|
|
427
|
+
|
|
428
|
+
Every parameter is validated before it reaches any tool handler:
|
|
429
|
+
|
|
430
|
+
- **Zod schemas** — runtime type checking with string length limits, enum constraints, numeric ranges, array bounds
|
|
431
|
+
- **Path traversal protection** — `../` sequences rejected, null bytes blocked, symlinks resolved and re-validated
|
|
432
|
+
- **Shell metacharacter blocking** — `[;|&$\`(){}<>!\\\n\r]` stripped from all inputs
|
|
433
|
+
- **Control character rejection** — `[\x00-\x08\x0e-\x1f\x7f]` blocked
|
|
434
|
+
- **Specialized validators** for: targets (hostname/IP/CIDR), ports, service names, sysctl keys, package names, iptables chains, network interfaces, usernames, YARA rules, certificate paths, firewall zones, auditd keys
|
|
435
|
+
- **ReDoS protection** — regex patterns limited to 200 characters, nested quantifiers and excessive alternation rejected
|
|
436
|
+
|
|
437
|
+
### Layer 4: Sudo & Privilege Management
|
|
438
|
+
|
|
439
|
+
The server never asks for your password in a way an AI can see:
|
|
440
|
+
|
|
441
|
+
- **GUI elevation** — `sudo_elevate_gui` opens a native zenity/kdialog dialog. The password goes directly to sudo, never through the AI conversation
|
|
442
|
+
- **Buffer storage** — passwords are stored as Node.js Buffers (not V8 strings), which can be explicitly zeroed from memory
|
|
443
|
+
- **Auto-zeroing** — password buffer is zeroed on session drop, timeout expiry, and process exit
|
|
444
|
+
- **Credential validation** — password is tested with `sudo -S -k -v` before being accepted
|
|
445
|
+
- **Auth rate limiting** — 5 failed attempts per 5 minutes, then locked out
|
|
446
|
+
- **Session UID guard** — session is dropped immediately if the OS user ID changes
|
|
447
|
+
- **NOPASSWD:ALL rejection** — the sudoers management tool explicitly refuses to write `NOPASSWD: ALL` rules
|
|
448
|
+
- **`sudo -S` stdin piping** — passwords are piped via stdin, never passed as command-line arguments (which would be visible in `ps`)
|
|
449
|
+
- **40+ permission error patterns** — detected and surfaced with clear elevation prompts instead of cryptic failures
|
|
450
|
+
|
|
451
|
+
### Layer 5: Secure File Operations
|
|
452
|
+
|
|
453
|
+
Every file write is atomic and permission-hardened:
|
|
454
|
+
|
|
455
|
+
- **Atomic writes** — write to temp file, then rename. No partial writes, no corruption on crash
|
|
456
|
+
- **Owner-only permissions** — files created with `0o600`, directories with `0o700`
|
|
457
|
+
- **Explicit chmod** — permissions enforced independently of umask
|
|
458
|
+
- **Symlink protection** — real paths resolved and re-validated against allowed directories
|
|
459
|
+
- **Backup before modify** — timestamped backups with manifest tracking under `~/.defense-mcp/backups/`
|
|
460
|
+
|
|
461
|
+
### Layer 6: Encrypted State Storage
|
|
462
|
+
|
|
463
|
+
Sensitive runtime data is encrypted at rest:
|
|
464
|
+
|
|
465
|
+
- **Algorithm**: AES-256-GCM (authenticated encryption)
|
|
466
|
+
- **Key derivation**: PBKDF2 with 100,000 iterations, SHA-512
|
|
467
|
+
- **IV**: 96-bit (GCM-recommended)
|
|
468
|
+
- **Auth tag**: 128-bit
|
|
469
|
+
- **Salt**: 128-bit per file
|
|
470
|
+
- **Fallback**: plaintext JSON with warning when no key is configured (`DEFENSE_MCP_STATE_KEY`)
|
|
471
|
+
|
|
472
|
+
### Layer 7: Supply Chain Security
|
|
473
|
+
|
|
474
|
+
Auto-installed tools are verified, not blindly trusted:
|
|
475
|
+
|
|
476
|
+
- **System packages** — installed only via official package manager (apt/dnf/pacman)
|
|
477
|
+
- **pip allowlist** — only 9 pre-approved packages (yara-python, python-nmap, etc.)
|
|
478
|
+
- **npm allowlist** — only 2 pre-approved packages (cdxgen, snyk)
|
|
479
|
+
- **Third-party tools** (Falco, Trivy, Grype, Syft, TruffleHog, slsa-verifier):
|
|
480
|
+
- Never uses `curl | sh` — all downloads verified before execution
|
|
481
|
+
- SHA256 checksums hardcoded in manifest
|
|
482
|
+
- GPG fingerprints verified against known-good values
|
|
483
|
+
- Cosign verification where available
|
|
484
|
+
- Requires explicit `DEFENSE_MCP_THIRD_PARTY_INSTALL=true` to enable
|
|
485
|
+
|
|
486
|
+
### Layer 8: Rate Limiting & Safeguards
|
|
487
|
+
|
|
488
|
+
Protection against runaway or abusive tool invocations:
|
|
489
|
+
|
|
490
|
+
- **Per-tool limit**: 30 invocations per 60 seconds
|
|
491
|
+
- **Global limit**: 100 invocations per 60 seconds
|
|
492
|
+
- **Running service detection** — detects VS Code, Docker, databases, web servers, MCP servers, SSH sessions before operations that could affect them
|
|
493
|
+
- **Pre-flight validation** — every tool checks dependencies, privileges, and safeguards before executing
|
|
494
|
+
|
|
495
|
+
### Layer 9: Audit Trail & Rollback
|
|
496
|
+
|
|
497
|
+
Every change is recorded and reversible:
|
|
498
|
+
|
|
499
|
+
- **Structured changelog** — JSON entries with tool name, action, target, before/after values, timestamp
|
|
500
|
+
- **Rollback commands** — stored with each change, validated against command allowlist
|
|
501
|
+
- **Structured logging** — JSON-formatted security events to stderr with file rotation (10 MB, 5 files)
|
|
502
|
+
- **Security log level** — critical events always logged regardless of log level setting
|
|
503
|
+
|
|
504
|
+
### Layer 10: Policy Engine
|
|
505
|
+
|
|
506
|
+
Hardening policies are enforced safely:
|
|
239
507
|
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
- Rate limited to prevent abuse (30/tool/min, 100 global/min)
|
|
245
|
-
- All file writes go through secure-fs with audit trail
|
|
246
|
-
- Encrypted state storage (AES-256-GCM) for sensitive runtime data
|
|
247
|
-
- Atomic file writes (write-to-temp-then-rename) to prevent corruption
|
|
508
|
+
- **No shell interpreters** — policy check and remediation commands use direct binary invocation only
|
|
509
|
+
- **Regex safety** — pattern length limits (200 chars), nested quantifier rejection
|
|
510
|
+
- **Severity classification** — critical, high, medium, low, info
|
|
511
|
+
- **Secure policy storage** — policy files created with `0o700` directory permissions
|
|
248
512
|
|
|
249
513
|
For the full security architecture, see [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
250
514
|
|
|
@@ -269,7 +533,7 @@ npm run audit:security
|
|
|
269
533
|
|
|
270
534
|
## Test Coverage
|
|
271
535
|
|
|
272
|
-
- **
|
|
536
|
+
- **2,048+ tests** across 62 test files
|
|
273
537
|
- Every source module (core + tools) has a corresponding test file
|
|
274
538
|
- Coverage enforced in CI pipeline
|
|
275
539
|
|
|
@@ -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;AASvD,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;AA0FD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAoOD,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;
|
|
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;AASvD,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;AA0FD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAoOD,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;IAsP5D;;;;;;;OAOG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgHlE;;;;;;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;IAwG1D;;OAEG;YACW,SAAS;IAOvB;;OAEG;IACH,OAAO,CAAC,aAAa;CAmBtB"}
|
|
@@ -589,7 +589,6 @@ export class AutoInstaller {
|
|
|
589
589
|
const useSudo = distro.packageManager !== "brew";
|
|
590
590
|
const result = execWithSudo(installArgs, { useSudo, timeoutMs: 300_000 });
|
|
591
591
|
if (!result.success) {
|
|
592
|
-
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
593
592
|
console.error(`[auto-installer] ✗ Failed to install '${binary}' (package: ${packageName}): ${result.stderr.slice(0, 200)}`);
|
|
594
593
|
return {
|
|
595
594
|
dependency: binary,
|
|
@@ -695,7 +694,6 @@ export class AutoInstaller {
|
|
|
695
694
|
result = execWithSudo([pip, "install", module], { timeoutMs: 120_000 });
|
|
696
695
|
}
|
|
697
696
|
if (!result.success) {
|
|
698
|
-
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
699
697
|
console.error(`[auto-installer] ✗ Failed to install Python module '${module}': ${result.stderr.slice(0, 200)}`);
|
|
700
698
|
return {
|
|
701
699
|
dependency: module,
|
|
@@ -888,7 +886,6 @@ export class AutoInstaller {
|
|
|
888
886
|
lastError = result.stderr.slice(0, 200);
|
|
889
887
|
}
|
|
890
888
|
if (!installed) {
|
|
891
|
-
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
892
889
|
console.error(`[auto-installer] ✗ Failed to install library '${lib}': ${lastError}`);
|
|
893
890
|
return {
|
|
894
891
|
dependency: lib,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backup-manager.d.ts","sourceRoot":"","sources":["../../src/core/backup-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"backup-manager.d.ts","sourceRoot":"","sources":["../../src/core/backup-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAiC1E;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,SAAS,CAAC,EAAE,MAAM;IAK9B,sCAAsC;IACtC,OAAO,CAAC,SAAS;IAIjB,8DAA8D;IAC9D,OAAO,CAAC,YAAY;IAepB,8BAA8B;IAC9B,OAAO,CAAC,aAAa;IAKrB;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW;IAyCzC;;;OAGG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK/C;;OAEG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB9C;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAO3C;;OAEG;IACG,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAiCtD"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Backups are stored under ~/.defense-mcp/backups/ with timestamped filenames.
|
|
5
5
|
* A manifest.json tracks all backups for listing and restore operations.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { readFileSync, unlinkSync, statSync, lstatSync, } from "node:fs";
|
|
8
8
|
import { join, basename, dirname, resolve as pathResolve } from "node:path";
|
|
9
9
|
import { secureWriteFileSync, secureMkdirSync, secureCopyFileSync } from "./secure-fs.js";
|
|
10
10
|
import { homedir } from "node:os";
|
|
@@ -38,20 +38,18 @@ export function validateBackupPath(filePath, baseDir) {
|
|
|
38
38
|
if (!resolved.startsWith(resolvedBase + "/") && resolved !== resolvedBase) {
|
|
39
39
|
throw new Error(`SECURITY: Backup path '${resolved}' escapes base directory '${resolvedBase}'`);
|
|
40
40
|
}
|
|
41
|
-
// 4. Reject symlinks
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
throw new Error(`SECURITY: Backup path '${filePath}' is a symlink. Refusing to use.`);
|
|
47
|
-
}
|
|
41
|
+
// 4. Reject symlinks — use lstatSync directly to avoid TOCTOU
|
|
42
|
+
try {
|
|
43
|
+
const lstats = lstatSync(filePath);
|
|
44
|
+
if (lstats.isSymbolicLink()) {
|
|
45
|
+
throw new Error(`SECURITY: Backup path '${filePath}' is a symlink. Refusing to use.`);
|
|
48
46
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// lstat failure on existing path is suspicious but non-fatal for validation
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof Error && err.message.startsWith("SECURITY:")) {
|
|
50
|
+
throw err;
|
|
54
51
|
}
|
|
52
|
+
// ENOENT (path doesn't exist yet) is fine for validation; other errors are non-fatal
|
|
55
53
|
}
|
|
56
54
|
}
|
|
57
55
|
// ── BackupManager ────────────────────────────────────────────────────────────
|
|
@@ -69,17 +67,16 @@ export class BackupManager {
|
|
|
69
67
|
/** Read manifest from disk with migration from old format. */
|
|
70
68
|
readManifest() {
|
|
71
69
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
70
|
+
// Read directly — avoids TOCTOU between existsSync and readFileSync
|
|
71
|
+
const raw = readFileSync(this.manifestPath, "utf-8");
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
if (parsed && Array.isArray(parsed.backups)) {
|
|
74
|
+
// Migrate: ensure version field is present (old format may lack it)
|
|
75
|
+
return { version: 1, backups: parsed.backups };
|
|
79
76
|
}
|
|
80
77
|
}
|
|
81
78
|
catch {
|
|
82
|
-
//
|
|
79
|
+
// Missing, unreadable, or corrupt manifest — start fresh
|
|
83
80
|
}
|
|
84
81
|
return { version: 1, backups: [] };
|
|
85
82
|
}
|
|
@@ -95,9 +92,6 @@ export class BackupManager {
|
|
|
95
92
|
backupSync(filePath) {
|
|
96
93
|
const validated = FilePathSchema.parse(filePath);
|
|
97
94
|
this.ensureDir();
|
|
98
|
-
if (!existsSync(validated)) {
|
|
99
|
-
throw new Error(`Source file does not exist: ${validated}`);
|
|
100
|
-
}
|
|
101
95
|
const id = randomUUID();
|
|
102
96
|
const now = new Date();
|
|
103
97
|
const ts = now.toISOString().replace(/[:.]/g, "-");
|
|
@@ -106,7 +100,16 @@ export class BackupManager {
|
|
|
106
100
|
const backupPath = join(this.backupDir, backupName);
|
|
107
101
|
// SECURITY (CORE-015): Validate the backup destination path
|
|
108
102
|
validateBackupPath(backupPath, this.backupDir);
|
|
109
|
-
|
|
103
|
+
// Copy atomically — no prior existsSync to avoid TOCTOU
|
|
104
|
+
try {
|
|
105
|
+
secureCopyFileSync(validated, backupPath);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
109
|
+
throw new Error(`Source file does not exist: ${validated}`);
|
|
110
|
+
}
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
110
113
|
const stat = statSync(backupPath);
|
|
111
114
|
const entry = {
|
|
112
115
|
id,
|
|
@@ -139,13 +142,19 @@ export class BackupManager {
|
|
|
139
142
|
if (!entry) {
|
|
140
143
|
throw new Error(`Backup not found: ${validated}`);
|
|
141
144
|
}
|
|
142
|
-
if (!existsSync(entry.backupPath)) {
|
|
143
|
-
throw new Error(`Backup file missing on disk: ${entry.backupPath}`);
|
|
144
|
-
}
|
|
145
145
|
// SECURITY (CORE-015): Validate the backup source path before restore
|
|
146
146
|
validateBackupPath(entry.backupPath, this.backupDir);
|
|
147
147
|
secureMkdirSync(dirname(entry.originalPath));
|
|
148
|
-
|
|
148
|
+
// Copy atomically — no prior existsSync to avoid TOCTOU
|
|
149
|
+
try {
|
|
150
|
+
secureCopyFileSync(entry.backupPath, entry.originalPath);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
154
|
+
throw new Error(`Backup file missing on disk: ${entry.backupPath}`);
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
149
158
|
console.error(`[backup-manager] Restored ${entry.backupPath} → ${entry.originalPath}`);
|
|
150
159
|
}
|
|
151
160
|
/**
|
|
@@ -174,12 +183,14 @@ export class BackupManager {
|
|
|
174
183
|
}
|
|
175
184
|
for (const entry of remove) {
|
|
176
185
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
186
|
+
// Unlink directly — no prior existsSync to avoid TOCTOU
|
|
187
|
+
unlinkSync(entry.backupPath);
|
|
180
188
|
}
|
|
181
189
|
catch (err) {
|
|
182
|
-
|
|
190
|
+
// ENOENT is fine (already gone); log other errors
|
|
191
|
+
if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
|
|
192
|
+
console.error(`[backup-manager] Failed to delete ${entry.backupPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
193
|
+
}
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
196
|
manifest.backups = keep;
|