From 7f903242390f9dab6d5e3a1b6a88af74b35069b3 Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Thu, 21 May 2026 12:44:28 -0300 Subject: [PATCH 1/2] fix: align pro install machine fingerprint --- package-lock.json | 3 +- packages/installer/package.json | 3 +- packages/installer/src/wizard/pro-setup.js | 31 +++++++++++++++++++ .../tests/unit/pro-setup-machine-id.test.js | 16 ++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 packages/installer/tests/unit/pro-setup-machine-id.test.js diff --git a/package-lock.json b/package-lock.json index f5be15626c..1281b8f837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14518,7 +14518,7 @@ }, "packages/installer": { "name": "@aiox-squads/installer", - "version": "3.3.8", + "version": "3.3.9", "license": "MIT", "dependencies": { "@aiox-squads/core": "^5.1.0", @@ -14528,6 +14528,7 @@ "fs-extra": "^11.3.2", "inquirer": "^8.2.6", "js-yaml": "^4.1.0", + "node-machine-id": "^1.1.12", "ora": "^5.4.1", "semver": "^7.7.2" }, diff --git a/packages/installer/package.json b/packages/installer/package.json index 908fe481b0..846a07511a 100644 --- a/packages/installer/package.json +++ b/packages/installer/package.json @@ -1,6 +1,6 @@ { "name": "@aiox-squads/installer", - "version": "3.3.8", + "version": "3.3.9", "description": "AIOX Installer - Automated setup wizard for AIOX projects (greenfield & brownfield)", "main": "src/index.js", "bin": { @@ -42,6 +42,7 @@ "fs-extra": "^11.3.2", "inquirer": "^8.2.6", "js-yaml": "^4.1.0", + "node-machine-id": "^1.1.12", "ora": "^5.4.1", "semver": "^7.7.2" }, diff --git a/packages/installer/src/wizard/pro-setup.js b/packages/installer/src/wizard/pro-setup.js index a3e3bf21ce..bf64b52cc8 100644 --- a/packages/installer/src/wizard/pro-setup.js +++ b/packages/installer/src/wizard/pro-setup.js @@ -137,6 +137,7 @@ function resolveLicenseServerUrl(rawUrl = DEFAULT_LICENSE_SERVER_URL) { const LICENSE_SERVER_URL = resolveLicenseServerUrl(process.env.AIOX_LICENSE_API_URL); const PASSWORD_RESET_URL = new URL('/reset-password', LICENSE_SERVER_URL).toString(); +const MACHINE_ID_HASH_PREFIX = 'aiox-pro-native-machine-id:v1:'; /** * Inline License Client — lightweight HTTP client for pre-bootstrap license checks. @@ -612,6 +613,34 @@ function generateMachineId() { return licenseCryptoModule.generateMachineId(); } + const nativeMachineId = generateNativeMachineId(); + if (nativeMachineId) { + return nativeMachineId; + } + + return generateLegacyMachineId(); +} + +function generateNativeMachineId() { + const crypto = require('crypto'); + try { + const { machineIdSync } = require('node-machine-id'); + const nativeMachineId = machineIdSync(true); + + if (!nativeMachineId || typeof nativeMachineId !== 'string') { + throw new Error('Native machine id unavailable'); + } + + return crypto + .createHash('sha256') + .update(`${MACHINE_ID_HASH_PREFIX}${nativeMachineId}`) + .digest('hex'); + } catch { + return null; + } +} + +function generateLegacyMachineId() { const crypto = require('crypto'); const os = require('os'); const components = []; @@ -2322,6 +2351,8 @@ module.exports = { resolveProSourceDir, InlineLicenseClient, generateMachineId, + generateNativeMachineId, + generateLegacyMachineId, persistLicenseCache, resolveLicenseServerUrl, resolveNpmInvocation, diff --git a/packages/installer/tests/unit/pro-setup-machine-id.test.js b/packages/installer/tests/unit/pro-setup-machine-id.test.js new file mode 100644 index 0000000000..73d0044ec3 --- /dev/null +++ b/packages/installer/tests/unit/pro-setup-machine-id.test.js @@ -0,0 +1,16 @@ +const crypto = require('crypto'); +const { machineIdSync } = require('node-machine-id'); + +const { _testing } = require('../../src/wizard/pro-setup'); + +describe('pro setup machine id', () => { + test('uses the same native machine id fingerprint as the Pro runtime', () => { + const nativeMachineId = machineIdSync(true); + const expected = crypto + .createHash('sha256') + .update(`aiox-pro-native-machine-id:v1:${nativeMachineId}`) + .digest('hex'); + + expect(_testing.generateMachineId()).toBe(expected); + }); +}); From d8302113a1e13e8f0d11f955519ea3a1ca53485c Mon Sep 17 00:00:00 2001 From: rafaelscosta Date: Thu, 21 May 2026 12:54:10 -0300 Subject: [PATCH 2/2] test: cover pro install machine id fallback --- .../tests/unit/pro-setup-machine-id.test.js | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/installer/tests/unit/pro-setup-machine-id.test.js b/packages/installer/tests/unit/pro-setup-machine-id.test.js index 73d0044ec3..f1c4e161a4 100644 --- a/packages/installer/tests/unit/pro-setup-machine-id.test.js +++ b/packages/installer/tests/unit/pro-setup-machine-id.test.js @@ -1,9 +1,14 @@ const crypto = require('crypto'); const { machineIdSync } = require('node-machine-id'); -const { _testing } = require('../../src/wizard/pro-setup'); +const { _testing } = require('@aiox-squads/installer/pro-setup'); describe('pro setup machine id', () => { + afterEach(() => { + jest.dontMock('node-machine-id'); + jest.dontMock('os'); + }); + test('uses the same native machine id fingerprint as the Pro runtime', () => { const nativeMachineId = machineIdSync(true); const expected = crypto @@ -13,4 +18,29 @@ describe('pro setup machine id', () => { expect(_testing.generateMachineId()).toBe(expected); }); + + test('falls back to the legacy fingerprint when native machine id is unavailable', () => { + jest.doMock('node-machine-id', () => ({ + machineIdSync: jest.fn(() => { + throw new Error('Native machine id unavailable'); + }), + })); + jest.doMock('os', () => ({ + hostname: jest.fn(() => 'fallback-host'), + cpus: jest.fn(() => [{ model: 'fallback-cpu' }]), + networkInterfaces: jest.fn(() => ({ + en0: [{ internal: false, mac: 'aa:bb:cc:dd:ee:ff' }], + })), + })); + + jest.isolateModules(() => { + const { _testing: isolatedTesting } = require('@aiox-squads/installer/pro-setup'); + const expected = crypto + .createHash('sha256') + .update('fallback-host|fallback-cpu|aa:bb:cc:dd:ee:ff') + .digest('hex'); + + expect(isolatedTesting.generateMachineId()).toBe(expected); + }); + }); });