nsauditor-ai 0.1.27 → 0.1.29

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 (2) hide show
  1. package/cli.mjs +107 -16
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -90,7 +90,7 @@ function redactSensitiveForAI(input, targetHost) {
90
90
 
91
91
  /* ------------------------- OpenAI & reporting -------------------------- */
92
92
 
93
- async function maybeSendToOpenAI({ host, results, conclusion, promptMode = 'basic' }) {
93
+ async function maybeSendToOpenAI({ host, results, conclusion, promptMode = 'basic', outDir: presetOutDir = null }) {
94
94
  // --- env & opts -----------------------------------------------------------
95
95
  const sendEnabled = parseBool(process.env.AI_ENABLED);
96
96
  const redactEnabled = parseBool(process.env.OPENAI_REDACT, true);
@@ -108,17 +108,17 @@ async function maybeSendToOpenAI({ host, results, conclusion, promptMode = 'basi
108
108
  : await resolveSecret(process.env.OPENAI_API_KEY);
109
109
  const key = keyRaw ? String(keyRaw).trim() : null;
110
110
 
111
- // Base output folder (resolved via the shared helper honors --out and
112
- // the SCAN_OUT_PATH / OPENAI_OUT_PATH env vars consistently with the
113
- // SARIF/CSV/MD writers below).
114
- const baseOutDir = resolveBaseOutDir();
115
-
116
- await fsp.mkdir(baseOutDir, { recursive: true });
117
-
118
- // Per-scan folder
119
- const ts = nowStamp();
120
- const runDir = `${safeHost(host)}_${ts}`;
121
- const outDir = path.join(baseOutDir, runDir);
111
+ // Per-scan folder. Caller (scanSingleHost) may pass a pre-computed outDir so
112
+ // that EE enrichment + compliance artifacts share the same folder as the AI
113
+ // outputs otherwise compute one here for legacy callers.
114
+ let outDir = presetOutDir;
115
+ if (!outDir) {
116
+ const baseOutDir = resolveBaseOutDir();
117
+ await fsp.mkdir(baseOutDir, { recursive: true });
118
+ const ts = nowStamp();
119
+ const runDir = `${safeHost(host)}_${ts}`;
120
+ outDir = path.join(baseOutDir, runDir);
121
+ }
122
122
  await fsp.mkdir(outDir, { recursive: true });
123
123
 
124
124
  // Paths (fixed names inside per-scan folder)
@@ -522,6 +522,14 @@ async function maybeSendToOpenAI({ host, results, conclusion, promptMode = 'basi
522
522
  async function parseArgs(argv) {
523
523
  const args = { cmd: 'scan', host: undefined, plugins: 'all', insecureHttps: false };
524
524
  const a = argv.slice(2);
525
+ // Help: bare `--help`/`-h`/`help` or completely empty invocation.
526
+ // Recognized before the scan-default so it doesn't crash with
527
+ // "--host or --host-file is required" on a help request.
528
+ if (a.length === 0 || a[0] === '--help' || a[0] === '-h' || a[0] === 'help' ||
529
+ a.includes('--help') || a.includes('-h')) {
530
+ args.cmd = 'help';
531
+ return args;
532
+ }
525
533
  if (a.length && !a[0].startsWith('--')) args.cmd = a[0];
526
534
 
527
535
  const get = (name) => {
@@ -536,7 +544,7 @@ async function parseArgs(argv) {
536
544
  const p = get('plugins');
537
545
  if (p && p !== true && p.toLowerCase() !== 'all') {
538
546
  args.plugins = p.split(',').map((s) => s.trim()).filter(Boolean);
539
- } else if (p && p.toLowerCase() === 'all') {
547
+ } else if (p && p !== true && p.toLowerCase() === 'all') {
540
548
  args.plugins = 'all';
541
549
  }
542
550
  args.insecureHttps = !!(get('insecure-https') || get('insecure_https'));
@@ -570,6 +578,13 @@ async function parseArgs(argv) {
570
578
  const alertSev = get('alert-severity') || get('alert_severity') || null;
571
579
  args.alertSeverity = (alertSev && alertSev !== true) ? alertSev.toLowerCase() : 'high';
572
580
 
581
+ // Compliance: framework selector + scope file. Forwarded to EE's
582
+ // runCompliancePhase via enrichScan(). No-op without an EE Enterprise license.
583
+ const complianceVal = get('compliance');
584
+ args.compliance = (complianceVal && complianceVal !== true) ? complianceVal : null;
585
+ const complianceScopeVal = get('compliance-scope') || get('compliance_scope');
586
+ args.complianceScope = (complianceScopeVal && complianceScopeVal !== true) ? complianceScopeVal : null;
587
+
573
588
  return args;
574
589
  }
575
590
 
@@ -599,17 +614,34 @@ async function scanSingleHost(pm, host, plugins, opts, promptMode) {
599
614
  conclusion.result.techniques = techniques;
600
615
  }
601
616
 
617
+ // Pre-compute the per-scan output folder so EE enrichment, compliance
618
+ // artifacts, and AI outputs all land in the same directory. maybeSendToOpenAI
619
+ // will reuse this presetOutDir below.
620
+ const baseOutDir = resolveBaseOutDir();
621
+ await fsp.mkdir(baseOutDir, { recursive: true });
622
+ const ts = nowStamp();
623
+ const outDir = path.join(baseOutDir, `${safeHost(host)}_${ts}`);
624
+ await fsp.mkdir(outDir, { recursive: true });
625
+
602
626
  // EE enrichment hook — no-op if @nsasoft/nsauditor-ai-ee is not installed
627
+ // or the license tier doesn't grant intelligenceEngine. Compliance + outDir
628
+ // are forwarded so EE can write scan_finding_queue.json and SOC 2 artifacts.
603
629
  try {
604
630
  const { enrichScan } = await import('@nsasoft/nsauditor-ai-ee');
605
- const eeEnrichment = await enrichScan(conclusion, { host });
631
+ const eeEnrichment = await enrichScan(conclusion, {
632
+ host,
633
+ outDir,
634
+ compliance: opts.compliance ?? process.env.COMPLIANCE_FRAMEWORKS ?? null,
635
+ complianceScope: opts.complianceScope ?? null,
636
+ onWarn: (msg) => console.warn(`[EE] ${msg}`),
637
+ });
606
638
  if (eeEnrichment?.enrichedPrompt) {
607
639
  conclusion.result = conclusion.result || {};
608
640
  conclusion.result.eeEnrichment = eeEnrichment;
609
641
  }
610
642
  } catch { /* EE not installed — CE proceeds unchanged */ }
611
643
 
612
- const { file_paths: ai_file_paths, ai_conclusion } = await maybeSendToOpenAI({ host, results, conclusion, promptMode });
644
+ const { file_paths: ai_file_paths, ai_conclusion } = await maybeSendToOpenAI({ host, results, conclusion, promptMode, outDir });
613
645
 
614
646
  // --- Scan history: record & compare ---
615
647
  let scanDiff = null;
@@ -724,7 +756,64 @@ function maxSeverityInConclusion(conclusion) {
724
756
  }
725
757
 
726
758
  async function main() {
727
- const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports } = await parseArgs(process.argv);
759
+ const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports, compliance, complianceScope } = await parseArgs(process.argv);
760
+
761
+ // Help: handled before license verification so it works without a key.
762
+ if (cmd === 'help') {
763
+ console.log(`nsauditor-ai — Modular AI-assisted network security audit platform
764
+
765
+ Usage:
766
+ nsauditor-ai [scan] --host <ip|cidr|hostname> [options]
767
+ nsauditor-ai [scan] --host-file <path> [options]
768
+ nsauditor-ai license <subcommand>
769
+ nsauditor-ai security <subcommand>
770
+ nsauditor-ai validate
771
+ nsauditor-ai help
772
+
773
+ Scan options:
774
+ --host, --ip, --target <h> Target host, IP, or CIDR
775
+ --host-file <path> File with one host per line
776
+ --plugins <list|all> Plugins to run (e.g. 001,003,020 or "all"; default: all)
777
+ --ports <range> Override port list (e.g. 22,80,443 or 1-1000)
778
+ --out <dir> Output directory for scan artifacts
779
+ --parallel <n> Parallel host concurrency (default 1)
780
+ --fail-on <severity> Exit non-zero if any finding ≥ severity
781
+ --output-format <fmt> Additional report format: sarif | csv | md
782
+ --insecure-https Skip TLS validation on probed HTTPS targets
783
+ --watch CTEM continuous mode
784
+ --interval <minutes> Watch interval (default 60)
785
+ --webhook-url <url> Send delta alerts (must be public; private/loopback blocked)
786
+ --alert-severity <sev> Min severity to alert on (default: high)
787
+ --compliance <framework> Run compliance mapping (e.g. soc2). Enterprise only.
788
+ --compliance-scope <path> JSON file describing the assessment scope
789
+
790
+ License subcommands:
791
+ nsauditor-ai license --status Show active tier, org, seats, expiry
792
+ nsauditor-ai license --capabilities List active capabilities for current tier
793
+
794
+ Security subcommands (macOS Keychain):
795
+ nsauditor-ai security set <KEY> Store a secret (read from stdin)
796
+ nsauditor-ai security delete <KEY> Remove a secret
797
+ nsauditor-ai security list List stored secrets (masked)
798
+ nsauditor-ai security get <KEY> Echo a secret (avoid in shared shells)
799
+
800
+ Environment:
801
+ NSAUDITOR_LICENSE_KEY Pro/Enterprise license JWT (env var; takes precedence)
802
+ NSA_ALLOW_ALL_HOSTS=1 Permit RFC1918 / loopback (local-network auditing)
803
+ CLOUD_PROVIDER=aws|gcp|azure Required for cloud scanner plugins (020/021/022)
804
+ AI_PROVIDER=openai|claude|ollama AI provider for report generation
805
+ COMPLIANCE_TSA_URL RFC 3161 timestamp authority for SOC 2 attestation
806
+
807
+ Examples:
808
+ nsauditor-ai scan --host 10.0.0.1 --plugins all
809
+ CLOUD_PROVIDER=aws AWS_PROFILE=default \\
810
+ nsauditor-ai scan --host aws --plugins 020
811
+ nsauditor-ai scan --host 10.0.0.0/24 --plugins all --compliance soc2
812
+ nsauditor-ai license --status
813
+
814
+ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/pricing/`);
815
+ process.exit(0);
816
+ }
728
817
 
729
818
  // Verify license JWT at startup (~5ms for ES256). Populates _verifiedTier
730
819
  // so all subsequent getTierFromEnv() calls return the cryptographically
@@ -844,6 +933,8 @@ async function main() {
844
933
 
845
934
  const opts = { insecureHttps };
846
935
  if (ports) opts.ports = ports;
936
+ if (compliance) opts.compliance = compliance;
937
+ if (complianceScope) opts.complianceScope = complianceScope;
847
938
  const pm = await PluginManager.create(`${__dirname}/plugins`);
848
939
  const promptMode = String(process.env.OPENAI_PROMPT_MODE || 'basic').toLowerCase().trim();
849
940
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Modular AI-assisted network security audit platform — Community Edition",
5
5
  "type": "module",
6
6
  "private": false,