nsauditor-ai 0.1.10 → 0.1.12

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 CHANGED
@@ -7,7 +7,7 @@ A modular, AI-assisted network security audit platform that scans, understands,
7
7
  [![npm](https://img.shields.io/npm/v/nsauditor-ai.svg)](https://www.npmjs.com/package/nsauditor-ai)
8
8
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
9
  [![Node.js 20+](https://img.shields.io/badge/node-20%2B-green.svg)](https://nodejs.org)
10
- [![Tests](https://img.shields.io/badge/tests-493%20passing-brightgreen.svg)](#tests)
10
+ [![Tests](https://img.shields.io/badge/tests-506%20passing-brightgreen.svg)](#tests)
11
11
 
12
12
  ---
13
13
 
@@ -532,17 +532,13 @@ export default {
532
532
 
533
533
  ## Pro & Enterprise Activation
534
534
 
535
- Install the EE package alongside the CE platform:
535
+ After purchasing at [nsauditor.com/ai/pricing](https://www.nsauditor.com/ai/pricing), you'll receive an email with your license key and an npm install command. Two steps:
536
536
 
537
537
  ```bash
538
- npm install -g @nsasoft/nsauditor-ai-ee
539
- ```
540
-
541
- Set your license key:
538
+ # 1. Install EE package (one-time, token included in email)
539
+ npm install -g @nsasoft/nsauditor-ai-ee --//registry.npmjs.org/:_authToken=npm_xxxxx
542
540
 
543
- ```bash
544
- echo "NSAUDITOR_LICENSE_KEY=pro_eyJhbGci..." >> ~/.nsauditor/.env
545
- # or export directly
541
+ # 2. Set your license key
546
542
  export NSAUDITOR_LICENSE_KEY=pro_eyJhbGci...
547
543
  ```
548
544
 
@@ -556,6 +552,8 @@ nsauditor-ai license --capabilities
556
552
  # ✓ intelligenceEngine ✓ riskScoring ✓ proAI ✓ advancedCTEM ...
557
553
  ```
558
554
 
555
+ License keys are delivered automatically via Stripe webhook — no manual processing. Subscription renewals generate a fresh key and email it to you before the current one expires.
556
+
559
557
  No license key? Everything in this repository works perfectly without one. The CE is not crippled — it's a complete, production-ready security scanner.
560
558
 
561
559
  → [Pricing](https://www.nsauditor.com/ai/pricing) · [Start free trial](https://www.nsauditor.com/ai/trial) · [Enterprise contact](https://www.nsauditor.com/ai/enterprise)
@@ -564,7 +562,7 @@ No license key? Everything in this repository works perfectly without one. The C
564
562
 
565
563
  ## Tests
566
564
 
567
- Run all 487 tests:
565
+ Run all 506 tests:
568
566
 
569
567
  ```bash
570
568
  npm test
package/cli.mjs CHANGED
@@ -13,7 +13,7 @@ import { parseHostArg, parseHostFile } from './utils/host_iterator.mjs';
13
13
  import { buildSarifLog } from './utils/sarif.mjs';
14
14
  import { buildCsv } from './utils/export_csv.mjs';
15
15
  import { recordScan, getLastScan, computeDiff, formatDiffReport, pruneForCE, HISTORY_FILE } from './utils/scan_history.mjs';
16
- import { getTierFromEnv } from './utils/license.mjs';
16
+ import { getTierFromEnv, loadLicense } from './utils/license.mjs';
17
17
  import { resolveCapabilities, hasCapability } from './utils/capabilities.mjs';
18
18
  import { createScheduler } from './utils/scheduler.mjs';
19
19
  import { buildDeltaReport, formatDeltaSummary, hasSignificantChanges } from './utils/delta_reporter.mjs';
@@ -722,23 +722,35 @@ function maxSeverityInConclusion(conclusion) {
722
722
  async function main() {
723
723
  const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports } = await parseArgs(process.argv);
724
724
 
725
+ // Verify license JWT at startup (~5ms for ES256). Populates _verifiedTier
726
+ // so all subsequent getTierFromEnv() calls return the cryptographically
727
+ // validated tier instead of relying on prefix detection alone.
728
+ await loadLicense();
729
+
725
730
  if (cmd === 'license') {
726
- const { getTierFromEnv } = await import('./utils/license.mjs');
727
731
  const { resolveCapabilities } = await import('./utils/capabilities.mjs');
728
- // TODO: replace getTierFromEnv() with loadLicense() for full license validation
729
- const tier = getTierFromEnv();
730
- const caps = resolveCapabilities(tier);
731
732
  const key = process.env.NSAUDITOR_LICENSE_KEY;
732
733
  const rawArgs = process.argv.slice(2);
733
734
 
734
735
  if (rawArgs.includes('--status')) {
736
+ const result = await loadLicense(key);
735
737
  const tierLabel = { ce: 'Community Edition (CE)', pro: 'Pro', enterprise: 'Enterprise' };
736
- console.log(`License status: ${tierLabel[tier] ?? tier}`);
737
- console.log(`License key: ${key ? `set (${key.slice(0, 8)}...)` : 'not set — running CE'}`);
738
- if (!key) {
739
- console.log('\n→ Start a free 14-day Pro trial: https://www.nsauditor.com/ai/trial');
738
+ if (result.valid) {
739
+ console.log(`✓ ${tierLabel[result.tier]} license active`);
740
+ console.log(` Org: ${result.org}`);
741
+ console.log(` Seats: ${result.seats}`);
742
+ console.log(` License ID: ${result.licenseId}`);
743
+ console.log(` Expires: ${result.expiresAt}`);
744
+ } else {
745
+ console.log(`✗ ${tierLabel[result.tier] ?? 'Community Edition (CE)'}`);
746
+ console.log(` Reason: ${result.reason}`);
747
+ if (!key) {
748
+ console.log('\n→ Start a free 14-day Pro trial: https://www.nsauditor.com/ai/trial');
749
+ }
740
750
  }
741
751
  } else if (rawArgs.includes('--capabilities')) {
752
+ const tier = getTierFromEnv();
753
+ const caps = resolveCapabilities(tier);
742
754
  console.log(`Active capabilities for tier: ${tier}\n`);
743
755
  for (const [name, enabled] of Object.entries(caps)) {
744
756
  console.log(` ${enabled ? '✓' : '✗'} ${name}`);
package/mcp_server.mjs CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  CallToolRequestSchema,
20
20
  ListToolsRequestSchema,
21
21
  } from '@modelcontextprotocol/sdk/types.js';
22
- import { getTierFromEnv } from './utils/license.mjs';
22
+ import { getTierFromEnv, loadLicense } from './utils/license.mjs';
23
23
  import { resolveCapabilities } from './utils/capabilities.mjs';
24
24
 
25
25
  const _require = createRequire(import.meta.url);
@@ -29,7 +29,8 @@ const { version: TOOL_VERSION } = _require('./package.json');
29
29
  // License tier & capability resolution (module-level, overridable for tests)
30
30
  // ---------------------------------------------------------------------------
31
31
 
32
- // TODO: replace getTierFromEnv() with loadLicense() for full license validation.
32
+ // Module-level: prefix-based tier for immediate use. loadLicense() runs async
33
+ // at server startup (below) and upgrades _tier to the cryptographically verified value.
33
34
  let _tier = getTierFromEnv();
34
35
  let _capabilities = resolveCapabilities(_tier);
35
36
 
@@ -380,6 +381,12 @@ const isMainModule =
380
381
  process.argv[1].endsWith('mcp_server'));
381
382
 
382
383
  if (isMainModule) {
384
+ // Verify license JWT before accepting MCP requests — upgrades _tier from
385
+ // prefix-based to cryptographically verified.
386
+ await loadLicense();
387
+ _tier = getTierFromEnv();
388
+ _capabilities = resolveCapabilities(_tier);
389
+
383
390
  const server = createServer();
384
391
  const transport = new StdioServerTransport();
385
392
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Modular AI-assisted network security audit platform — Community Edition",
5
5
  "type": "module",
6
6
  "private": false,
@@ -25,6 +25,7 @@
25
25
  "oui-data": "^1.1.427",
26
26
  "simple-wappalyzer": "^1.1.75",
27
27
  "snmp-native": "^1.2.0",
28
+ "jose": "^6.2.0",
28
29
  "uuid": "^13.0.0",
29
30
  "xml2js": "^0.6.2"
30
31
  },
package/utils/license.mjs CHANGED
@@ -1,27 +1,127 @@
1
1
  // utils/license.mjs
2
- // CE stub — full license validation coming in a future release.
2
+ // JWT license verification for NSAuditor AI.
3
+ // Uses ES256 (ECDSA P-256) public key embedded below — no file I/O needed.
4
+ //
5
+ // KEY ROTATION: If the private key is compromised, generate a new EC P-256 key
6
+ // pair, update PUBLIC_KEY_PEM below, and ship a CE update. All existing JWTs
7
+ // become invalid. See license-manager docs/architecture.md for full procedure.
8
+
9
+ import { jwtVerify, importSPKI } from 'jose';
10
+
11
+ // ES256 public key — embedded directly so it works in npm package (no file read).
12
+ // Corresponding private key is in the license-manager service (NEVER shipped here).
13
+ const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
14
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDMDuTDV5dPqNafE473AIlCCdbLX7
15
+ u8cSY2dN6mfevYnOydP0SXLHCfWHr+SlpZpA2BiU6GKEk+QdIlWXOgGZsA==
16
+ -----END PUBLIC KEY-----`;
17
+
18
+ // Set by loadLicense(), read by getTierFromEnv().
19
+ // Starts null — before loadLicense() runs, getTierFromEnv() returns 'ce' (safe default).
20
+ // CE (Community Edition) is free and always works without a license.
21
+ let _verifiedTier = null;
3
22
 
4
23
  /**
5
- * Parse tier from NSAUDITOR_LICENSE_KEY environment variable.
6
- * Stub uses key prefix: pro_* → 'pro', enterprise_* 'enterprise'.
24
+ * Synchronous tier detection.
25
+ * Returns 'pro' | 'enterprise' | 'ce'.
26
+ *
27
+ * Before loadLicense() runs: returns 'ce' (Community Edition — safe default).
28
+ * After loadLicense() runs: returns the cryptographically verified tier.
29
+ *
30
+ * CE is the free default — licensed features only activate after loadLicense()
31
+ * confirms the JWT signature. This prevents prefix spoofing from granting
32
+ * elevated privileges during the startup window.
33
+ *
34
+ * MUST remain synchronous — called in hot paths (cli.mjs, plugin_manager, mcp_server).
7
35
  */
8
36
  export function getTierFromEnv() {
9
- const key = process.env.NSAUDITOR_LICENSE_KEY;
10
- if (!key) return 'ce';
11
- if (key.startsWith('pro_')) return 'pro';
12
- if (key.startsWith('enterprise_')) return 'enterprise';
37
+ if (_verifiedTier !== null) return _verifiedTier;
38
+
39
+ // Not yet verified — CE is the safe default.
40
+ // Call loadLicense() at startup to enable Pro/Enterprise.
13
41
  return 'ce';
14
42
  }
15
43
 
16
44
  /**
17
- * Validate a license key string.
18
- * Gracefully degrades to CE on any failure never throws.
45
+ * Full async JWT verification. Call once at startup.
46
+ * On success, caches verified tier so subsequent getTierFromEnv() calls
47
+ * return the cryptographically validated result.
48
+ *
49
+ * Never throws — degrades to 'ce' on any failure.
19
50
  *
20
- * @param {string|undefined} keyStr
21
- * @returns {Promise<{valid: boolean, tier: string, reason: string}>}
51
+ * @param {string} [keyStr] - License key; defaults to NSAUDITOR_LICENSE_KEY env var.
52
+ * @returns {Promise<{valid: boolean, tier: string, org?: string, seats?: number,
53
+ * licenseId?: string, capabilities?: string[], expiresAt?: string, reason?: string}>}
22
54
  */
23
55
  export async function loadLicense(keyStr) {
24
- if (!keyStr) return { valid: false, tier: 'ce', reason: 'no key provided' };
25
- // TODO: implement full license validation
26
- return { valid: false, tier: 'ce', reason: 'license validation not yet implemented' };
56
+ const raw = keyStr ?? process.env.NSAUDITOR_LICENSE_KEY;
57
+ if (!raw) return { valid: false, tier: 'ce', reason: 'no key provided' };
58
+
59
+ // Strip tier prefix
60
+ let token = raw;
61
+ let prefixTier = null;
62
+ if (raw.startsWith('pro_')) { token = raw.slice(4); prefixTier = 'pro'; }
63
+ else if (raw.startsWith('enterprise_')) { token = raw.slice(11); prefixTier = 'enterprise'; }
64
+ else return { valid: false, tier: 'ce', reason: 'unknown key format' };
65
+
66
+ try {
67
+ const publicKey = await importSPKI(PUBLIC_KEY_PEM, 'ES256');
68
+ const { payload } = await jwtVerify(token, publicKey, {
69
+ issuer: 'nsasoft',
70
+ audience: 'nsauditor-ai',
71
+ subject: 'license',
72
+ algorithms: ['ES256'],
73
+ clockTolerance: 120,
74
+ });
75
+
76
+ // Cross-check: prefix must match JWT tier claim
77
+ if (payload.tier !== prefixTier) {
78
+ return { valid: false, tier: 'ce', reason: 'tier mismatch' };
79
+ }
80
+
81
+ // Cache verified tier for synchronous access
82
+ _verifiedTier = payload.tier;
83
+
84
+ // Compute days until expiry for renewal warnings (air-gapped VPC support)
85
+ const expiresAt = new Date(payload.exp * 1000);
86
+ const daysUntilExpiry = Math.max(0, Math.floor((expiresAt - Date.now()) / 86_400_000));
87
+
88
+ let expiryWarning = null;
89
+ if (daysUntilExpiry <= 1) {
90
+ expiryWarning = 'License expires tomorrow — update NSAUDITOR_LICENSE_KEY now';
91
+ } else if (daysUntilExpiry <= 7) {
92
+ expiryWarning = `License expires in ${daysUntilExpiry} days — check email for renewal key`;
93
+ }
94
+
95
+ if (expiryWarning) {
96
+ console.warn(`\u26A0 ${expiryWarning}`);
97
+ }
98
+
99
+ return {
100
+ valid: true,
101
+ tier: payload.tier,
102
+ org: payload.org,
103
+ seats: payload.seats,
104
+ licenseId: payload.licenseId,
105
+ capabilities: payload.capabilities,
106
+ expiresAt: expiresAt.toISOString(),
107
+ daysUntilExpiry,
108
+ expiryWarning,
109
+ };
110
+ } catch {
111
+ // Verification failure — actively downgrade to CE (prevents prefix spoofing).
112
+ // Generic reason to avoid leaking jose internals to end users.
113
+ _verifiedTier = 'ce';
114
+ return { valid: false, tier: 'ce', reason: 'invalid license key' };
115
+ }
116
+ }
117
+
118
+ /**
119
+ * @internal Test-only. Reset cached verified tier between tests.
120
+ * Disabled in production to prevent accidental tier cache clearing.
121
+ */
122
+ export function _resetCache() {
123
+ if (process.env.NODE_ENV === 'production') {
124
+ throw new Error('_resetCache is test-only and disabled in production');
125
+ }
126
+ _verifiedTier = null;
27
127
  }