nsauditor-ai 0.1.21 → 0.1.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Modular AI-assisted network security audit platform — Community Edition",
5
5
  "type": "module",
6
6
  "private": false,
@@ -123,7 +123,7 @@ function withBaseContext(ctxLike) {
123
123
  return { ...base, ...live };
124
124
  }
125
125
 
126
- async function callPlugin(mod, host, ctx, priorOutputs = null) {
126
+ async function callPlugin(mod, host, ctx, priorOutputs = null, cliOpts = {}) {
127
127
  // Decide if we run once per matching open port, or once total.
128
128
  const req = mod?.requirements || {};
129
129
  const runs = [];
@@ -141,7 +141,10 @@ async function callPlugin(mod, host, ctx, priorOutputs = null) {
141
141
 
142
142
  const runWithCtx = (port) => {
143
143
  const extra = isOsDetector && Array.isArray(priorOutputs) ? { results: priorOutputs } : {};
144
- const pluginPromise = mod.run(host, port, { context: withBaseContext(ctx), ...extra });
144
+ // Forward CLI-derived opts (ports, etc.) so plugins can honor flags like --ports.
145
+ // CLI opts come last so they don't override critical orchestration fields like
146
+ // `context` if the CLI ever accidentally collides on those names.
147
+ const pluginPromise = mod.run(host, port, { ...cliOpts, context: withBaseContext(ctx), ...extra });
145
148
 
146
149
  const timeoutMs = PLUGIN_TIMEOUT_MS;
147
150
  let timer;
@@ -694,8 +697,9 @@ export class PluginManager {
694
697
 
695
698
  vlog(`Running ${mod.name} (priority ${getPriority(mod)}) on ${host}`);
696
699
  // **FIX**: pass prior outputs into OS Detector via callPlugin(..., priorOutputs)
700
+ // **N.27 FIX**: forward CLI-derived opts (ports, etc.) so plugins can honor CLI flags
697
701
  const startMs = Date.now();
698
- const wrappedRuns = await callPlugin(mod, host, ctx, outputs);
702
+ const wrappedRuns = await callPlugin(mod, host, ctx, outputs, opts);
699
703
  const duration_ms = Date.now() - startMs;
700
704
 
701
705
  // Determine manifest status from the plugin results
@@ -18,6 +18,45 @@ function uniqInts(arr = []) {
18
18
  return [...new Set((arr || []).map((x) => Number(x)).filter(Number.isFinite))];
19
19
  }
20
20
 
21
+ /**
22
+ * Parse a CLI-style ports spec string into TCP/UDP port arrays.
23
+ *
24
+ * Accepted formats (entries comma-separated, whitespace tolerated):
25
+ * "8090" → { tcp: [8090], udp: [] }
26
+ * "8090,9090" → { tcp: [8090, 9090], udp: [] }
27
+ * "8090/tcp" → { tcp: [8090], udp: [] }
28
+ * "8090/udp" → { tcp: [], udp: [8090] }
29
+ * "8090,9090/udp" → { tcp: [8090], udp: [9090] }
30
+ * "8090/tcp,9090/udp" → { tcp: [8090], udp: [9090] }
31
+ *
32
+ * Default protocol when not specified: TCP.
33
+ *
34
+ * Malformed entries (non-numeric, out-of-range 1–65535, empty, unknown
35
+ * protocol suffix) are silently skipped — defensive for sloppy CLI input.
36
+ *
37
+ * @param {string} spec
38
+ * @returns {{ tcp: number[], udp: number[] }}
39
+ */
40
+ export function parsePortsSpec(spec) {
41
+ const out = { tcp: [], udp: [] };
42
+ if (typeof spec !== 'string') return out;
43
+ const entries = spec.split(',').map(s => s.trim()).filter(Boolean);
44
+ for (const entry of entries) {
45
+ // Reject entries with more than one '/' separator (e.g. "8090/tcp/extra")
46
+ const parts = entry.split('/');
47
+ if (parts.length > 2) continue;
48
+ const portStr = parts[0];
49
+ const proto = (parts[1] || 'tcp').toLowerCase();
50
+ if (proto !== 'tcp' && proto !== 'udp') continue;
51
+ const port = Number(portStr);
52
+ if (!Number.isInteger(port) || port < 1 || port > 65535) continue;
53
+ out[proto].push(port);
54
+ }
55
+ out.tcp = uniqInts(out.tcp);
56
+ out.udp = uniqInts(out.udp);
57
+ return out;
58
+ }
59
+
21
60
  async function loadConfigPortsFromServicesJson(cwd = process.cwd()) {
22
61
  // Supports the "array schema" used by tests:
23
62
  // { "services": [ { port, protocol }, ... ] }
@@ -179,7 +218,13 @@ export default {
179
218
  const maxBannerBytes = toInt(process.env.TCP_BANNER_MAX_BYTES, 350);
180
219
  const udpPayload = Buffer.from("hi");
181
220
 
182
- // Port sources: opts first, else config/services.json, else empty (tests supply what they need)
221
+ // Port sources, in priority order:
222
+ // 1. Explicit opts.tcpPorts / opts.udpPorts arrays (tests, programmatic API)
223
+ // 2. config/services.json (default well-known port set)
224
+ // 3. Empty
225
+ // Then ADDITIVELY merge opts.ports (CLI --ports flag, comma-separated string with
226
+ // optional /tcp /udp suffix). Additive semantics so that --ports adds extras to the
227
+ // default scan rather than silently replacing it (Task N.27, fixed in v0.1.22).
183
228
  let tcpPorts = Array.isArray(opts.tcpPorts) ? uniqInts(opts.tcpPorts) : [];
184
229
  let udpPorts = Array.isArray(opts.udpPorts) ? uniqInts(opts.udpPorts) : [];
185
230
 
@@ -189,6 +234,13 @@ export default {
189
234
  udpPorts = cfg.udp;
190
235
  }
191
236
 
237
+ // Additive merge of CLI --ports flag (string spec)
238
+ if (typeof opts.ports === 'string' && opts.ports.trim()) {
239
+ const extra = parsePortsSpec(opts.ports);
240
+ tcpPorts = uniqInts([...tcpPorts, ...extra.tcp]);
241
+ udpPorts = uniqInts([...udpPorts, ...extra.udp]);
242
+ }
243
+
192
244
  // If still nothing, just return empty structure
193
245
  if (!tcpPorts.length && !udpPorts.length) {
194
246
  return {