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 +8 -10
- package/cli.mjs +21 -9
- package/mcp_server.mjs +9 -2
- package/package.json +2 -1
- package/utils/license.mjs +114 -14
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ A modular, AI-assisted network security audit platform that scans, understands,
|
|
|
7
7
|
[](https://www.npmjs.com/package/nsauditor-ai)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://nodejs.org)
|
|
10
|
-
[](#tests)
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -532,17 +532,13 @@ export default {
|
|
|
532
532
|
|
|
533
533
|
## Pro & Enterprise Activation
|
|
534
534
|
|
|
535
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
console.log(
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
*
|
|
18
|
-
*
|
|
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
|
|
21
|
-
* @returns {Promise<{valid: boolean, tier: 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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
}
|