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.
- package/README.md +76 -62
- package/cli.mjs +210 -179
- package/mcp_server.mjs +71 -31
- package/package.json +1 -1
- package/plugin_manager.mjs +49 -8
- package/plugins/040_tls_cert_auditor.mjs +176 -56
- package/plugins/050_tribe_health.mjs +13 -34
- package/plugins/060_dns_sec_auditor.mjs +24 -30
- package/plugins/dns_scanner.mjs +16 -8
- package/plugins/ftp_banner_check.mjs +21 -13
- package/plugins/host_up_check.mjs +31 -72
- package/plugins/http_probe.mjs +93 -25
- package/plugins/mcp_scanner.mjs +15 -1
- package/plugins/netbios_scanner.mjs +32 -13
- package/plugins/opensearch_scanner.mjs +4 -0
- package/plugins/port_scanner.mjs +29 -49
- package/plugins/result_concluder.mjs +122 -30
- package/plugins/snmp_scanner.mjs +20 -8
- package/plugins/syn_scanner.mjs +2 -1
- package/plugins/upnp_scanner.mjs +82 -13
- package/plugins/webapp_detector.mjs +82 -15
- package/utils/conclusion_utils.mjs +13 -1
- package/utils/cookie_redaction.mjs +50 -0
- package/utils/delta_reporter.mjs +70 -25
- package/utils/ee_load.mjs +45 -0
- package/utils/executive_report.mjs +48 -8
- package/utils/export_csv.mjs +14 -22
- package/utils/mcp_call_digest.mjs +31 -0
- package/utils/net_validation.mjs +107 -37
- package/utils/output_dir.mjs +70 -0
- package/utils/plugin_discovery.mjs +4 -2
- package/utils/ports_spec.mjs +46 -0
- package/utils/report_inputs.mjs +64 -1
- package/utils/report_md.mjs +51 -112
- package/utils/sarif.mjs +33 -93
- package/utils/scan_delta.mjs +514 -38
- package/utils/scan_delta_view.mjs +5 -3
- package/utils/scan_history.mjs +149 -0
- package/utils/scheduler.mjs +17 -5
- package/utils/service_flags.mjs +363 -0
- package/utils/watch_cycle.mjs +37 -0
- 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
|
-
*
|
|
39
|
-
*
|
|
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(
|
|
43
|
-
|
|
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
|
-
*
|
|
66
|
-
*
|
|
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
|
|
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: '
|
|
91
|
-
helpUri:
|
|
92
|
-
properties: { severity:
|
|
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(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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(
|
|
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
|
|
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,
|