@usex/mikrotik-mcp 5.0.0 → 5.1.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/dist/cli.js +19 -4
- package/dist/index.js +1 -1
- package/dist/shared/{cli-2dj736ma.js → cli-mqb3rqfy.js} +27 -5
- package/dist/shared/{cli-vvy7yfyg.js → cli-s9fc4c7j.js} +1 -1
- package/dist/shared/{library-13dhfdnq.js → library-tq8phch8.js} +31 -5
- package/dist/shared/{library-syt5fmtj.js → library-xgy70fsf.js} +1 -1
- package/dist/ui/observability.html +53 -52
- package/package.json +1 -1
- package/schemas/tool-catalog.json +8 -2
- package/schemas/tools/scan_for_attacks.json +6 -0
package/dist/cli.js
CHANGED
|
@@ -230,7 +230,7 @@ import {
|
|
|
230
230
|
validatePolicyText,
|
|
231
231
|
worstSeverity,
|
|
232
232
|
writeBackup
|
|
233
|
-
} from "./shared/cli-
|
|
233
|
+
} from "./shared/cli-mqb3rqfy.js";
|
|
234
234
|
|
|
235
235
|
// src/cli.ts
|
|
236
236
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -2703,12 +2703,27 @@ async function attackRoutes(req, url) {
|
|
|
2703
2703
|
}, 501);
|
|
2704
2704
|
}
|
|
2705
2705
|
if (p === "/api/attacks/scan" && req.method === "POST") {
|
|
2706
|
-
|
|
2707
|
-
const
|
|
2706
|
+
const body = await req.json().catch(() => ({}));
|
|
2707
|
+
const scope = body.devices?.length ? body.devices.join(", ") : body.onlineOnly ? "reachable devices" : "every device";
|
|
2708
|
+
logger.info(`attack sweep requested from the dashboard (${scope})`);
|
|
2709
|
+
const result = await sweep({
|
|
2710
|
+
devices: body.devices?.length ? body.devices : undefined,
|
|
2711
|
+
onlineOnly: body.onlineOnly,
|
|
2712
|
+
respond: false
|
|
2713
|
+
});
|
|
2708
2714
|
return json9({
|
|
2709
2715
|
incidents: result.incidents,
|
|
2710
2716
|
unavailable: result.unavailable,
|
|
2711
|
-
devices: result.devices
|
|
2717
|
+
devices: result.devices,
|
|
2718
|
+
skipped: result.skipped
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
if (p === "/api/attacks/devices" && req.method === "GET") {
|
|
2722
|
+
return json9({
|
|
2723
|
+
devices: listDevices().names.map((name) => ({
|
|
2724
|
+
name,
|
|
2725
|
+
reachable: getDeviceStatus(name).reachable
|
|
2726
|
+
}))
|
|
2712
2727
|
});
|
|
2713
2728
|
}
|
|
2714
2729
|
const rest = p.slice("/api/attacks/".length).split("/").filter(Boolean);
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
selectToolModules,
|
|
30
30
|
setConfig,
|
|
31
31
|
updateSummaryLine
|
|
32
|
-
} from "./shared/library-
|
|
32
|
+
} from "./shared/library-tq8phch8.js";
|
|
33
33
|
// src/server.ts
|
|
34
34
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
35
35
|
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -10832,7 +10832,7 @@ var cache2 = null;
|
|
|
10832
10832
|
async function gateway() {
|
|
10833
10833
|
if (cache2)
|
|
10834
10834
|
return cache2;
|
|
10835
|
-
const { moduleCatalog } = await import("./cli-
|
|
10835
|
+
const { moduleCatalog } = await import("./cli-s9fc4c7j.js");
|
|
10836
10836
|
const forIndex = [];
|
|
10837
10837
|
const byName = new Map;
|
|
10838
10838
|
for (const mod of moduleCatalog) {
|
|
@@ -34834,7 +34834,7 @@ var riskByTool = null;
|
|
|
34834
34834
|
async function riskIndex() {
|
|
34835
34835
|
if (riskByTool)
|
|
34836
34836
|
return riskByTool;
|
|
34837
|
-
const { moduleCatalog } = await import("./cli-
|
|
34837
|
+
const { moduleCatalog } = await import("./cli-s9fc4c7j.js");
|
|
34838
34838
|
const index = new Map;
|
|
34839
34839
|
for (const mod of moduleCatalog) {
|
|
34840
34840
|
for (const tool of mod.tools)
|
|
@@ -37566,11 +37566,28 @@ function policyFromConfig() {
|
|
|
37566
37566
|
autoRespondTo: cfg.autoRespondTo
|
|
37567
37567
|
};
|
|
37568
37568
|
}
|
|
37569
|
+
function resolveTargets2(spec) {
|
|
37570
|
+
const all = spec.devices ?? listDevices().names;
|
|
37571
|
+
if (!spec.onlineOnly)
|
|
37572
|
+
return { targets: all, skipped: [] };
|
|
37573
|
+
const targets = [];
|
|
37574
|
+
const skipped = [];
|
|
37575
|
+
for (const device of all) {
|
|
37576
|
+
if (getDeviceStatus(device).reachable === false)
|
|
37577
|
+
skipped.push(device);
|
|
37578
|
+
else
|
|
37579
|
+
targets.push(device);
|
|
37580
|
+
}
|
|
37581
|
+
return { targets, skipped };
|
|
37582
|
+
}
|
|
37569
37583
|
async function sweep(options = {}) {
|
|
37570
37584
|
const cfg = getConfig().attacks;
|
|
37571
37585
|
const now = options.now ?? Date.now();
|
|
37572
37586
|
const windowMinutes = options.windowMinutes ?? cfg.windowMinutes;
|
|
37573
|
-
const targets
|
|
37587
|
+
const { targets, skipped } = resolveTargets2({
|
|
37588
|
+
devices: options.devices,
|
|
37589
|
+
onlineOnly: options.onlineOnly
|
|
37590
|
+
});
|
|
37574
37591
|
const store3 = await attackStore().catch(() => null);
|
|
37575
37592
|
const signals = [];
|
|
37576
37593
|
const unavailable = [];
|
|
@@ -37674,7 +37691,7 @@ async function sweep(options = {}) {
|
|
|
37674
37691
|
});
|
|
37675
37692
|
}
|
|
37676
37693
|
}
|
|
37677
|
-
return { incidents, unavailable, devices: deviceResults, responses };
|
|
37694
|
+
return { incidents, unavailable, devices: deviceResults, responses, skipped };
|
|
37678
37695
|
}
|
|
37679
37696
|
var timer3 = null;
|
|
37680
37697
|
var running2 = false;
|
|
@@ -37726,15 +37743,17 @@ var attackTools = [
|
|
|
37726
37743
|
title: "Scan for Attacks Now",
|
|
37727
37744
|
annotations: READ,
|
|
37728
37745
|
noDevice: true,
|
|
37729
|
-
description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network.",
|
|
37746
|
+
description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network. Pass `devices` for one router, or `online_only` to skip " + "the ones currently unreachable \u2014 any device left out is named in the result.",
|
|
37730
37747
|
inputSchema: {
|
|
37731
37748
|
devices: z116.array(z116.string()).optional().describe("Limit to these devices (default: all)."),
|
|
37749
|
+
online_only: z116.boolean().default(false).describe("Skip devices the health probe currently reports as unreachable. A device not yet " + "probed is still scanned \u2014 never probed is not the same as known-offline."),
|
|
37732
37750
|
window_minutes: z116.coerce.number().int().positive().max(1440).optional().describe("How far back to read. Default from config (10).")
|
|
37733
37751
|
},
|
|
37734
37752
|
async handler(a, ctx) {
|
|
37735
37753
|
ctx.info("Scanning the fleet for attacks");
|
|
37736
37754
|
const result = await sweep({
|
|
37737
37755
|
devices: a.devices,
|
|
37756
|
+
onlineOnly: a.online_only,
|
|
37738
37757
|
windowMinutes: a.window_minutes,
|
|
37739
37758
|
respond: false
|
|
37740
37759
|
});
|
|
@@ -37743,6 +37762,9 @@ var attackTools = [
|
|
|
37743
37762
|
lines.push(`Scanned ${result.devices.length} device(s), ${result.devices.reduce((n, d) => n + d.events, 0)} log event(s).`);
|
|
37744
37763
|
for (const f of failed)
|
|
37745
37764
|
lines.push(` \u2717 ${f.device}: ${f.error}`);
|
|
37765
|
+
if (result.skipped.length > 0) {
|
|
37766
|
+
lines.push(` Skipped as unreachable: ${result.skipped.join(", ")}`);
|
|
37767
|
+
}
|
|
37746
37768
|
lines.push("");
|
|
37747
37769
|
if (result.incidents.length === 0) {
|
|
37748
37770
|
lines.push("No attack incidents found in this window.");
|
|
@@ -10607,7 +10607,7 @@ var cache2 = null;
|
|
|
10607
10607
|
async function gateway() {
|
|
10608
10608
|
if (cache2)
|
|
10609
10609
|
return cache2;
|
|
10610
|
-
const { moduleCatalog } = await import("./library-
|
|
10610
|
+
const { moduleCatalog } = await import("./library-xgy70fsf.js");
|
|
10611
10611
|
const forIndex = [];
|
|
10612
10612
|
const byName = new Map;
|
|
10613
10613
|
for (const mod of moduleCatalog) {
|
|
@@ -32930,6 +32930,10 @@ var HISTORY_CAP = 60;
|
|
|
32930
32930
|
var statuses = new Map;
|
|
32931
32931
|
var histories = new Map;
|
|
32932
32932
|
var neighbors = new Map;
|
|
32933
|
+
var UNKNOWN = { reachable: null, checkedAt: null, latencyMs: null };
|
|
32934
|
+
function getDeviceStatus(name) {
|
|
32935
|
+
return statuses.get(name) ?? UNKNOWN;
|
|
32936
|
+
}
|
|
32933
32937
|
function pushHistory(name, sample) {
|
|
32934
32938
|
const arr = histories.get(name) ?? [];
|
|
32935
32939
|
arr.push(sample);
|
|
@@ -34330,7 +34334,7 @@ var riskByTool = null;
|
|
|
34330
34334
|
async function riskIndex() {
|
|
34331
34335
|
if (riskByTool)
|
|
34332
34336
|
return riskByTool;
|
|
34333
|
-
const { moduleCatalog } = await import("./library-
|
|
34337
|
+
const { moduleCatalog } = await import("./library-xgy70fsf.js");
|
|
34334
34338
|
const index = new Map;
|
|
34335
34339
|
for (const mod of moduleCatalog) {
|
|
34336
34340
|
for (const tool of mod.tools)
|
|
@@ -37004,11 +37008,28 @@ function policyFromConfig() {
|
|
|
37004
37008
|
autoRespondTo: cfg.autoRespondTo
|
|
37005
37009
|
};
|
|
37006
37010
|
}
|
|
37011
|
+
function resolveTargets2(spec) {
|
|
37012
|
+
const all = spec.devices ?? listDevices().names;
|
|
37013
|
+
if (!spec.onlineOnly)
|
|
37014
|
+
return { targets: all, skipped: [] };
|
|
37015
|
+
const targets = [];
|
|
37016
|
+
const skipped = [];
|
|
37017
|
+
for (const device of all) {
|
|
37018
|
+
if (getDeviceStatus(device).reachable === false)
|
|
37019
|
+
skipped.push(device);
|
|
37020
|
+
else
|
|
37021
|
+
targets.push(device);
|
|
37022
|
+
}
|
|
37023
|
+
return { targets, skipped };
|
|
37024
|
+
}
|
|
37007
37025
|
async function sweep(options = {}) {
|
|
37008
37026
|
const cfg = getConfig().attacks;
|
|
37009
37027
|
const now = options.now ?? Date.now();
|
|
37010
37028
|
const windowMinutes = options.windowMinutes ?? cfg.windowMinutes;
|
|
37011
|
-
const targets
|
|
37029
|
+
const { targets, skipped } = resolveTargets2({
|
|
37030
|
+
devices: options.devices,
|
|
37031
|
+
onlineOnly: options.onlineOnly
|
|
37032
|
+
});
|
|
37012
37033
|
const store3 = await attackStore().catch(() => null);
|
|
37013
37034
|
const signals = [];
|
|
37014
37035
|
const unavailable = [];
|
|
@@ -37112,7 +37133,7 @@ async function sweep(options = {}) {
|
|
|
37112
37133
|
});
|
|
37113
37134
|
}
|
|
37114
37135
|
}
|
|
37115
|
-
return { incidents, unavailable, devices: deviceResults, responses };
|
|
37136
|
+
return { incidents, unavailable, devices: deviceResults, responses, skipped };
|
|
37116
37137
|
}
|
|
37117
37138
|
|
|
37118
37139
|
// src/tools/attack.ts
|
|
@@ -37137,15 +37158,17 @@ var attackTools = [
|
|
|
37137
37158
|
title: "Scan for Attacks Now",
|
|
37138
37159
|
annotations: READ,
|
|
37139
37160
|
noDevice: true,
|
|
37140
|
-
description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network.",
|
|
37161
|
+
description: "Reads recent logs from every configured device, runs the attack detectors, and correlates " + "what they find into incidents \u2014 brute force, credential spraying, a login that SUCCEEDED " + "after failures, port scans, management ports answering the internet, and unexplained " + "changes to security-relevant configuration. Read-only: it changes nothing on any device. " + "One source hitting several routers is reported as ONE incident, which is the pattern no " + "single router can see. Detectors whose input is missing say so and name the fix, rather " + "than reporting a quiet network. Pass `devices` for one router, or `online_only` to skip " + "the ones currently unreachable \u2014 any device left out is named in the result.",
|
|
37141
37162
|
inputSchema: {
|
|
37142
37163
|
devices: z116.array(z116.string()).optional().describe("Limit to these devices (default: all)."),
|
|
37164
|
+
online_only: z116.boolean().default(false).describe("Skip devices the health probe currently reports as unreachable. A device not yet " + "probed is still scanned \u2014 never probed is not the same as known-offline."),
|
|
37143
37165
|
window_minutes: z116.coerce.number().int().positive().max(1440).optional().describe("How far back to read. Default from config (10).")
|
|
37144
37166
|
},
|
|
37145
37167
|
async handler(a, ctx) {
|
|
37146
37168
|
ctx.info("Scanning the fleet for attacks");
|
|
37147
37169
|
const result = await sweep({
|
|
37148
37170
|
devices: a.devices,
|
|
37171
|
+
onlineOnly: a.online_only,
|
|
37149
37172
|
windowMinutes: a.window_minutes,
|
|
37150
37173
|
respond: false
|
|
37151
37174
|
});
|
|
@@ -37154,6 +37177,9 @@ var attackTools = [
|
|
|
37154
37177
|
lines.push(`Scanned ${result.devices.length} device(s), ${result.devices.reduce((n, d) => n + d.events, 0)} log event(s).`);
|
|
37155
37178
|
for (const f of failed)
|
|
37156
37179
|
lines.push(` \u2717 ${f.device}: ${f.error}`);
|
|
37180
|
+
if (result.skipped.length > 0) {
|
|
37181
|
+
lines.push(` Skipped as unreachable: ${result.skipped.join(", ")}`);
|
|
37182
|
+
}
|
|
37157
37183
|
lines.push("");
|
|
37158
37184
|
if (result.incidents.length === 0) {
|
|
37159
37185
|
lines.push("No attack incidents found in this window.");
|