node-command 2.0.9 โ 2.0.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/bin/nc.mjs +171 -11
- package/lib/setup.mjs +10 -1
- package/package.json +4 -6
package/bin/nc.mjs
CHANGED
|
@@ -1529,6 +1529,75 @@ export async function verifyHostRegistration({ baseUrl: baseUrlOpt = undefined,
|
|
|
1529
1529
|
}
|
|
1530
1530
|
}
|
|
1531
1531
|
|
|
1532
|
+
/**
|
|
1533
|
+
* Fetches the device-authenticated install context for the protected helper.
|
|
1534
|
+
* The control plane supplies only client/tenant/control-plane identity; the
|
|
1535
|
+
* trust anchor (pin + digest) is the OTA-signed runtime release. The response
|
|
1536
|
+
* must be exactly {tenant_id, control_plane_id}: any pin or extra field is
|
|
1537
|
+
* rejected. A missing/incomplete response is a non-fatal warning: ordinary
|
|
1538
|
+
* enrollment continues without the helper.
|
|
1539
|
+
*/
|
|
1540
|
+
async function fetchPrivilegedInstallContext({ baseUrl, hostId, hostToken, privateKey }) {
|
|
1541
|
+
const contextUrl = `${baseUrl}/api/clients/${encodeURIComponent(hostId)}/privileged-helper/context`;
|
|
1542
|
+
try {
|
|
1543
|
+
assertCredentialOrigin(contextUrl, { label: 'helper context URL', action: 'fetch' });
|
|
1544
|
+
const data = await fetchJson(contextUrl, buildDeviceDownloadHeaders({ hostId, hostToken, privateKey }, contextUrl));
|
|
1545
|
+
const fields = ['tenant_id', 'control_plane_id'];
|
|
1546
|
+
const tenantId = typeof data?.tenant_id === 'string' ? data.tenant_id.trim() : '';
|
|
1547
|
+
const controlPlaneId = typeof data?.control_plane_id === 'string' ? data.control_plane_id.trim() : '';
|
|
1548
|
+
if (!data || typeof data !== 'object' || Array.isArray(data) ||
|
|
1549
|
+
Object.keys(data).length !== fields.length || !fields.every((field) => Object.hasOwn(data, field)) ||
|
|
1550
|
+
!tenantId || !controlPlaneId || tenantId.length > 256 || controlPlaneId.length > 256) {
|
|
1551
|
+
console.warn('Warning: the control plane did not return the identity-only helper install context (tenant id, control-plane id) and no pin is accepted from device auth. Continuing ordinary enrollment without the privileged helper; re-run setup after the control plane advertises the helper context endpoint.');
|
|
1552
|
+
return null;
|
|
1553
|
+
}
|
|
1554
|
+
return { client_id: hostId, tenant_id: tenantId, control_plane_id: controlPlaneId };
|
|
1555
|
+
} catch (err) {
|
|
1556
|
+
const detail = sanitizeForDisplay(err instanceof Error ? err.message : String(err), 200);
|
|
1557
|
+
console.warn(`Warning: could not obtain the helper install context (${detail}). Continuing ordinary enrollment without the privileged helper; re-run setup with root consent after the control plane adds the /privileged-helper/context endpoint.`);
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* Publishes the locally generated helper public key as a pending candidate.
|
|
1564
|
+
* Only the public key is sent; a failure never rolls back the installed agent.
|
|
1565
|
+
*/
|
|
1566
|
+
async function publishPrivilegedHelperCandidate({ root, baseUrl, hostId, hostToken, privateKey }) {
|
|
1567
|
+
const publicKeyPath = path.join(root, 'libexec', 'privileged-helper', 'privileged-helper-public.pem');
|
|
1568
|
+
let pem;
|
|
1569
|
+
try {
|
|
1570
|
+
const info = fs.lstatSync(publicKeyPath);
|
|
1571
|
+
if (!info.isFile() || info.isSymbolicLink()) throw new Error('not a regular file');
|
|
1572
|
+
pem = fs.readFileSync(publicKeyPath, 'utf8');
|
|
1573
|
+
} catch {
|
|
1574
|
+
return { published: false, warning: 'Admin remains pending; candidate publication failed: protected helper public key not found.' };
|
|
1575
|
+
}
|
|
1576
|
+
try {
|
|
1577
|
+
const publicKey = crypto.createPublicKey(pem);
|
|
1578
|
+
if (publicKey.asymmetricKeyType !== 'ed25519') throw new Error('helper public key is not Ed25519');
|
|
1579
|
+
const candidateUrl = `${baseUrl}/api/clients/${encodeURIComponent(hostId)}/privileged-helper/candidate`;
|
|
1580
|
+
assertCredentialOrigin(candidateUrl, { label: 'helper candidate URL', action: 'publish' });
|
|
1581
|
+
const proof = createDeviceProofCli({ privateKey, hostId, method: 'POST', url: candidateUrl, credential: hostToken });
|
|
1582
|
+
const res = await postJson(
|
|
1583
|
+
candidateUrl,
|
|
1584
|
+
{ public_key: pem },
|
|
1585
|
+
{ Authorization: `NC-Device ${hostToken}`, 'NC-Device-Proof': proof }
|
|
1586
|
+
);
|
|
1587
|
+
if (res.status < 200 || res.status >= 300) {
|
|
1588
|
+
throw new Error(sanitizeForDisplay(res.data?.error || `HTTP ${res.status}`, 120));
|
|
1589
|
+
}
|
|
1590
|
+
const keyId = crypto.createHash('sha256').update(publicKey.export({ type: 'spki', format: 'der' })).digest('hex');
|
|
1591
|
+
return { published: true, fingerprint: `sha256:${keyId}` };
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
const detail = sanitizeForDisplay(err instanceof Error ? err.message : String(err), 200);
|
|
1594
|
+
return {
|
|
1595
|
+
published: false,
|
|
1596
|
+
warning: `Admin remains pending; candidate publication failed: ${detail}. The agent is installed without the Admin helper; approve the helper candidate from the dashboard once the control plane accepts it.`
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1532
1601
|
/**
|
|
1533
1602
|
* Downloads, verifies, extracts, and installs the agent package. Writes NO
|
|
1534
1603
|
* credentials โ the env file must already exist before this runs.
|
|
@@ -1554,13 +1623,15 @@ export async function verifyHostRegistration({ baseUrl: baseUrlOpt = undefined,
|
|
|
1554
1623
|
* (`/releases/client/<version>/bootstrap.tar.gz`) stays anonymous. The
|
|
1555
1624
|
* proof binds method GET + the full request URL + the redemption-issued
|
|
1556
1625
|
* client token, exactly like redeemEnrollment.
|
|
1557
|
-
* @param {{ ncRoot?: string, updatesUrl?: string, serverUrl?: string, release?: Record<string, unknown>|null, installMode?: 'system'|'user', rootConsent?: boolean, runInstaller?: (scriptPath: string, ncRoot: string) => void, hostId?: string, hostToken?: string, privateKey?: import('node:crypto').KeyObject, downloadTimeoutMs?: number }} options
|
|
1626
|
+
* @param {{ ncRoot?: string, updatesUrl?: string, serverUrl?: string, release?: Record<string, unknown>|null, installMode?: 'system'|'user', rootConsent?: boolean, runInstaller?: (scriptPath: string, ncRoot: string, env?: Record<string, string>) => void, hostId?: string, hostToken?: string, privateKey?: import('node:crypto').KeyObject, downloadTimeoutMs?: number }} options
|
|
1627
|
+
* @returns {Promise<{ helperCandidate: { published: boolean, fingerprint?: string, warning?: string }|null }>} `helperCandidate` is null when no helper was requested.
|
|
1558
1628
|
*/
|
|
1559
1629
|
export async function downloadAndInstallAgent({ ncRoot = undefined, updatesUrl = undefined, serverUrl = undefined, release = undefined, installMode = undefined, rootConsent = false, runInstaller = undefined, hostId = undefined, hostToken = undefined, privateKey = undefined, downloadTimeoutMs = undefined } = {}) {
|
|
1560
1630
|
ncRoot = ncRoot || (installMode ? getNcRootForMode(installMode) : getNcRoot());
|
|
1561
1631
|
const modeEnv = { ...(installMode ? installerEnvForMode(installMode) : {}), NC_ROOT_COMMAND_CONSENT: rootConsent === true ? 'yes' : 'no' };
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1632
|
+
const helperRequested = rootConsent === true && installMode !== 'user' && os.platform() === 'linux';
|
|
1633
|
+
const invokeInstaller = (scriptPath, installRoot) =>
|
|
1634
|
+
runInstaller ? runInstaller(scriptPath, installRoot, modeEnv) : runInstallerScript(scriptPath, installRoot, modeEnv);
|
|
1564
1635
|
const root = ncRoot;
|
|
1565
1636
|
console.log(`๐ Installation root: ${root}`);
|
|
1566
1637
|
|
|
@@ -1771,6 +1842,68 @@ export async function downloadAndInstallAgent({ ncRoot = undefined, updatesUrl =
|
|
|
1771
1842
|
throw new PairingError('INSTALL_SCRIPT_MISSING', `Installation script not found: ${scriptPath}.`);
|
|
1772
1843
|
}
|
|
1773
1844
|
|
|
1845
|
+
if (helperRequested) {
|
|
1846
|
+
const runtimePackageUrl = (() => {
|
|
1847
|
+
const raw = usableUrl(activeRelease.package_url) || usableUrl(activeRelease.download_url);
|
|
1848
|
+
if (!raw) return null;
|
|
1849
|
+
try {
|
|
1850
|
+
return new URL(raw, defaultBaseUrl).toString();
|
|
1851
|
+
} catch {
|
|
1852
|
+
return null;
|
|
1853
|
+
}
|
|
1854
|
+
})();
|
|
1855
|
+
const runtimeSha = typeof activeRelease.sha256 === 'string' ? activeRelease.sha256.toLowerCase() : '';
|
|
1856
|
+
const runtimeSize = activeRelease.size_bytes;
|
|
1857
|
+
const runtimeSignature = typeof activeRelease.signature === 'string' ? activeRelease.signature.trim() : '';
|
|
1858
|
+
const runtimeVersion = typeof version === 'string' ? version : '';
|
|
1859
|
+
const helperPrereqsReady =
|
|
1860
|
+
hasDeviceIdentity &&
|
|
1861
|
+
runtimePackageUrl &&
|
|
1862
|
+
/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/.test(runtimeVersion) &&
|
|
1863
|
+
/^[a-f0-9]{64}$/.test(runtimeSha) &&
|
|
1864
|
+
Number.isSafeInteger(runtimeSize) && runtimeSize > 0 &&
|
|
1865
|
+
/^[A-Za-z0-9_-]{86}$/.test(runtimeSignature);
|
|
1866
|
+
if (!helperPrereqsReady) {
|
|
1867
|
+
console.warn('Warning: the release does not carry a complete signed runtime package (version, package URL, sha256, size, signature) or this setup has no device identity. Continuing ordinary User enrollment without the privileged helper.');
|
|
1868
|
+
} else {
|
|
1869
|
+
const context = await fetchPrivilegedInstallContext({ baseUrl: defaultBaseUrl, hostId, hostToken, privateKey });
|
|
1870
|
+
if (context) {
|
|
1871
|
+
// Fetch the runtime archive with the device credential here; the root
|
|
1872
|
+
// installer must never need a client token and cannot fetch the
|
|
1873
|
+
// authenticated package endpoint itself.
|
|
1874
|
+
const runtimeArchivePath = path.join(tempDir, 'client.tar.gz');
|
|
1875
|
+
try {
|
|
1876
|
+
assertCredentialOrigin(runtimePackageUrl, { label: 'runtime release URL', action: 'fetch' });
|
|
1877
|
+
if (!isSameOrigin(runtimePackageUrl, defaultBaseUrl)) {
|
|
1878
|
+
throw new Error('runtime release URL is not on the trusted enrollment origin');
|
|
1879
|
+
}
|
|
1880
|
+
const download = await downloadToFile(
|
|
1881
|
+
runtimePackageUrl,
|
|
1882
|
+
runtimeArchivePath,
|
|
1883
|
+
buildDeviceDownloadHeaders({ hostId, hostToken, privateKey }, runtimePackageUrl),
|
|
1884
|
+
{ maxBytes: getCliMaxPackageBytes(), timeoutMs: downloadTimeoutMs ?? getCliDownloadTimeoutMs() }
|
|
1885
|
+
);
|
|
1886
|
+
if (download.bytes !== runtimeSize || download.sha256.toLowerCase() !== runtimeSha) {
|
|
1887
|
+
throw new Error('runtime release archive does not match the signed digest/size');
|
|
1888
|
+
}
|
|
1889
|
+
modeEnv.NC_PRIVILEGED_INSTALL_CONTEXT = JSON.stringify(context);
|
|
1890
|
+
modeEnv.NC_PRIVILEGED_ARCHIVE_FILE = runtimeArchivePath;
|
|
1891
|
+
modeEnv.NC_PRIVILEGED_RELEASE_VERSION = runtimeVersion;
|
|
1892
|
+
modeEnv.NC_PRIVILEGED_RELEASE_URL = runtimePackageUrl;
|
|
1893
|
+
modeEnv.NC_PRIVILEGED_RELEASE_SHA256 = runtimeSha;
|
|
1894
|
+
modeEnv.NC_PRIVILEGED_RELEASE_SIZE = String(runtimeSize);
|
|
1895
|
+
modeEnv.NC_PRIVILEGED_RELEASE_SIGNATURE = runtimeSignature;
|
|
1896
|
+
// The installer compares the archive URL origin with the enrollment
|
|
1897
|
+
// server origin. modeEnv values win over inherited NC_* variables.
|
|
1898
|
+
modeEnv.NC_SERVER_URL = defaultBaseUrl;
|
|
1899
|
+
} catch (err) {
|
|
1900
|
+
const detail = sanitizeForDisplay(err instanceof Error ? err.message : String(err), 200);
|
|
1901
|
+
console.warn(`Warning: could not prepare the signed runtime release for the privileged helper (${detail}). Continuing ordinary User enrollment without the privileged helper.`);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1774
1907
|
try {
|
|
1775
1908
|
invokeInstaller(scriptPath, root);
|
|
1776
1909
|
} catch (err) {
|
|
@@ -1785,6 +1918,21 @@ export async function downloadAndInstallAgent({ ncRoot = undefined, updatesUrl =
|
|
|
1785
1918
|
console.log('๐งน Cleaning up temporary directory...');
|
|
1786
1919
|
cleanup();
|
|
1787
1920
|
}
|
|
1921
|
+
|
|
1922
|
+
let helperCandidate = null;
|
|
1923
|
+
if (helperRequested && hasDeviceIdentity) {
|
|
1924
|
+
helperCandidate = await publishPrivilegedHelperCandidate({
|
|
1925
|
+
root,
|
|
1926
|
+
baseUrl: defaultBaseUrl,
|
|
1927
|
+
hostId,
|
|
1928
|
+
hostToken,
|
|
1929
|
+
privateKey
|
|
1930
|
+
});
|
|
1931
|
+
if (helperCandidate.published === false && helperCandidate.warning) {
|
|
1932
|
+
console.warn(`Warning: ${helperCandidate.warning}`);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return { helperCandidate };
|
|
1788
1936
|
}
|
|
1789
1937
|
|
|
1790
1938
|
/**
|
|
@@ -1839,7 +1987,7 @@ export async function pairDeviceAndInstall({
|
|
|
1839
1987
|
const installAgent =
|
|
1840
1988
|
hooks.installAgent ||
|
|
1841
1989
|
(async ({ ncRoot: installRoot, serverUrl: installServerUrl, release: installRelease = undefined, hostId: installHostId = undefined, hostToken: installHostToken = undefined, privateKey: installPrivateKey = undefined }) => {
|
|
1842
|
-
await downloadAndInstallAgent({ ncRoot: installRoot, serverUrl: installServerUrl, installMode, rootConsent: selectedRootConsent, release: installRelease, hostId: installHostId, hostToken: installHostToken, privateKey: installPrivateKey });
|
|
1990
|
+
return await downloadAndInstallAgent({ ncRoot: installRoot, serverUrl: installServerUrl, installMode, rootConsent: selectedRootConsent, release: installRelease, hostId: installHostId, hostToken: installHostToken, privateKey: installPrivateKey });
|
|
1843
1991
|
});
|
|
1844
1992
|
let selectedRootConsent = false;
|
|
1845
1993
|
|
|
@@ -1876,7 +2024,7 @@ export async function pairDeviceAndInstall({
|
|
|
1876
2024
|
return { hostId: localEnv.NC_CLIENT_ID, serverUrl: localEnv.NC_SERVER_URL, resumed: true };
|
|
1877
2025
|
}
|
|
1878
2026
|
console.log(`๐ Resuming installation for host ${localEnv.NC_CLIENT_ID} (stored credentials verified)...`);
|
|
1879
|
-
await installAgent({ ncRoot: root, serverUrl: storedServerUrl, hostId: localEnv.NC_CLIENT_ID, hostToken: localEnv.NC_CLIENT_TOKEN, privateKey: localKey.privateKey });
|
|
2027
|
+
const installOutcome = await installAgent({ ncRoot: root, serverUrl: storedServerUrl, hostId: localEnv.NC_CLIENT_ID, hostToken: localEnv.NC_CLIENT_TOKEN, privateKey: localKey.privateKey });
|
|
1880
2028
|
writePairState(statePath, { host_id: localEnv.NC_CLIENT_ID, phase: 'installed' });
|
|
1881
2029
|
await verifyHostRegistration({
|
|
1882
2030
|
apiBase: localEnv.NC_SERVER_URL,
|
|
@@ -1887,7 +2035,8 @@ export async function pairDeviceAndInstall({
|
|
|
1887
2035
|
intervalMs: verifyIntervalMs
|
|
1888
2036
|
});
|
|
1889
2037
|
writePairState(statePath, { host_id: localEnv.NC_CLIENT_ID, phase: 'verified' });
|
|
1890
|
-
return { hostId: localEnv.NC_CLIENT_ID, serverUrl: localEnv.NC_SERVER_URL, resumed: true
|
|
2038
|
+
return { hostId: localEnv.NC_CLIENT_ID, serverUrl: localEnv.NC_SERVER_URL, resumed: true,
|
|
2039
|
+
helperCandidate: installOutcome?.helperCandidate ?? null };
|
|
1891
2040
|
} catch (err) {
|
|
1892
2041
|
// Failure at the bound destination is not permission to enroll on
|
|
1893
2042
|
// another server or replace this installed host identity.
|
|
@@ -1956,7 +2105,7 @@ export async function pairDeviceAndInstall({
|
|
|
1956
2105
|
writePairState(statePath, { host_id: hostId, phase: 'redeemed' });
|
|
1957
2106
|
|
|
1958
2107
|
console.log('๐ Installing agent...');
|
|
1959
|
-
await installAgent({ ncRoot: root, serverUrl: enrollServerUrl, release: redemptionRelease, hostId, hostToken, privateKey: keyPair.privateKey, rootConsent: selectedRootConsent });
|
|
2108
|
+
const installOutcome = await installAgent({ ncRoot: root, serverUrl: enrollServerUrl, release: redemptionRelease, hostId, hostToken, privateKey: keyPair.privateKey, rootConsent: selectedRootConsent });
|
|
1960
2109
|
writePairState(statePath, { host_id: hostId, phase: 'installed' });
|
|
1961
2110
|
|
|
1962
2111
|
console.log(`๐ Verifying host registration at ${sanitizeForDisplay(enrollServerUrl, 256)}...`);
|
|
@@ -1973,7 +2122,8 @@ export async function pairDeviceAndInstall({
|
|
|
1973
2122
|
});
|
|
1974
2123
|
writePairState(statePath, { host_id: hostId, phase: 'verified' });
|
|
1975
2124
|
return { hostId, serverUrl: enrollServerUrl, resumed: false, version: agentVersion, rootConsent: selectedRootConsent,
|
|
1976
|
-
privilegeReadiness: selectedRootConsent ? 'pending' : 'unavailable', executionInitialization
|
|
2125
|
+
privilegeReadiness: selectedRootConsent ? 'pending' : 'unavailable', executionInitialization,
|
|
2126
|
+
helperCandidate: installOutcome?.helperCandidate ?? null };
|
|
1977
2127
|
}
|
|
1978
2128
|
|
|
1979
2129
|
/**
|
|
@@ -2235,9 +2385,9 @@ function getCliVersion() {
|
|
|
2235
2385
|
/**
|
|
2236
2386
|
* Prints the one-line install summary required after a successful setup:
|
|
2237
2387
|
* host id, install mode, CLI version, and verification status.
|
|
2238
|
-
* @param {{ hostId: string, serverUrl: string, installMode?: 'system'|'user', resumed?: boolean, version?: string|null, rootConsent?: boolean, ncRoot?: string, executionInitialization?: any }} options
|
|
2388
|
+
* @param {{ hostId: string, serverUrl: string, installMode?: 'system'|'user', resumed?: boolean, version?: string|null, rootConsent?: boolean, ncRoot?: string, executionInitialization?: any, helperCandidate?: { published: boolean, fingerprint?: string, warning?: string }|null }} options
|
|
2239
2389
|
*/
|
|
2240
|
-
export function printSetupSuccess({ hostId, serverUrl, installMode = undefined, resumed = false, version = null, rootConsent = false, ncRoot = undefined, executionInitialization = null }) {
|
|
2390
|
+
export function printSetupSuccess({ hostId, serverUrl, installMode = undefined, resumed = false, version = null, rootConsent = false, ncRoot = undefined, executionInitialization = null, helperCandidate = null }) {
|
|
2241
2391
|
console.log(`\nโ
Setup complete for host ${sanitizeForDisplay(hostId, 128)}${resumed ? ' (resumed)' : ''}.`);
|
|
2242
2392
|
if (installMode) console.log(`installed as ${installMode}`);
|
|
2243
2393
|
if (version) console.log(`agent version: ${sanitizeForDisplay(version, 120)}`);
|
|
@@ -2251,7 +2401,17 @@ export function printSetupSuccess({ hostId, serverUrl, installMode = undefined,
|
|
|
2251
2401
|
console.log('execution permission: existing choice preserved; no privilege maintenance performed');
|
|
2252
2402
|
} else {
|
|
2253
2403
|
console.log(`root command consent: ${rootConsent === true ? 'Yes' : 'No'}`);
|
|
2254
|
-
|
|
2404
|
+
if (rootConsent !== true) {
|
|
2405
|
+
console.log('local privilege readiness: unavailable (not requested)');
|
|
2406
|
+
} else if (helperCandidate && helperCandidate.published === true) {
|
|
2407
|
+
const match = /^sha256:([0-9a-f]{64})$/i.exec(String(helperCandidate.fingerprint || ''));
|
|
2408
|
+
const shortFingerprint = match ? `sha256:${match[1].slice(0, 16).toLowerCase()}` : null;
|
|
2409
|
+
console.log(`local privilege readiness: Admin pending: approve the helper candidate in the dashboard${shortFingerprint ? ` (fingerprint: ${shortFingerprint})` : ''}`);
|
|
2410
|
+
} else if (helperCandidate && typeof helperCandidate.warning === 'string' && helperCandidate.warning) {
|
|
2411
|
+
console.log(`local privilege readiness: ${helperCandidate.warning}`);
|
|
2412
|
+
} else {
|
|
2413
|
+
console.log('local privilege readiness: pending independent management approval and protected helper binding');
|
|
2414
|
+
}
|
|
2255
2415
|
const receipt = executionInitialization;
|
|
2256
2416
|
const fields = ['outcome', 'reason', 'subject_user_id', 'permission'];
|
|
2257
2417
|
const reasons = { requested: ['admin_requested'], preserved: ['existing_permission_preserved', 'existing_host_preserved'],
|
package/lib/setup.mjs
CHANGED
|
@@ -296,7 +296,16 @@ function linuxPairedInstallation(io = fs) {
|
|
|
296
296
|
} finally { io.closeSync(fd); }
|
|
297
297
|
};
|
|
298
298
|
check(app, true);
|
|
299
|
-
|
|
299
|
+
const libexec = helperRoot + '/libexec';
|
|
300
|
+
const requiredLibexecEntries = ['nc-linux-peercred', 'privileged-helper'];
|
|
301
|
+
const managedSealedSnapshot = name => {
|
|
302
|
+
if (!/^privileged-helper-sealed-[0-9a-f]{64}$/.test(name)) return false;
|
|
303
|
+
const st = lstat(libexec + '/' + name);
|
|
304
|
+
return st.isDirectory() && !st.isSymbolicLink() && st.uid === 0 && st.gid === 0 && (st.mode & 0o7777) === 0o700;
|
|
305
|
+
};
|
|
306
|
+
const libexecEntries = io.readdirSync(libexec).sort();
|
|
307
|
+
if (!requiredLibexecEntries.every(name => libexecEntries.includes(name)) ||
|
|
308
|
+
!libexecEntries.every(name => requiredLibexecEntries.includes(name) || managedSealedSnapshot(name))) fail();
|
|
300
309
|
check(helperRoot + '/libexec/nc-linux-peercred');
|
|
301
310
|
const bootstrap = JSON.parse(read(app + '/helper-bootstrap.json'));
|
|
302
311
|
const selection = JSON.parse(read('/var/lib/nc-lifecycle/selected.json'));
|
package/package.json
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-command",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": ">=
|
|
7
|
+
"node": ">=24"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"nc": "bin/nc.mjs",
|
|
11
|
-
"nodecommand": "bin/nc.mjs"
|
|
12
|
-
"nc-agent": "bin/nc.mjs",
|
|
13
|
-
"node-command": "bin/nc.mjs"
|
|
10
|
+
"nc": "./bin/nc.mjs",
|
|
11
|
+
"nodecommand": "./bin/nc.mjs"
|
|
14
12
|
},
|
|
15
13
|
"files": [
|
|
16
14
|
"bin/",
|