nsauditor-ai 0.2.55 → 0.2.57

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.
Files changed (42) hide show
  1. package/README.md +76 -62
  2. package/cli.mjs +210 -179
  3. package/mcp_server.mjs +71 -31
  4. package/package.json +1 -1
  5. package/plugin_manager.mjs +49 -8
  6. package/plugins/040_tls_cert_auditor.mjs +176 -56
  7. package/plugins/050_tribe_health.mjs +13 -34
  8. package/plugins/060_dns_sec_auditor.mjs +24 -30
  9. package/plugins/dns_scanner.mjs +16 -8
  10. package/plugins/ftp_banner_check.mjs +21 -13
  11. package/plugins/host_up_check.mjs +31 -72
  12. package/plugins/http_probe.mjs +93 -25
  13. package/plugins/mcp_scanner.mjs +15 -1
  14. package/plugins/netbios_scanner.mjs +32 -13
  15. package/plugins/opensearch_scanner.mjs +4 -0
  16. package/plugins/port_scanner.mjs +29 -49
  17. package/plugins/result_concluder.mjs +122 -30
  18. package/plugins/snmp_scanner.mjs +20 -8
  19. package/plugins/syn_scanner.mjs +2 -1
  20. package/plugins/upnp_scanner.mjs +82 -13
  21. package/plugins/webapp_detector.mjs +82 -15
  22. package/utils/conclusion_utils.mjs +13 -1
  23. package/utils/cookie_redaction.mjs +50 -0
  24. package/utils/delta_reporter.mjs +70 -25
  25. package/utils/ee_load.mjs +45 -0
  26. package/utils/executive_report.mjs +48 -8
  27. package/utils/export_csv.mjs +14 -22
  28. package/utils/mcp_call_digest.mjs +31 -0
  29. package/utils/net_validation.mjs +107 -37
  30. package/utils/output_dir.mjs +70 -0
  31. package/utils/plugin_discovery.mjs +4 -2
  32. package/utils/ports_spec.mjs +46 -0
  33. package/utils/report_inputs.mjs +64 -1
  34. package/utils/report_md.mjs +51 -112
  35. package/utils/sarif.mjs +33 -93
  36. package/utils/scan_delta.mjs +514 -38
  37. package/utils/scan_delta_view.mjs +5 -3
  38. package/utils/scan_history.mjs +149 -0
  39. package/utils/scheduler.mjs +17 -5
  40. package/utils/service_flags.mjs +363 -0
  41. package/utils/watch_cycle.mjs +37 -0
  42. package/utils/webhook.mjs +2 -1
package/utils/sarif.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  // Generate SARIF 2.1.0 output from nsauditor scan results.
3
3
 
4
4
  import { createRequire } from 'node:module';
5
+ import { flagFindings, conclusionFindings, OPEN_SERVICE_SEVERITY } from './service_flags.mjs';
5
6
  const require = createRequire(import.meta.url);
6
7
  const { version: TOOL_VERSION } = require('../package.json');
7
8
 
@@ -35,13 +36,12 @@ function ruleIdFromService(svc) {
35
36
  }
36
37
 
37
38
  /**
38
- * Infer severity from a service's status and other indicators.
39
- * @param {object} svc
39
+ * The "service detected" result's grade: OPEN_SERVICE_SEVERITY from the shared table (1.3.0 (s2)) — an open port is
40
+ * inventory, never a warning of its own. A service that is not open is never graded above it.
40
41
  * @returns {string}
41
42
  */
42
- function inferServiceSeverity(svc) {
43
- if (svc.status === 'open') return 'Medium';
44
- return 'Info';
43
+ function inferServiceSeverity() {
44
+ return OPEN_SERVICE_SEVERITY;
45
45
  }
46
46
 
47
47
  /**
@@ -62,101 +62,31 @@ function buildServiceMessage(svc, host) {
62
62
  }
63
63
 
64
64
  /**
65
- * Build SARIF result entries for security findings on a service.
66
- * @param {object} svc
65
+ * SARIF rule + result entries for graded findings. Every grade comes from the shared service-flag table
66
+ * (utils/service_flags.mjs, 1.3.0 (s1)); the rule ids of the four flags SARIF graded before 1.3.0 are unchanged, since a
67
+ * code-scanning alert is keyed on its rule id.
68
+ * @param {object[]} findings - from the table, each with key/severity/title/ruleId/evidence
67
69
  * @param {string} host
70
+ * @param {string} where - the location text in the message (host:port/protocol, or the host for an evidence payload)
68
71
  * @returns {{ results: object[], rules: object[] }}
69
72
  */
70
- function securityFindingsFromService(svc, host) {
73
+ function sarifEntries(findings, host, where) {
71
74
  const results = [];
72
75
  const rules = [];
73
-
74
- const makeResult = (ruleId, level, message) => ({
75
- ruleId,
76
- level,
77
- message: { text: message },
78
- locations: [{
79
- physicalLocation: {
80
- artifactLocation: { uri: host }
81
- }
82
- }]
83
- });
84
-
85
- // anonymousLogin: true
86
- if (svc.anonymousLogin === true) {
87
- const ruleId = 'ftp-anonymous-login';
76
+ for (const f of findings) {
88
77
  rules.push({
89
- id: ruleId,
90
- shortDescription: { text: 'FTP anonymous login enabled' },
91
- helpUri: `${TOOL_URI}`,
92
- properties: { severity: 'Critical' }
78
+ id: f.ruleId,
79
+ shortDescription: { text: f.key === 'cves' ? `Known vulnerability: ${f.ruleId}` : f.title },
80
+ helpUri: f.key === 'cves' ? `https://nvd.nist.gov/vuln/detail/${f.ruleId}` : TOOL_URI,
81
+ properties: { severity: f.severity }
93
82
  });
94
- results.push(makeResult(ruleId, 'error',
95
- `FTP anonymous login is enabled on ${host}:${svc.port}. This allows unauthenticated access to the FTP server.`));
96
- }
97
-
98
- // axfrAllowed: true
99
- if (svc.axfrAllowed === true) {
100
- const ruleId = 'dns-zone-transfer';
101
- rules.push({
102
- id: ruleId,
103
- shortDescription: { text: 'DNS zone transfer (AXFR) allowed' },
104
- helpUri: `${TOOL_URI}`,
105
- properties: { severity: 'Critical' }
83
+ results.push({
84
+ ruleId: f.ruleId,
85
+ level: severityToLevel(f.severity),
86
+ message: { text: `${f.title} on ${where}.${f.evidence ? ` ${f.evidence}` : ''}` },
87
+ locations: [{ physicalLocation: { artifactLocation: { uri: host } } }]
106
88
  });
107
- results.push(makeResult(ruleId, 'error',
108
- `DNS zone transfer (AXFR) is allowed on ${host}:${svc.port}. This can expose the entire DNS zone to attackers.`));
109
- }
110
-
111
- // weakAlgorithms: [...]
112
- if (Array.isArray(svc.weakAlgorithms) && svc.weakAlgorithms.length > 0) {
113
- for (const algo of svc.weakAlgorithms) {
114
- const algoName = typeof algo === 'string' ? algo : (algo?.algorithm || algo?.name || String(algo));
115
- const ruleId = `weak-algorithm-${algoName.replace(/[^a-zA-Z0-9._-]/g, '-')}`;
116
- rules.push({
117
- id: ruleId,
118
- shortDescription: { text: `Weak algorithm: ${algoName}` },
119
- helpUri: `${TOOL_URI}`,
120
- properties: { severity: 'Medium' }
121
- });
122
- results.push(makeResult(ruleId, 'warning',
123
- `Weak algorithm "${algoName}" is supported on ${host}:${svc.port}/${svc.protocol || 'tcp'}.`));
124
- }
125
89
  }
126
-
127
- // dangerousMethods: [...]
128
- if (Array.isArray(svc.dangerousMethods) && svc.dangerousMethods.length > 0) {
129
- for (const method of svc.dangerousMethods) {
130
- const ruleId = `http-dangerous-method-${String(method).toLowerCase()}`;
131
- rules.push({
132
- id: ruleId,
133
- shortDescription: { text: `Dangerous HTTP method: ${method}` },
134
- helpUri: `${TOOL_URI}`,
135
- properties: { severity: 'Medium' }
136
- });
137
- results.push(makeResult(ruleId, 'warning',
138
- `Dangerous HTTP method "${method}" is allowed on ${host}:${svc.port}.`));
139
- }
140
- }
141
-
142
- // CVE data (if service has cves or cve array)
143
- const cves = svc.cves || svc.cve || [];
144
- if (Array.isArray(cves)) {
145
- for (const cve of cves) {
146
- const cveId = typeof cve === 'string' ? cve : (cve?.id || cve?.cveId || String(cve));
147
- const cveSeverity = cve?.severity || 'High';
148
- const ruleId = cveId;
149
- rules.push({
150
- id: ruleId,
151
- shortDescription: { text: `Known vulnerability: ${cveId}` },
152
- helpUri: `https://nvd.nist.gov/vuln/detail/${cveId}`,
153
- properties: { severity: cveSeverity }
154
- });
155
- results.push(makeResult(ruleId, severityToLevel(cveSeverity),
156
- `${cveId} affects ${svc.program || svc.service}${svc.version && svc.version !== 'Unknown' ? ' ' + svc.version : ''} on ${host}:${svc.port}.`));
157
- }
158
- }
159
-
160
90
  return { results, rules };
161
91
  }
162
92
 
@@ -175,7 +105,7 @@ export function buildSarifLog(scanData) {
175
105
  for (const svc of services) {
176
106
  // Base service result
177
107
  const ruleId = ruleIdFromService(svc);
178
- const severity = inferServiceSeverity(svc);
108
+ const severity = inferServiceSeverity();
179
109
  const level = severityToLevel(severity);
180
110
  const message = buildServiceMessage(svc, host);
181
111
 
@@ -200,13 +130,23 @@ export function buildSarifLog(scanData) {
200
130
  });
201
131
 
202
132
  // Security findings
203
- const { results: secResults, rules: secRules } = securityFindingsFromService(svc, host);
133
+ const where = `${host}:${svc.port}/${svc.protocol || 'tcp'}`;
134
+ const { results: secResults, rules: secRules } = sarifEntries(flagFindings(svc, { target: where }), host, where);
204
135
  for (const sr of secResults) sarifResults.push(sr);
205
136
  for (const rule of secRules) {
206
137
  if (!rulesMap.has(rule.id)) rulesMap.set(rule.id, rule);
207
138
  }
208
139
  }
209
140
 
141
+ // An adapter payload that landed in EVIDENCE (no port — a domain's DNS posture when no 53/udp service was found) is
142
+ // graded too, located at the host: its grade must not depend on whether an unrelated port answered.
143
+ const onEvidence = conclusionFindings({ evidence: conclusion?.result?.evidence ?? [] }, host);
144
+ const { results: evResults, rules: evRules } = sarifEntries(onEvidence, host, String(host));
145
+ for (const sr of evResults) sarifResults.push(sr);
146
+ for (const rule of evRules) {
147
+ if (!rulesMap.has(rule.id)) rulesMap.set(rule.id, rule);
148
+ }
149
+
210
150
  return {
211
151
  $schema: SARIF_SCHEMA,
212
152
  version: SARIF_VERSION,