nsauditor-ai 0.1.10 → 0.1.11
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/cli.mjs +21 -9
- package/mcp_server.mjs +9 -2
- package/package.json +2 -1
- package/utils/license.mjs +97 -14
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.11",
|
|
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,110 @@
|
|
|
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.
|
|
19
48
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
49
|
+
* Never throws — degrades to 'ce' on any failure.
|
|
50
|
+
*
|
|
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
|
+
return {
|
|
85
|
+
valid: true,
|
|
86
|
+
tier: payload.tier,
|
|
87
|
+
org: payload.org,
|
|
88
|
+
seats: payload.seats,
|
|
89
|
+
licenseId: payload.licenseId,
|
|
90
|
+
capabilities: payload.capabilities,
|
|
91
|
+
expiresAt: new Date(payload.exp * 1000).toISOString(),
|
|
92
|
+
};
|
|
93
|
+
} catch {
|
|
94
|
+
// Verification failure — actively downgrade to CE (prevents prefix spoofing).
|
|
95
|
+
// Generic reason to avoid leaking jose internals to end users.
|
|
96
|
+
_verifiedTier = 'ce';
|
|
97
|
+
return { valid: false, tier: 'ce', reason: 'invalid license key' };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @internal Test-only. Reset cached verified tier between tests.
|
|
103
|
+
* Disabled in production to prevent accidental tier cache clearing.
|
|
104
|
+
*/
|
|
105
|
+
export function _resetCache() {
|
|
106
|
+
if (process.env.NODE_ENV === 'production') {
|
|
107
|
+
throw new Error('_resetCache is test-only and disabled in production');
|
|
108
|
+
}
|
|
109
|
+
_verifiedTier = null;
|
|
27
110
|
}
|