Integrate reviewed private Unix lifecycle command repair

Merge the exact reviewed Web tip and retain its stronger retirement copy assertions where concurrent test-only maintenance overlaps. Runtime frontend content remains exactly browser-verified; existing credential issuance, identity and transport policy are preserved.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-01 15:24:52 +01:00
20 changed files with 1155 additions and 424 deletions
@@ -8344,20 +8344,37 @@ Focused proof lives in `internal/hostagent/agent_new_test.go`
`TestGetReliableMachineIDNormalizesWindowsBraces`); the Windows recovery file is
cross-compiled with `GOOS=windows go build ./internal/hostagent/`.
### Unix bootstrap survives single-line command fields
### Private Unix lifecycle bootstrap
The shared frontend Unix installer command uses explicit shell statement
separators rather than literal newlines or backslash-newline continuations.
This preserves its grammar when copied into a single-line command field (#2123).
The builder rejects embedded CR/LF in its normalized inputs rather than silently
altering a quoted URL, credential, CA path or option. Shell quoting is unchanged.
Download and unprivileged preflight still precede token-file creation and sudo
installation. Root and sudo paths retain private token directories, file modes,
TLS options, non-interactive operation and cleanup on success or failure.
`agentInstallCommand.test.ts` covers normalized-input syntax and
synthetic root/sudo execution under sh and Bash, including download, preflight
and install failures. A clipboard fixture proves input compatibility, not native
FreeBSD installation, offline dependency availability or service persistence.
The shared frontend Unix installer preserves single-line paste grammar,
quoted destinations, explicit custom CA/insecure continuity and profile flags.
Issued credentials are separate from copied commands. A privileged Bash child
owns the Core-aligned silent bounded reader and temporary 0700/0600 handoff;
maximum-size input must survive unchanged, overflow must not reach the caller's
history, and every exit must restore terminal modes and remove temporary files.
Download/preflight failures never reach credential entry or installation.
Sudo validation occurs before download; download and unprivileged preflight
still precede privileged installation. Token-optional and saved-state update
paths need no terminal or replacement credential.
For non-terminal FreeBSD command fields, the existing page offers an absolute
local private-token-file variant, with ownership/regular-file/symlink/mode
checks and the installer's bounded trusted reader. The operator provisions and
removes that file through a trusted file path; the command neither contains its
contents nor deletes it. A GUI without private file provisioning requires a
console/SSH session; native pfSense/FreeBSD installation is not inferred.
Unix upgrades, explicit credential repairs and uninstall now share this
complete-download transport rather than executing a partial fetch pipe or
putting tokens in argv. Repairs/removal retain canonical ID and hostname;
ordinary updates preserve their saved-state credential/identity route. Removal
skips new-binary preflight, which is not a detachment prerequisite. Issuance,
scopes, tenant/host binding, TLS and command-execution opt-in are unchanged.
Windows credential handling is deliberately unchanged pending its own complete
validated step. `agentInstallCommand.test.ts` is the executable helper proof;
`infrastructureAgentDoctorModel.test.ts` verifies the real operations closures
and existing credential/identity choices. Browser receipts cover real settings
components with synthetic APIs, not installed outcome or reporter acceptance.
### Agent Doctor handoff stays readable on a phone
@@ -11015,19 +11015,37 @@ status-only or unrelated test for the setup contract. The registry routes only `
setup proof policy; unrelated API runtime paths retain their existing verification
requirements. No runtime paths or required contract updates are exempted.
### Single-line Unix client install transport
### Single-line private Unix client install transport
`frontend-modern/src/utils/agentInstallCommand.ts` emits a single-line Unix
bootstrap with explicit shell separators so text-input paste normalization
cannot join shell statements (#2123). It retains the canonical URL, quoted
credential-to-private-file transport, custom CA/insecure continuity, and
preflight-before-install ordering; no API request, response or auth scope changes.
Normalized values containing CR/LF are rejected, not rewritten. Regression
coverage in `agentInstallCommand.test.ts` checks shell syntax after
text-input normalization, literal quoted token bytes, root/sudo private-file
permissions, cleanup and early exit when download or preflight fails. This
client transport repair does not establish the reporter's native pfSense abort
cause or successful offline installation.
`frontend-modern/src/utils/agentInstallCommand.ts` retains explicit single-line
shell grammar for paste hosts (#2123), canonical URLs, custom CA/insecure
continuity and complete-download-before-execution. Token values select private
entry only and never become shell source, argv or environment. The privileged
Bash child uses the bounded Core bootstrap reader; 4096-character input is
preserved, overflow is drained before returning to the caller, and terminal
modes and temporary 0700/0600 files are restored/removed on exit. Download and
unprivileged preflight precede credential entry and installation; sudo
validation precedes the download so authentication cannot consume token input.
A no-terminal command can use an absolute local `tokenFilePath`. It requires a
privileged-user-owned regular non-symlink 0600 file in an owned non-symlink 0700
parent, passes only its path, and leaves this operator-owned file untouched.
The existing installer's bounded trusted token reader still validates content
and ancestor trust. The FreeBSD card explains this existing private-file route
and does not put credentials in GUI command fields. It does not claim a GUI can
create private files or establish native appliance installation.
Unix install, credential replacement, upgrade and uninstall use the shared
transport. Explicit replacement/uninstall retain canonical agent ID and host;
ordinary updates recover their existing credential/identity from installer
state without a new prompt. Uninstall skips new-binary preflight so detachment
is not blocked by availability of an unrelated replacement binary. Server
minting, scope, TLS choice, execution opt-in and response shapes are unchanged.
Windows command transport is unchanged and remains separate unfinished work.
`agentInstallCommand.test.ts` exercises actual shells, PTYs, private-file
boundaries and failures; `infrastructureAgentDoctorModel.test.ts` exercises the
real lifecycle closures. These are modelled installer/privilege probes, not
native system installation or publication.
### Pulse Mobile pairing readiness wording
@@ -0,0 +1,257 @@
// UI proof only. No real token issuance, appliance, download or installation.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { chromium, webkit } = require('playwright');
(async () => {
const root = '/workspace/frontend-modern';
process.chdir(root);
console.log(
JSON.stringify({
fixtureHTML: fs.existsSync(path.join(root, 'browser-tests/unix-private-install.html')),
fixtureTSX: fs.existsSync(path.join(root, 'browser-tests/unix-private-install.tsx')),
}),
);
const artifacts = path.join(root, 'node_modules', 'unix-private-browser');
fs.mkdirSync(artifacts, { recursive: true });
const { createServer } = await import(path.join(root, 'node_modules/vite/dist/node/index.js'));
const server = await createServer({
root,
configFile: path.join(root, 'vite.config.ts'),
server: { host: '127.0.0.1', port: 5241, strictPort: true },
});
const results = [];
let browser;
const secret = 'browser-synthetic-install-token';
try {
await server.listen();
for (const [engine, width, tone] of [
['chromium', 1280, 'light'],
['webkit', 390, 'dark'],
]) {
browser = await { chromium, webkit }[engine].launch(
engine === 'chromium'
? { headless: true, channel: 'chromium', args: ['--no-sandbox'] }
: { headless: true },
);
const page = await browser.newPage({
viewport: { width, height: 900 },
...(engine === 'webkit' ? { isMobile: true, hasTouch: true } : {}),
});
const errors = [];
page.on('pageerror', (e) => errors.push(e.message));
page.on('console', (m) => {
if (m.type() === 'error') {
console.log('PAGE_CONSOLE_ERROR', m.text());
errors.push(m.text());
}
});
page.on('requestfailed', (r) => console.log('REQUEST_FAILED', r.url(), r.failure()));
let minted = 0;
let optionalAuth = false;
await page.addInitScript(() => {
window.__copies = [];
Object.defineProperty(navigator, 'clipboard', {
value: { writeText: async (text) => window.__copies.push(text) },
});
});
await page.route('**/api/**', async (route) => {
const url = new URL(route.request().url());
if (!url.pathname.startsWith('/api/')) return route.continue();
if (url.pathname === '/api/security/status')
return route.fulfill({
json: {
requiresAuth: !optionalAuth,
hasAuthentication: !optionalAuth,
apiTokenConfigured: !optionalAuth,
},
});
if (url.pathname === '/api/agent-install-command') {
const body = route.request().postDataJSON();
assert.equal(body.type, 'host');
assert.equal(body.enableCommands, false);
minted++;
return route.fulfill({
json: {
token: secret,
record: {
id: 'synthetic-token-record',
name: 'Synthetic installer',
prefix: 'browser',
suffix: 'token',
createdAt: new Date().toISOString(),
},
},
});
}
if (url.pathname === '/api/state')
return route.fulfill({ json: { connectedInfrastructure: [] } });
return route.fulfill({ json: { data: [] } });
});
await page.goto('http://127.0.0.1:5241/browser-tests/unix-private-install.html');
await page.waitForTimeout(1000);
console.log('PAGE_TITLE', await page.title());
console.log(
JSON.stringify({
engine,
errors,
body: (await page.locator('body').innerText()).slice(0, 300),
}),
);
await page
.getByRole('heading', { name: 'Private Unix agent installation', exact: true })
.waitFor();
if (tone === 'dark')
await page.evaluate(() => document.documentElement.classList.add('dark'));
await page.getByRole('button', { name: 'Generate token', exact: true }).first().click();
const dialog = page.getByRole('dialog', { name: 'API token ready' });
await dialog.waitFor();
await page.waitForFunction(() => {
const d = document.querySelector('[role="dialog"]');
return d && getComputedStyle(d).opacity === '1';
});
assert.match(await dialog.innerText(), /silent “Pulse agent token” prompt/);
const copyToken = dialog.getByRole('button', { name: 'Copy token', exact: true });
await copyToken.focus();
await page.keyboard.press('Enter');
assert.equal(await page.evaluate(() => window.__copies.at(-1)), secret);
await page.screenshot({ path: path.join(artifacts, `${engine}-token.png`), fullPage: true });
await page.keyboard.press('Escape');
await dialog.waitFor({ state: 'hidden' });
// The real component's platform cards and copy handlers.
const code = page.locator('code').filter({ hasText: 'Pulse agent token' });
assert.ok((await code.count()) >= 3);
const unixCommands = await code.allTextContents();
for (const command of unixCommands) {
assert.ok(!command.includes(secret));
assert.ok(command.includes('--token-file "$token_file"'));
assert.ok(!/[\r\n]/.test(command));
}
const guiCode = page.locator('code').filter({ hasText: 'token_parent=${token_file%/*}' });
assert.equal(await guiCode.count(), 1);
const guiCommand = await guiCode.innerText();
assert.ok(guiCommand.includes('/root/.config/pulse-agent/bootstrap-token'));
assert.ok(!guiCommand.includes('read -r'));
assert.ok(!guiCommand.includes(secret));
assert.ok(
(await page.locator('body').innerText()).includes(
'Never put the token in a GUI command field',
),
);
await page.getByRole('button', { name: 'Copy Install command', exact: true }).click();
assert.equal(await page.evaluate(() => window.__copies.at(-1)), unixCommands[0]);
await page
.getByRole('button', {
name: 'Copy Install from a private token file (no terminal) command',
exact: true,
})
.click();
assert.equal(await page.evaluate(() => window.__copies.at(-1)), guiCommand);
await page
.getByRole('button', {
name: 'Copy authentication repair command for doctor-fixture',
exact: true,
})
.click();
assert.ok(!(await page.evaluate(() => window.__copies.at(-1))).includes(secret));
await page.getByRole('button', { name: 'Show token only', exact: true }).click();
await dialog.waitFor();
assert.equal(minted, 1);
await dialog.getByRole('button', { name: 'Dismiss', exact: true }).click();
await page
.getByRole('button', { name: 'Show advanced connection and install options', exact: true })
.click();
await page
.getByLabel('Connection URL (Agent → Pulse)', { exact: true })
.fill('https://pulse.example/base/');
await page
.getByLabel('Custom CA certificate path (optional)', { exact: true })
.fill('/etc/pulse/ca.pem');
for (const label of [
'Copy Unix uninstall',
'Copy Unix credential repair',
'Copy Unix saved-state update',
]) {
await page.getByRole('button', { name: label, exact: true }).click();
const command = await page.evaluate(() => window.__copies.at(-1));
assert.ok(!command.includes(secret));
assert.ok(command.includes('https://pulse.example/base'));
assert.ok(command.includes('--cacert'));
assert.ok(!command.includes('| bash'));
if (label.includes('uninstall')) {
assert.ok(command.includes('--uninstall'));
assert.ok(command.includes('agent-fixture-42'));
assert.ok(command.includes('node.example'));
assert.ok(!command.includes('--preflight-only'));
} else {
assert.ok(command.includes('--update'));
assert.ok(command.includes('--enable-docker'));
assert.ok(command.includes('--preflight-only'));
if (label.includes('credential')) {
assert.ok(command.includes('agent-fixture-42'));
assert.ok(command.includes('node.example'));
assert.ok(command.includes('--token-file'));
} else {
assert.ok(!command.includes('--token-file'));
assert.ok(!command.includes('read -r'));
}
}
}
assert.ok(
(await page.locator('body').innerText()).includes(
'Never insert the token into the command',
),
);
await page.screenshot({
path: path.join(artifacts, `${engine}-commands.png`),
fullPage: true,
});
optionalAuth = true;
await page.goto('http://127.0.0.1:5241/browser-tests/unix-private-install.html?optional=1');
await page.getByRole('button', { name: 'Confirm without token', exact: true }).click();
const optionalCode = page.locator('code').filter({ hasText: 'bootstrap_dir=$(mktemp' });
const optionalCommands = await optionalCode.allTextContents();
assert.ok(optionalCommands.length >= 3);
for (const command of optionalCommands) {
assert.ok(!command.includes('--token-file'));
assert.ok(!command.includes('read -r'));
}
assert.equal(
await page
.getByRole('button', {
name: 'Copy Install from a private token file (no terminal) command',
exact: true,
})
.count(),
0,
);
assert.deepEqual(errors, []);
results.push({
engine,
version: browser.version(),
width,
tone,
issued: minted,
copied: await page.evaluate(() => window.__copies.length),
unixCommands: unixCommands.length,
guiCommand: true,
optionalAuth: true,
errors,
});
await browser.close();
browser = undefined;
}
fs.writeFileSync(
path.join(artifacts, 'result.json'),
JSON.stringify({ playwright: require('playwright/package.json').version, results }, null, 2),
);
console.log(JSON.stringify({ result: 'passed', artifacts, results }));
} finally {
if (browser) await browser.close();
await server.close();
}
})().catch((e) => {
console.error(e);
process.exitCode = 1;
});
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Unix private agent installation</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./unix-private-install.tsx"></script>
</body>
</html>
@@ -0,0 +1,103 @@
// Production settings state, installer and token dialog; synthetic API data only.
import { Route, Router } from '@solidjs/router';
import { render } from 'solid-js/web';
import {
InfrastructureOperationsStateProvider,
useInfrastructureOperationsContext,
} from '../src/components/Settings/useInfrastructureOperationsState';
import { InfrastructureInstallerSection } from '../src/components/Settings/InfrastructureInstallerSection';
import { InfrastructureAgentDoctorPage } from '../src/components/Settings/InfrastructureAgentDoctorPage';
import type { InfrastructureAgentDoctorTarget } from '../src/components/Settings/infrastructureAgentUpdateCommandsModel';
import { TokenRevealDialog } from '../src/components/TokenRevealDialog';
import '../src/index.css';
const Operations = () => {
const state = useInfrastructureOperationsContext();
const identity = {
agentActionId: 'agent-fixture-42',
agentId: 'unused-alias',
hostname: 'node.example',
};
const connection = {
id: 'agent:agent-fixture-42',
type: 'agent',
name: 'node.example',
address: 'node.example',
state: 'unauthorized',
agentIdentity: { hostname: 'node.example', commandsEnabled: false },
} as Parameters<typeof state.getAgentConnectionUpgradeCommand>[0];
return (
<section class="space-y-2 p-4 border border-border" aria-label="Lifecycle command fixture">
<p>Actual operations state: identity and trust options must survive.</p>
<button onClick={() => navigator.clipboard.writeText(state.getUninstallCommand(identity))}>
Copy Unix uninstall
</button>
<button
onClick={() =>
navigator.clipboard.writeText(
state.getAgentConnectionUpgradeCommand(connection, ['--enable-docker'], 'linux', true),
)
}
>
Copy Unix credential repair
</button>
<button
onClick={() =>
navigator.clipboard.writeText(
state.getAgentConnectionUpgradeCommand(connection, ['--enable-docker'], 'linux', false),
)
}
>
Copy Unix saved-state update
</button>
</section>
);
};
const Fixture = () => (
<InfrastructureOperationsStateProvider embedded>
<main class="mx-auto max-w-5xl p-4 bg-base text-base-content">
<h1 class="text-xl mb-4">Private Unix agent installation</h1>
<InfrastructureInstallerSection />
<Operations />
<Doctor />
<TokenRevealDialog />
</main>
</InfrastructureOperationsStateProvider>
);
const Doctor = () => {
const target = {
key: 'agent:doctor-fixture',
connectionId: 'agent:doctor-fixture',
displayName: 'doctor-fixture',
contextLabel: 'Machine',
currentVersion: '6.4.5',
installFlags: ['--enable-docker'],
status: 'critical',
reasons: [],
evidence: [],
needsUpdate: false,
needsCredentialRepair: true,
commandPlatform: 'linux',
safeCollector: false,
actionRunnerPosture: [],
actionRunnerCredentialEligible: false,
source: 'ledger',
connection: {
id: 'agent:doctor-fixture',
type: 'agent',
name: 'doctor-fixture',
address: 'doctor.example',
state: 'unauthorized',
agentIdentity: { hostname: 'doctor.example', commandsEnabled: false },
},
} as InfrastructureAgentDoctorTarget;
return <InfrastructureAgentDoctorPage targets={[target]} />;
};
render(
() => (
<Router>
<Route path="*" component={Fixture} />
</Router>
),
document.getElementById('root')!,
);
+31 -32
View File
@@ -1,23 +1,27 @@
{
"version": 1,
"base_sha": "6b23876ec8104965c7842e194e055f3206134d4f",
"verified_at": "2026-10-01T04:21:56.727078Z",
"base_sha": "9189a7262e84b201a5af6649caf0f8cff7c6d144",
"verified_at": "2026-10-01T13:06:33.056850Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/api/nodes.ts",
"frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx",
"frontend-modern/src/components/Settings/useNodeModalState.ts",
"frontend-modern/src/stores/tokenReveal.ts"
"frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx",
"frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx",
"frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx",
"frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx",
"frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx",
"frontend-modern/src/utils/agentInstallCommand.ts"
],
"content_sha256": {
"frontend-modern/src/api/nodes.ts": "398f3b87ce192c7650a9e34f184ecdca0e8baf35b57d90ef5f8f697348675fec",
"frontend-modern/src/components/Settings/NodeModalSetupGuideSection.tsx": "e4168945744f8ec20b5439f5730b1ecd8b4811b29ec00da4e7e3798f868a1b41",
"frontend-modern/src/components/Settings/useNodeModalState.ts": "9372c3a4b346a0e31733a7042c0a65b5ebe3518b12595c6c6a68b1cadd667dcd",
"frontend-modern/src/stores/tokenReveal.ts": "c21ac970bde6229506f7d6e627430b6e91c9ea983f705a28e3d149e4415c39b7"
"frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx": "416c7627303c665f956168a32e5aaf6af4268abacdd7e93045ab26cd45732d14",
"frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx": "d2ff336c1e602794fd696aed3541684498adfcfeaf1f4e603e21a44865fce3bb",
"frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx": "983a44871cfb908851c215a177e4a39185f7ac9017cc1b2152323b7ee396995a",
"frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx": "c57bf5a99d457d6d21f0d22e0cc34af3618db214391fd09afce34743173a1c0f",
"frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx": "276c89076d36670752f5d37be1895d739ae13501ea9d87d1cc3a497c7e6f5895",
"frontend-modern/src/utils/agentInstallCommand.ts": "9bb98b1db1b49aa478f894b346479951954899d4c6049d991ec4c75730cafa30"
},
"routes": [
"/browser-tests/proxmox-private-input.html?type=pve",
"/browser-tests/proxmox-private-input.html?type=pbs"
"/browser-tests/unix-private-install.html",
"/browser-tests/unix-private-install.html?optional=1"
],
"viewports": [
{
@@ -30,34 +34,29 @@
}
],
"states": [
"Actual settings state/setup section and existing token-reveal dialog with production CSS; synthetic server artifacts and no installed bootstrap execution",
"Separate command and credential copy for API Inventory and Host Telemetry Agent; raw secrets absent from copied shell text",
"PVE/PBS tokenless script download reuses live bootstrap metadata; credential cache clears on close/reopen",
"Held setup and agent issuance completed after modal close is discarded: no secret cache and no reveal dialog",
"Default TLS verification and monitoring-only agent request (enableCommands=false) unchanged",
"Desktop light and phone-emulated WebKit dark token reveal is readable; screenshots visually inspected"
"Actual infrastructure installer, operations closures, Agent Doctor and token dialog with production CSS; synthetic APIs, not installed agent/appliance execution",
"Required-auth Unix command and credential remain separate; single-line command grammar, private file no-terminal variant and separate-token instructions",
"Canonical repair/removal identity and custom CA preserved; saved-state update has no replacement credential; removal has no new-binary preflight",
"Optional-auth commands require no terminal or token-file; default TLS verification and monitoring-only issuance unchanged",
"Desktop Chromium light and phone-emulated WebKit dark; four final screenshots visually inspected"
],
"interactions": [
"Copy command; copy token via Enter and pointer; Escape and Dismiss close token dialog",
"Close/reopen setup; download script; switch API/Agent paths",
"Hold setup/agent response, close modal, then release response and verify no late reveal/cache"
"Generate scoped host token, copy token with keyboard, Escape, reopen existing reveal without issuing another token",
"Copy actual Linux installer, no-terminal private-file installer and Agent Doctor repair commands; raw credential absent from each clipboard result",
"Change endpoint and custom CA; copy actual operations-state repair/removal/saved-state update commands; identity/profile/trust checked",
"Reload with optional auth and confirm tokenless commands"
],
"command": "pulse-worker-browser frontend-modern/browser-tests/proxmox-private-input.cjs; pulse-worker-browser frontend-modern/browser-tests/proxmox-private-input.cjs --phone (separate commands from assigned workspace root)",
"command": "pulse-worker-browser frontend-modern/browser-tests/unix-private-install.cjs",
"browser_versions": {
"playwright": "1.56.1",
"chromium": "141.0.7390.37",
"webkit": "26.0"
},
"artifacts": [
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-desktop/pbs-agent.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-desktop/pbs-setup.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-desktop/pve-agent.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-desktop/pve-setup.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-desktop/result.json",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-phone/pbs-agent.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-phone/pbs-setup.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-phone/pve-agent.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-phone/pve-setup.png",
"/var/lib/pulse-maintainer/worker-outputs/core-runtime-yp94gsia/browser-phone/result.json"
"/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/chromium-commands.png",
"/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/chromium-token.png",
"/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/result.json",
"/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/webkit-commands.png",
"/var/lib/pulse-maintainer/worker-outputs/web-product-wielhxw1/browser/webkit-token.png"
]
}
@@ -214,7 +214,7 @@ describe('aiChatUtils', () => {
expect(link?.getAttribute('rel')).toBe('noopener noreferrer');
});
it.each(['beforeSanitizeElements', 'uponSanitizeElement'] as const)(
it.each(['beforeSanitizeElements', 'uponSanitizeElement', 'afterSanitizeAttributes'] as const)(
'neutralizes a hook-detached IN_PLACE subtree through the %s hook',
(hookName) => {
// Configure the same singleton hooks used by renderMarkdown before
@@ -232,8 +232,10 @@ describe('aiChatUtils', () => {
};
if (hookName === 'beforeSanitizeElements') {
DOMPurify.addHook('beforeSanitizeElements', removeSection);
} else {
} else if (hookName === 'uponSanitizeElement') {
DOMPurify.addHook('uponSanitizeElement', removeSection);
} else {
DOMPurify.addHook('afterSanitizeAttributes', removeSection);
}
try {
@@ -253,8 +255,10 @@ describe('aiChatUtils', () => {
} finally {
if (hookName === 'beforeSanitizeElements') {
DOMPurify.removeHook('beforeSanitizeElements', removeSection);
} else {
} else if (hookName === 'uponSanitizeElement') {
DOMPurify.removeHook('uponSanitizeElement', removeSection);
} else {
DOMPurify.removeHook('afterSanitizeAttributes', removeSection);
}
}
},
@@ -917,6 +917,18 @@ export const InfrastructureAgentDoctorPage: Component<InfrastructureAgentDoctorP
<pre class="overflow-x-auto rounded-md bg-base p-3 pr-12 text-xs text-base-content">
<code>{command()}</code>
</pre>
<Show
when={
commandRequiresToken(target) &&
target.commandPlatform !== 'windows'
}
>
<p class="mt-2 text-xs text-muted">
Run this command in a console or SSH session first, then
paste the separately revealed token at the silent Pulse
agent token prompt. Never insert the token into the command.
</p>
</Show>
</div>
</Show>
</Show>
@@ -929,7 +941,9 @@ export const InfrastructureAgentDoctorPage: Component<InfrastructureAgentDoctorP
This agent was removed from Pulse, but the agent software may
still be installed on its host. Finish detaching it by running
the uninstall command on the affected host itself. Pulse does
not run commands remotely.
not run commands remotely. For Unix commands that require a
token, run the command first, then paste the separately revealed
token at its silent prompt; never insert it into the command.
</p>
<For each={handoff().commands}>
{(entry) => (
@@ -396,12 +396,13 @@ export const InfrastructureInstallerSection: Component<InfrastructureInstallerSe
{state.latestTokenSource() === 'setup_handoff' ? (
<>
First-host install token <strong>{state.latestRecord()?.name}</strong>{' '}
prepared automatically. Commands below already include this credential.
prepared automatically. Unix commands request it separately at a silent
prompt.
</>
) : (
<>
Install token <strong>{state.latestRecord()?.name}</strong> created. Commands
below now include this credential.
Install token <strong>{state.latestRecord()?.name}</strong> created. Unix
commands keep it separate: copy the command first, then use Show token only.
</>
)}
</span>
@@ -421,8 +422,8 @@ export const InfrastructureInstallerSection: Component<InfrastructureInstallerSe
<Show when={!state.requiresToken()}>
<div class="space-y-3">
<div class="rounded-md border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800 dark:border-amber-700 dark:bg-amber-900 dark:text-amber-200">
Tokens are optional on this Pulse instance. Confirm to generate commands without
embedding a token.
Tokens are optional on this Pulse instance. Confirm to generate commands without a
token.
</div>
<button
type="button"
@@ -449,8 +450,8 @@ export const InfrastructureInstallerSection: Component<InfrastructureInstallerSe
Installation commands
</h4>
<p class="ml-6 text-xs text-muted">
Generate an install token first. Pulse will then build copy-ready commands with the
credential inserted for the target host.
Generate an install token first. Then copy the command for the target host and
follow its credential-entry instructions.
</p>
</div>
<div class="grid gap-3 lg:grid-cols-2">
@@ -496,9 +497,9 @@ export const InfrastructureInstallerSection: Component<InfrastructureInstallerSe
</h4>
<p class={`mt-0.5 text-xs text-muted ${state.requiresToken() ? 'ml-6' : ''}`}>
Copy the default command for the first host first. It checks this Pulse URL and
the matching agent binary before asking for administrator privileges, then
installs Pulse Agent as a background service on each machine where you want full
node-local telemetry.
the matching agent binary before reading a Unix token, then installs Pulse Agent
as a background service on each machine where you want full node-local
telemetry.
</p>
</div>
</div>
@@ -277,9 +277,7 @@ describe('infrastructure operations model', () => {
'Use Availability checks for ping-only or agentless device monitoring.',
);
expect(infrastructureInstallerSectionSource).toContain('checks this Pulse URL and');
expect(infrastructureInstallerSectionSource).toContain(
'before asking for administrator privileges',
);
expect(infrastructureInstallerSectionSource).toContain('before reading a Unix token');
expect(infrastructureInstallerSectionSource).toContain(
'For Proxmox, start with a dedicated read-only or narrowly scoped API token',
);
@@ -287,7 +285,11 @@ describe('infrastructure operations model', () => {
expect(infrastructureInstallerSectionSource).toContain('augmentation.');
expect(infrastructureInstallerSectionSource).toContain('Installation commands');
expect(infrastructureInstallerSectionSource).toContain(
'Generate an install token first. Pulse will then build copy-ready commands',
'Generate an install token first. Then copy the command for the target host and',
);
expect(infrastructureInstallerSectionSource).toContain('follow its credential-entry instructions.');
expect(infrastructureInstallerSectionSource).toContain(
'commands keep it separate: copy the command first, then use Show token only.',
);
expect(infrastructureInstallerSectionSource).toContain(
'Install the transitional combined runtime that can accept server command requests',
@@ -472,13 +474,9 @@ describe('infrastructure operations model', () => {
expect(agentUpgradeStart).toBeGreaterThanOrEqual(0);
expect(agentUpgradeEnd).toBeGreaterThan(agentUpgradeStart);
const agentUpgradeSource = operationsStateSource.slice(agentUpgradeStart, agentUpgradeEnd);
const unixUpgradeStart = agentUpgradeSource.indexOf('let command = `curl');
expect(unixUpgradeStart).toBeGreaterThanOrEqual(0);
const unixUpgradeSource = agentUpgradeSource.slice(unixUpgradeStart);
expect(agentUpgradeSource).toContain(
'| bash -s -- --update --url ${shellQuoteArg(url)} --non-interactive',
);
expect(agentUpgradeSource).toContain("const extraArgs = ['--update', ...installFlags];");
expect(agentUpgradeSource).toContain('token: replaceCredential ? token : null,');
expect(agentUpgradeSource).toContain('buildUnixAgentInstallCommand({');
const requiresTokenEnd = operationsStateSource.indexOf('return {', agentUpgradeEnd);
expect(requiresTokenEnd).toBeGreaterThan(agentUpgradeEnd);
const requiresTokenSource = operationsStateSource.slice(agentUpgradeEnd, requiresTokenEnd);
@@ -487,9 +485,9 @@ describe('infrastructure operations model', () => {
);
expect(requiresTokenSource).toContain("=== 'windows'");
expect(requiresTokenSource).toContain('installState.requiresToken()');
expect(unixUpgradeSource).not.toContain('command += ` --token ${shellQuoteArg(token)}`;');
expect(unixUpgradeSource).not.toContain('--agent-id');
expect(unixUpgradeSource).not.toContain('--hostname');
expect(agentUpgradeSource).not.toContain('command += ` --token ${shellQuoteArg(token)}`;');
// Identity overrides remain inside the explicit credential-replacement branch.
expect(agentUpgradeSource).toContain('if (replaceCredential) {');
});
it('resolves connection upgrade platforms through the shared caption-tolerant resolver', async () => {
@@ -1,4 +1,7 @@
import { describe, expect, it } from 'vitest';
import { unixBootstrapTestSource } from '@/utils/__tests__/unixBootstrapTestSource';
import { execFileSync } from 'node:child_process';
import { useInfrastructureOperationsState } from '../useInfrastructureOperationsState';
import { describe, expect, it, vi } from 'vitest';
import type { AgentFleetAgentDiagnostic } from '@/api/agentDiagnostics';
import type { Connection } from '@/api/connections';
import type { InfrastructureSystemRow } from '../connectionsTableModel';
@@ -390,8 +393,8 @@ describe('Agent Doctor model', () => {
expect(install).toContain('--enable-privileged-helper');
expect(install).toContain('--enable-action-runner');
expect(install).toContain('--action-token-file');
expect(install).toContain("--agent-id 'host-1'");
expect(install).toContain("--hostname 'host-1.local'");
expect(unixBootstrapTestSource(install)).toContain("--agent-id 'host-1'");
expect(unixBootstrapTestSource(install)).toContain("--hostname 'host-1.local'");
expect(install).toContain("--cacert '/etc/pulse/ca.pem'");
expect(install).not.toContain(secret);
expect(install).not.toContain('--action-token ');
@@ -1097,3 +1100,76 @@ describe('Agent Doctor model', () => {
});
});
});
// Exercise the real operations closures, not a hand-written command fixture.
// Minting/state storage are outside this narrow presentation/transport proof.
const privateInstallState = vi.hoisted(() => ({ token: 'issued-secret-not-shell-source' }));
vi.mock('../useInfrastructureInstallState', () => ({
useInfrastructureInstallState: () => ({
currentToken: () => privateInstallState.token,
requiresToken: () => true,
selectedAgentUrl: () => 'https://pulse.example/base',
insecureMode: () => false,
customCaPath: () => '/etc/pulse/ca.pem',
enableCommands: () => false,
}),
}));
describe('Agent Doctor Unix credential handoffs', () => {
it('keeps repair/uninstall secrets separate while preserving identity, profile and custom trust', () => {
const operations = useInfrastructureOperationsState();
const connection = connectionFixture({
id: 'agent:canonical-id-42',
agentIdentity: { hostname: 'canonical.example', commandsEnabled: false },
state: 'unauthorized',
});
const repair = operations.getAgentConnectionUpgradeCommand(
connection,
['--enable-docker'],
'linux',
true,
);
const uninstall = operations.getUninstallCommand({
agentActionId: 'canonical-id-42',
agentId: 'old-alias',
hostname: 'canonical.example',
});
for (const command of [repair, uninstall]) {
expect(command).not.toContain(privateInstallState.token);
expect(command).not.toContain('old-alias');
expect(command).not.toContain('| bash');
expect(command).toContain('--token-file "$token_file"');
expect(command).toContain('--agent-id');
expect(command).toContain('canonical-id-42');
expect(command).toContain('--hostname');
expect(command).toContain('canonical.example');
expect(command).toContain('--cacert');
expect(command).not.toContain('--insecure');
expect(command).not.toContain('--enable-commands');
execFileSync('sh', ['-n', '-c', command]);
}
expect(repair).toContain('--update');
expect(repair).toContain('--enable-docker');
expect(repair).toContain('--preflight-only');
expect(uninstall).toContain('--uninstall');
expect(uninstall).not.toContain('--preflight-only');
});
it('does not mint, inject or prompt for another credential during a saved-state update', () => {
const operations = useInfrastructureOperationsState();
const command = operations.getAgentConnectionUpgradeCommand(
connectionFixture(),
['--enable-docker'],
'linux',
);
expect(command).toContain('--update');
expect(command).toContain('--enable-docker');
expect(command).toContain('--preflight-only');
expect(command).not.toContain('--token-file');
expect(command).not.toContain('read -r');
expect(command).not.toContain(privateInstallState.token);
expect(
operations.getAgentConnectionUpgradeCommandRequiresToken(connectionFixture(), 'linux'),
).toBe(false);
});
});
@@ -1,3 +1,4 @@
import { unixBootstrapTestSource } from '@/utils/__tests__/unixBootstrapTestSource';
import { createRoot } from 'solid-js';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { Connection } from '@/api/connections';
@@ -159,51 +160,47 @@ describe('useInfrastructureOperationsState command-building closures', () => {
vi.resetAllMocks();
});
describe('withPrivilegeEscalation (via getUninstallCommand)', () => {
it('wraps a "| bash -s --" command in the root/sudo escalation block verbatim', async () => {
describe('shared Unix bootstrap (via getUninstallCommand)', () => {
it('uses the complete-download/preflight/privileged private-input path', async () => {
const { state, dispose } = mountHook();
await flushAsync();
state.setCustomAgentUrl(HTTPS_URL);
const cmd = state.getUninstallCommand();
expect(cmd).toContain('| { if [ "$(id -u)" -eq 0 ]; then bash -s -- --uninstall --url');
expect(cmd).toContain(
'; elif command -v sudo >/dev/null 2>&1; then sudo bash -s -- --uninstall --url',
);
expect(cmd).toContain(
'; else echo "Root privileges required. Run as root (su -) and retry." >&2; exit 1; fi; }',
);
// The original pipe form is consumed by the replacement.
expect(cmd).not.toMatch(/^\S+ \| bash -s --/);
expect(cmd).toContain('--uninstall');
expect(cmd).toContain('sudo bash -c');
expect(cmd).not.toContain('--preflight-only');
expect(cmd).not.toContain('| bash');
expect(cmd).not.toContain("--token '");
dispose();
});
});
describe('resolvedCommandToken (via getUninstallCommand)', () => {
it('substitutes the api-token placeholder when a token is required but absent', async () => {
it('requests private input without copying a placeholder when a token is required', async () => {
const { state, dispose } = mountHook();
await flushAsync();
state.setCustomAgentUrl(HTTPS_URL);
expect(state.getUninstallCommand()).toContain("--token '<api-token>'");
expect(state.getUninstallCommand()).toContain('--token-file "$token_file"');
expect(state.getUninstallCommand()).not.toContain('<api-token>');
dispose();
});
it('uses the minted token verbatim when one is present and required', async () => {
it('keeps the minted token separate when one is present and required', async () => {
const { state, dispose } = mountHook();
await flushAsync();
state.setCustomAgentUrl(HTTPS_URL);
await state.handleGenerateToken();
const cmd = state.getUninstallCommand();
expect(cmd).toContain("--token 'tok-1'");
expect(cmd).toContain('--token-file "$token_file"');
expect(cmd).not.toContain('tok-1');
expect(cmd).not.toContain('<api-token>');
expect(mocks.showTokenReveal).toHaveBeenCalledWith({
token: 'tok-1',
record: expect.objectContaining({ id: 'rec-1', name: 'Agent' }),
source: 'agent',
note: expect.stringContaining('PULSE_TOKEN or Compose environment configuration'),
note: expect.stringContaining('silent “Pulse agent token” prompt'),
});
dispose();
});
@@ -221,7 +218,7 @@ describe('useInfrastructureOperationsState command-building closures', () => {
token: 'tok-1',
record: expect.objectContaining({ id: 'rec-1', name: 'Agent' }),
source: 'agent',
note: expect.stringContaining('PULSE_TOKEN or Compose environment configuration'),
note: expect.stringContaining('silent “Pulse agent token” prompt'),
});
dispose();
});
@@ -239,7 +236,9 @@ describe('useInfrastructureOperationsState command-building closures', () => {
const cmd = state.getUninstallCommand();
expect(cmd).not.toContain('--token');
expect(cmd).not.toContain('<api-token>');
expect(cmd).toContain("--uninstall --url 'https://pulse.test'");
expect(cmd).toContain('--uninstall');
expect(cmd).toContain('--url');
expect(cmd).toContain('https://pulse.test');
dispose();
});
});
@@ -310,8 +309,8 @@ describe('useInfrastructureOperationsState command-building closures', () => {
};
const cmd = state.getUninstallCommand(row);
expect(cmd).toContain("--agent-id 'act-9'");
expect(cmd).toContain("--hostname 'host-9'");
expect(unixBootstrapTestSource(cmd)).toContain("--agent-id 'act-9'");
expect(unixBootstrapTestSource(cmd)).toContain("--hostname 'host-9'");
expect(cmd).not.toContain('agent-9');
dispose();
});
@@ -323,8 +322,8 @@ describe('useInfrastructureOperationsState command-building closures', () => {
const row: AgentUninstallIdentity = { agentId: 'agent-7', hostname: 'host-7' };
const cmd = state.getUninstallCommand(row);
expect(cmd).toContain("--agent-id 'agent-7'");
expect(cmd).toContain("--hostname 'host-7'");
expect(unixBootstrapTestSource(cmd)).toContain("--agent-id 'agent-7'");
expect(unixBootstrapTestSource(cmd)).toContain("--hostname 'host-7'");
dispose();
});
@@ -633,8 +632,8 @@ describe('useInfrastructureOperationsState command-building closures', () => {
true,
);
expect(cmd).toContain('tmp_dir=$(mktemp -d)');
expect(cmd).toContain('printf %s \'tok-1\' > "$token_file"');
expect(cmd).toContain('bootstrap_dir=$(mktemp -d /tmp/pulse-bootstrap.XXXXXX)');
expect(cmd).not.toContain('tok-1');
expect(cmd).toContain('--token-file "$token_file"');
expect(cmd).toContain('--update');
expect(cmd).toContain('--enable-proxmox');
@@ -651,9 +650,10 @@ describe('useInfrastructureOperationsState command-building closures', () => {
const cmd = state.getAgentConnectionUpgradeCommand(baseConnection, [], 'linux');
expect(cmd).toContain('| { if [ "$(id -u)" -eq 0 ]; then bash -s -- --update');
expect(cmd).toContain('--update');
expect(cmd).toContain('sudo bash -c');
expect(cmd).not.toContain('--token-file');
expect(cmd).not.toContain('mktemp -d');
expect(cmd).not.toContain('token_dir=');
dispose();
});
@@ -333,6 +333,7 @@ export const buildCommandsByPlatform = (
unixCommand: string,
windowsInteractiveCommand: string,
windowsParameterizedCommand: string,
unixPrivateFileCommand?: string,
): Record<AgentPlatform, InfrastructureCommandSection> => ({
linux: {
title: 'Install on Linux',
@@ -345,8 +346,9 @@ export const buildCommandsByPlatform = (
note: (
<span>
Command auto-escalates with <code>sudo</code> when available. Otherwise run from a root
shell (for example <code>su -</code>). Auto-detects your init system and works on
Debian, Ubuntu, Proxmox, Fedora, Alpine, Unraid, Synology, and more.
shell (for example <code>su -</code>). If a token is required, run this command first,
then paste the separately revealed token at its silent prompt. Auto-detects your init
system and works on Debian, Ubuntu, Proxmox, Fedora, Alpine, Unraid, Synology, and more.
</span>
),
},
@@ -363,8 +365,10 @@ export const buildCommandsByPlatform = (
note: (
<span>
Command auto-escalates with <code>sudo</code> when available, so macOS may ask for your
admin password. Creates <code>/Library/LaunchDaemons/com.pulse.agent.plist</code> and
starts the agent automatically.
admin password. If a token is required, run this command first, then paste the
separately revealed token at its silent prompt. Creates{' '}
<code>/Library/LaunchDaemons/com.pulse.agent.plist</code> and starts the agent
automatically.
</span>
),
},
@@ -380,12 +384,33 @@ export const buildCommandsByPlatform = (
command: unixCommand,
note: (
<span>
Run as root. <strong>Note:</strong> pfSense/OPNsense don't include bash by default.
Install it first: <code>pkg install bash</code>. Creates{' '}
Run in a console or SSH session as root. Paste the separately revealed token only at the
silent prompt, not into the command. For GUI command fields without a terminal, use the
private-file command below. <strong>Note:</strong> pfSense/OPNsense don't include bash
by default. Install it first: <code>pkg install bash</code>. Creates{' '}
<code>/usr/local/etc/rc.d/pulse-agent</code> and starts the agent automatically.
</span>
),
},
...(unixPrivateFileCommand
? [
{
label: 'Install from a private token file (no terminal)',
command: unixPrivateFileCommand,
note: (
<span>
Before running, save the separately revealed token to{' '}
<code>/root/.config/pulse-agent/bootstrap-token</code> using a trusted file editor
or upload path. The file must be root-owned with mode <code>0600</code> in a
root-owned <code>0700</code> directory. Never put the token in a GUI command
field. This command does not prompt or delete your file; remove it through the
same trusted file path afterwards. If your GUI cannot create private files, use
console or SSH instead. Bash must already be installed.
</span>
),
},
]
: []),
],
},
windows: {
@@ -47,7 +47,7 @@ const SETUP_HANDOFF_INSTALL_STEPS = new Set<InfrastructurePanelStep>([
'kubernetes',
]);
const INSTALL_TOKEN_REVEAL_NOTE =
'Copy just this token for PULSE_TOKEN or Compose environment configuration. The generated install commands remain available after you close this dialog.';
'For Unix installation, run the copied command first, then paste this token only at the silent “Pulse agent token” prompt. The no-terminal command reads the private token file named beside it instead. Never insert the token into a Unix command. For Windows or container configuration, keep copied credentials private.';
const isActiveInfrastructureItem = (item: ConnectedInfrastructureItem) => item.status === 'active';
@@ -568,6 +568,15 @@ Pulse prepares the first-host install token from setup so you can move straight
unixCommand,
windowsInteractiveCommand,
windowsParameterizedCommand,
token
? buildUnixAgentInstallCommand({
baseUrl: url,
tokenFilePath: '/root/.config/pulse-agent/bootstrap-token',
insecure: insecureMode(),
caCertPath: selectedCustomCaPath(),
extraArgs: getInstallerExtraArgs(),
})
: undefined,
);
return Object.entries(commands).map(([platform, meta]) => ({
platform: platform as AgentPlatform,
@@ -33,26 +33,7 @@ export const useInfrastructureOperationsState = (
) => {
const installState = useInfrastructureInstallState(options);
const withPrivilegeEscalation = (command: string) => {
if (!command.includes('| bash -s --')) return command;
return command.replace(/\|\s*bash -s --([\s\S]*)$/, (_match, args: string) => {
return `| { if [ "$(id -u)" -eq 0 ]; then bash -s --${args}; elif command -v sudo >/dev/null 2>&1; then sudo bash -s --${args}; else echo "Root privileges required. Run as root (su -) and retry." >&2; exit 1; fi; }`;
});
};
const selectedCustomCaPath = () => installState.customCaPath().trim();
const urlRequiresInstallerInsecure = (url: string) =>
installState.insecureMode() || url.trim().toLowerCase().startsWith('http://');
const getInsecureFlag = (url: string) => (urlRequiresInstallerInsecure(url) ? ' --insecure' : '');
const getCurlFlags = () => (installState.insecureMode() ? '-kfsSL' : '-fsSL');
const getShellCustomCaCurlFlag = () => {
const caPath = selectedCustomCaPath();
return caPath ? ` --cacert ${shellQuoteArg(caPath)}` : '';
};
const getShellCustomCaInstallerFlag = () => {
const caPath = selectedCustomCaPath();
return caPath ? ` --cacert ${shellQuoteArg(caPath)}` : '';
};
const getPowerShellTransportEnv = () => {
const envAssignments: string[] = [];
if (installState.insecureMode()) {
@@ -99,16 +80,19 @@ export const useInfrastructureOperationsState = (
const getUninstallCommand = (row?: AgentUninstallIdentity) => {
const url = installState.selectedAgentUrl();
const token = resolvedCommandToken();
const insecure = getInsecureFlag(url);
const agentId = getCanonicalUninstallAgentId(row);
const hostname = getCanonicalUninstallHostname(row);
const baseArgs = token
? `--uninstall --url ${shellQuoteArg(url)} --token ${shellQuoteArg(token)}${insecure}${getShellCustomCaInstallerFlag()}`
: `--uninstall --url ${shellQuoteArg(url)}${insecure}${getShellCustomCaInstallerFlag()}`;
const identityArgs = `${agentId ? ` --agent-id ${shellQuoteArg(agentId)}` : ''}${hostname ? ` --hostname ${shellQuoteArg(hostname)}` : ''}`;
return withPrivilegeEscalation(
`curl ${getCurlFlags()}${getShellCustomCaCurlFlag()} ${shellQuoteArg(`${url}/install.sh`)} | bash -s -- ${baseArgs}${identityArgs}`,
);
return buildUnixAgentInstallCommand({
baseUrl: url,
token,
insecure: installState.insecureMode(),
caCertPath: selectedCustomCaPath(),
extraArgs: [
'--uninstall',
...(agentId ? [`--agent-id ${shellQuoteArg(agentId)}`] : []),
...(hostname ? [`--hostname ${shellQuoteArg(hostname)}`] : []),
],
});
};
const getWindowsUninstallCommand = (row?: AgentUninstallIdentity) => {
@@ -163,24 +147,17 @@ export const useInfrastructureOperationsState = (
extraEnvAssignments: envAssignments,
});
}
let command = `curl ${getCurlFlags()}${getShellCustomCaCurlFlag()} ${shellQuoteArg(`${url}/install.sh`)} | bash -s -- --url ${shellQuoteArg(url)}`;
if (token) {
command += ` --token ${shellQuoteArg(token)}`;
}
if (row.installFlags.length > 0) {
command += ` ${row.installFlags.join(' ')}`;
}
if (urlRequiresInstallerInsecure(url)) {
command += getInsecureFlag(url);
}
command += getShellCustomCaInstallerFlag();
if (agentId) {
command += ` --agent-id ${shellQuoteArg(agentId)}`;
}
if (hostname) {
command += ` --hostname ${shellQuoteArg(hostname)}`;
}
return withPrivilegeEscalation(command);
return buildUnixAgentInstallCommand({
baseUrl: url,
token,
insecure: installState.insecureMode(),
caCertPath: selectedCustomCaPath(),
extraArgs: [
...row.installFlags,
...(agentId ? [`--agent-id ${shellQuoteArg(agentId)}`] : []),
...(hostname ? [`--hostname ${shellQuoteArg(hostname)}`] : []),
],
});
};
const getAgentConnectionUpgradeCommand = (
@@ -218,41 +195,23 @@ export const useInfrastructureOperationsState = (
});
}
if (replaceCredential) {
const extraArgs = ['--update', ...installFlags];
if (commandsEnabled || installState.enableCommands()) {
extraArgs.push('--enable-commands');
}
// Pin the existing identity the way the Windows path does. A repair
// reinstall without it can register a fresh suffixed agent identity
// for the same machine instead of converging on this one.
if (agentId) {
extraArgs.push(`--agent-id ${shellQuoteArg(agentId)}`);
}
if (hostname) {
extraArgs.push(`--hostname ${shellQuoteArg(hostname)}`);
}
return buildUnixAgentInstallCommand({
baseUrl: url,
token,
insecure: installState.insecureMode(),
caCertPath: selectedCustomCaPath(),
extraArgs,
});
}
let command = `curl ${getCurlFlags()}${getShellCustomCaCurlFlag()} ${shellQuoteArg(`${url}/install.sh`)} | bash -s -- --update --url ${shellQuoteArg(url)} --non-interactive`;
if (installFlags.length > 0) {
command += ` ${installFlags.join(' ')}`;
}
const extraArgs = ['--update', ...installFlags];
if (commandsEnabled || installState.enableCommands()) {
command += ' --enable-commands';
extraArgs.push('--enable-commands');
}
if (urlRequiresInstallerInsecure(url)) {
command += getInsecureFlag(url);
if (replaceCredential) {
// Credential replacement pins the existing identity; ordinary updates
// recover saved identity and credentials through the installer's route.
if (agentId) extraArgs.push(`--agent-id ${shellQuoteArg(agentId)}`);
if (hostname) extraArgs.push(`--hostname ${shellQuoteArg(hostname)}`);
}
command += getShellCustomCaInstallerFlag();
return withPrivilegeEscalation(command);
return buildUnixAgentInstallCommand({
baseUrl: url,
token: replaceCredential ? token : null,
insecure: installState.insecureMode(),
caCertPath: selectedCustomCaPath(),
extraArgs,
});
};
const getAgentConnectionUpgradeCommandRequiresToken = (
@@ -145,7 +145,7 @@ describe('systemSettings store', () => {
expect(configurationDoc).toContain('PULSE_TELEMETRY');
});
it('keeps existing mobile pairing security guidance aligned with retirement', () => {
it('keeps existing mobile pairing security without offering retired Relay', () => {
const securityDoc = readFileSync(path.join(repoRoot, 'SECURITY.md'), 'utf8');
const publicSecurityDoc = readFileSync(
path.join(frontendRoot, 'public', 'docs', 'SECURITY.md'),
@@ -153,14 +153,17 @@ describe('systemSettings store', () => {
);
for (const copy of [securityDoc, publicSecurityDoc]) {
expect(copy).toContain('Existing Mobile Pairings (Retirement)');
expect(copy).toContain('31 March 2027');
expect(copy).toContain('### Existing Mobile Pairings (Retirement)');
expect(copy).toContain('Pulse Mobile and Relay retire on **31 March 2027**');
expect(copy).toContain('Relay is no longer sold');
expect(copy).toContain('receive Pro features at their current price');
expect(copy).toContain('Relay connects the app, not the');
expect(copy).toContain('Paired-app access remains license-gated until retirement');
expect(copy).toMatch(/Relay connects the app, not the\s+web UI/);
expect(copy).not.toContain('Relay Security (Relay and Above)');
expect(copy).not.toContain('Relay Security (Pro)');
expect(copy).not.toContain('Relay functionality requires a Pro or Cloud license');
}
expect(publicSecurityDoc).toBe(securityDoc);
});
it('documents self-hosted AI provider transport and resource-policy redaction in the privacy doc', () => {
@@ -1,7 +1,16 @@
import { execFileSync } from 'node:child_process';
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { execFileSync, spawnSync } from 'node:child_process';
import {
chmodSync,
existsSync,
mkdirSync,
symlinkSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import {
buildUnixAgentInstallCommand,
@@ -20,8 +29,8 @@ describe('agentInstallCommand', () => {
expect(command).toContain("--url 'http://pulse.example:7655'");
expect(command).toContain('token_dir=$(mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX)');
expect(command).toContain('token_dir=$(sudo mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX)');
expect(command).toContain('printf %s \'token-123\' | sudo tee "$token_file" >/dev/null');
expect(command).toContain('sudo bash -c');
expect(command).not.toContain('token-123');
expect(command).toContain('--token-file "$token_file"');
expect(command).toContain('--insecure');
});
@@ -36,8 +45,8 @@ describe('agentInstallCommand', () => {
"curl -fsSL 'https://pulse.example/base path/agent'\"'\"'s/install.sh' -o \"$install_script\"",
);
expect(command).toContain("--url 'https://pulse.example/base path/agent'\"'\"'s'");
expect(command).toContain("printf %s 'tok'\"'\"'en' > \"$token_file\"");
expect(command).toContain("printf %s 'tok'\"'\"'en' | sudo tee \"$token_file\" >/dev/null");
expect(command).not.toContain("tok'en");
expect(command).not.toContain("tok'\"'\"'en");
expect(command).toContain('--token-file "$token_file"');
expect(command).not.toContain("--token 'tok");
});
@@ -49,103 +58,20 @@ describe('agentInstallCommand', () => {
});
const preflightIndex = command.indexOf('--preflight-only');
const sudoIndex = command.indexOf('sudo bash "$install_script"');
const sudoIndex = command.indexOf('sudo bash -c');
expect(command).toContain('tmp_dir=$(mktemp -d)');
expect(command).toContain('trap cleanup EXIT HUP INT TERM');
expect(command).toContain('bootstrap_dir=$(mktemp -d /tmp/pulse-bootstrap.XXXXXX)');
expect(command).toContain('trap cleanup EXIT');
expect(command).toContain('bash "$install_script" --url');
expect(command).toContain('--output json');
expect(command).toContain('--non-interactive');
expect(preflightIndex).toBeGreaterThan(-1);
expect(sudoIndex).toBeGreaterThan(preflightIndex);
expect(
command.indexOf('token_dir=$(sudo mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX)'),
command.indexOf('token_dir=$(mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX)'),
).toBeGreaterThan(preflightIndex);
});
it('executes root and sudo token bootstraps from trusted private directories', () => {
if (process.platform === 'win32') return;
const fixtureDir = mkdtempSync(join(tmpdir(), 'pulse-install-command-'));
try {
const binDir = join(fixtureDir, 'bin');
const installer = join(fixtureDir, 'installer.sh');
const capture = join(fixtureDir, 'captured-token');
execFileSync('mkdir', ['-p', binDir]);
writeFileSync(
installer,
`#!/usr/bin/env bash
set -e
token_file=""
while [ "$#" -gt 0 ]; do
case "$1" in
--preflight-only) exit 0 ;;
--token-file) token_file="$2"; shift 2 ;;
*) shift ;;
esac
done
[ -n "$token_file" ]
[ "$(stat -c %a "$token_file" 2>/dev/null || stat -f %Lp "$token_file")" = "600" ]
parent_dir=$(dirname "$token_file")
[ "$(stat -c %a "$parent_dir" 2>/dev/null || stat -f %Lp "$parent_dir")" = "700" ]
[ "$(stat -c %u "$token_file" 2>/dev/null || stat -f %u "$token_file")" = "$(stat -c %u "$parent_dir" 2>/dev/null || stat -f %u "$parent_dir")" ]
cat "$token_file" > "$FAKE_CAPTURE"
`,
);
chmodSync(installer, 0o700);
writeFileSync(
join(binDir, 'curl'),
`#!/bin/sh
output=""
while [ "$#" -gt 0 ]; do
case "$1" in
-o) output="$2"; shift 2 ;;
*) shift ;;
esac
done
cp "$FAKE_INSTALLER" "$output"
`,
);
writeFileSync(
join(binDir, 'sudo'),
`#!/bin/sh
exec "$@"
`,
);
writeFileSync(
join(binDir, 'id'),
`#!/bin/sh
if [ "$1" = "-u" ]; then
printf '%s\n' "$FAKE_ID_UID"
else
exec /usr/bin/id "$@"
fi
`,
);
for (const name of ['curl', 'sudo', 'id']) chmodSync(join(binDir, name), 0o700);
const command = buildUnixAgentInstallCommand({
baseUrl: 'https://pulse.example',
token: 'token-123',
});
for (const fakeUID of ['0', '1000']) {
rmSync(capture, { force: true });
execFileSync('bash', ['-c', command], {
env: {
...process.env,
PATH: `${binDir}:${process.env.PATH || ''}`,
FAKE_CAPTURE: capture,
FAKE_ID_UID: fakeUID,
FAKE_INSTALLER: installer,
},
});
expect(readFileSync(capture, 'utf8')).toBe('token-123');
}
} finally {
rmSync(fixtureDir, { recursive: true, force: true });
}
});
it('normalizes trailing slashes before building installer transport', () => {
expect(normalizeInstallerBaseUrl('https://pulse.example/base///')).toBe(
'https://pulse.example/base',
@@ -422,100 +348,392 @@ describe('single-line Unix install commands', () => {
it.each([
{ baseUrl: 'https://pulse.invalid/a\nb' },
{ baseUrl: 'https://pulse.invalid', token: 'a\rb' },
{ baseUrl: 'https://pulse.invalid', tokenFilePath: '/tmp/a\rb' },
{ baseUrl: 'https://pulse.invalid', caCertPath: '/tmp/a\nb' },
{ baseUrl: 'https://pulse.invalid', extraArgs: ['--a\n--b'] },
])('rejects embedded line breaks without silently changing values: %j', (options) => {
expect(() => buildUnixAgentInstallCommand(options)).toThrow('must not contain line breaks');
});
it.each(['sh', 'bash'])(
'preserves execution and failure ordering under %s after paste',
(shell) => {
const dir = mkdtempSync(join(tmpdir(), 'pulse-single-line-'));
const trace = join(dir, 'trace');
const tokenPath = join(dir, 'token-path');
const scriptPath = join(dir, 'script-path');
const token = "tok'en;$(false)`false`\\value";
const installer = join(dir, 'fixture-installer');
const fake = (name: string, body: string) => {
writeFileSync(join(dir, name), `#!/bin/sh\n${body}\n`);
chmodSync(join(dir, name), 0o700);
};
try {
fake('id', 'echo "$FAKE_UID"');
fake('sudo', 'echo sudo >> "$TRACE"; exec "$@"');
fake(
'curl',
`echo fetch >> "$TRACE"
it.each(['sh', 'bash'])('runs tokenless commands under %s', (shell) => {
const fixture = makeUnixFixture();
try {
for (const uid of ['0', '1000']) {
const command = buildUnixAgentInstallCommand({ baseUrl: 'https://pulse.invalid' });
const result = spawnSync(shell, ['-c', command], {
env: fixture.env(uid),
encoding: 'utf8',
});
expect(result.status, result.stderr).toBe(0);
expect(readFileSync(fixture.trace, 'utf8')).toContain('install');
assertUnixCleanup(fixture.dir);
}
} finally {
fixture.close();
}
});
});
const unixPTYRunner = `
import errno, fcntl, json, os, pty, select, signal, subprocess, sys, termios, time
p = json.load(sys.stdin)
master, slave = pty.openpty()
tty_watch = os.dup(slave)
initial_tty = termios.tcgetattr(tty_watch)
def session():
os.setsid()
fcntl.ioctl(0, termios.TIOCSCTTY, 0)
history = p.get("history")
env = os.environ.copy()
if history:
env.update(HISTFILE=history, HISTSIZE="100", HISTFILESIZE="100", HISTCONTROL="", PS1="PULSE_TEST$ ")
program = ["bash", "--noprofile", "--norc", "-i"] if history else ["sh", "-c", p["command"]]
child = subprocess.Popen(program, stdin=slave, stdout=slave, stderr=slave, preexec_fn=session, env=env)
if history: os.write(master, (p["command"] + "\\n").encode())
os.close(slave)
output = b""
sent = False
deadline = time.monotonic() + 15
while time.monotonic() < deadline:
ready, _, _ = select.select([master], [], [], .1)
if ready:
try: chunk = os.read(master, 65536)
except OSError as e:
if e.errno == errno.EIO: break
raise
if not chunk: break
output += chunk
if output.endswith(b"(paste at this prompt, not in the command): ") and not sent:
if p.get("read_signal"):
time.sleep(.1) # read has displayed its own prompt and is waiting
with open(env["READER_PID"]) as f: reader = int(f.read())
os.kill(reader, getattr(signal, "SIG" + p["read_signal"]))
elif p.get("slow"):
for piece in [p["input"][:4], p["input"][4:], "\\n"]:
os.write(master, piece.encode())
time.sleep(.05)
else:
os.write(master, (p["input"] + "\\n").encode())
sent = True
if history: os.write(master, b"exit\\n")
if child.poll() is not None and not ready: break
else:
os.killpg(child.pid, signal.SIGKILL)
output += b"\\nPTY fixture deadline expired\\n"
code = child.wait()
final_tty = termios.tcgetattr(tty_watch)
mode_mask = termios.ECHO | termios.ICANON
if (initial_tty[3] & mode_mask) != (final_tty[3] & mode_mask):
output += b"\\nBootstrap did not restore terminal echo/canonical mode\\n"
code = 1
os.close(tty_watch)
os.close(master)
sys.stdout.buffer.write(output)
sys.exit(code if code >= 0 else 128 - code)
`;
const makeUnixFixture = () => {
const dir = mkdtempSync(join(tmpdir(), 'pulse-private-unix-'));
chmodSync(dir, 0o700);
const trace = join(dir, 'trace');
writeFileSync(trace, '');
const bin = join(dir, 'bin');
mkdirSync(bin, { mode: 0o700 });
const fake = (name: string, body: string) => {
writeFileSync(join(bin, name), `#!/bin/sh\n${body}\n`, { mode: 0o700 });
};
fake(
'stty',
'if [ \"$1\" = -echo ]; then echo \"$PPID\" > \"$READER_PID\"; fi; exec /bin/stty \"$@\"',
);
fake('id', 'if [ "$1" = -u ]; then echo "$FAKE_UID"; else exec /usr/bin/id "$@"; fi');
fake(
'sudo',
'printf "%s\\n" "$@" >> "$ARGV"; [ "$FAIL_AT" != sudo ] || exit 1; [ "$1" != -v ] || exit 0; exec "$@"',
);
fake(
'mktemp',
'path=$(/usr/bin/mktemp "$@") || exit 1; printf "%s\\n" "$path" >> "$DIRS"; printf "%s\\n" "$path"',
);
fake(
'curl',
`printf '%s\\n' "$@" >> "$ARGV"
while [ "$#" -gt 0 ]; do
if [ "$1" = -o ]; then output="$2"; shift; fi
shift
done
printf %s "$output" > "$SCRIPT_PATH"
[ "$FAIL_AT" != fetch ] || exit 22
cp "$INSTALLER" "$output"`,
);
writeFileSync(
installer,
`#!/bin/sh
set -e
case " $* " in
*' --preflight-only '*) echo preflight >> "$TRACE"; [ "$FAIL_AT" != preflight ]; exit $? ;;
esac
cp "$INSTALLER" "$output"
echo fetch >> "$TRACE"
[ "$FAIL_AT" != fetch ] || exit 22`,
);
writeFileSync(
join(dir, 'installer'),
`#!/bin/bash
set -eu
printf '%s\\n' "$@" >> "$ARGV"
case " $* " in *' --preflight-only '*) echo preflight >> "$TRACE"; [ "$FAIL_AT" != preflight ]; exit $?;; esac
echo install >> "$TRACE"
printf '%s:%s' "\${pulse_token:-}" "\${PULSE_TOKEN:-}" > "$SECRET_ENV"
token_file=''
while [ "$#" -gt 0 ]; do
if [ "$1" = --token-file ]; then token_file="$2"; shift; fi
shift
done
printf %s "$token_file" > "$TOKEN_PATH"
[ "$(stat -c %a "$token_file")" = 600 ]
[ "$(stat -c %a "$(dirname "$token_file")")" = 700 ]
[ "$(cat "$token_file")" = "$EXPECTED_TOKEN" ]
if [ -n "$token_file" ]; then
[ "$(stat -c %a "$token_file")" = 600 ]
[ "$(stat -c %a "$(dirname "$token_file")")" = 700 ]
[ "$(stat -c %u "$token_file")" = "$(stat -c %u "$(dirname "$token_file")")" ]
cat "$token_file" > "$CAPTURE"
fi
if [ "$FAIL_AT" = TERM ] || [ "$FAIL_AT" = HUP ]; then kill -s "$FAIL_AT" "$PPID"; fi
[ "$FAIL_AT" != install ]
`,
);
for (const uid of ['0', '1000']) {
for (const failAt of ['', 'fetch', 'preflight', 'install']) {
writeFileSync(trace, '');
rmSync(tokenPath, { force: true });
const command = buildUnixAgentInstallCommand({
baseUrl: 'https://pulse.invalid',
token,
});
const input = document.createElement('input');
input.value = command;
const run = () =>
execFileSync(shell, ['-c', input.value], {
env: {
...process.env,
PATH: `${dir}:${process.env.PATH}`,
FAKE_UID: uid,
FAIL_AT: failAt,
TRACE: trace,
TOKEN_PATH: tokenPath,
SCRIPT_PATH: scriptPath,
INSTALLER: installer,
EXPECTED_TOKEN: token,
},
});
if (failAt) expect(run).toThrow();
else expect(run).not.toThrow();
const events = readFileSync(trace, 'utf8').trim().split('\n');
expect(events[0]).toBe('fetch');
expect(existsSync(dirname(readFileSync(scriptPath, 'utf8')))).toBe(false);
if (failAt === 'fetch' || failAt === 'preflight') {
expect(events).toEqual(failAt === 'fetch' ? ['fetch'] : ['fetch', 'preflight']);
expect(existsSync(tokenPath)).toBe(false);
} else {
expect(events[1]).toBe('preflight');
expect(events).toContain('install');
expect(existsSync(dirname(readFileSync(tokenPath, 'utf8')))).toBe(false);
}
}
}
{ mode: 0o700 },
);
return {
dir,
trace,
env: (uid = '0', failure = '') => ({
...process.env,
PATH: `${bin}:${process.env.PATH}`,
FAKE_UID: uid,
FAIL_AT: failure,
TRACE: trace,
ARGV: join(dir, 'argv'),
DIRS: join(dir, 'dirs'),
INSTALLER: join(dir, 'installer'),
CAPTURE: join(dir, 'capture'),
SECRET_ENV: join(dir, 'secret-env'),
READER_PID: join(dir, 'reader-pid'),
PULSE_TOKEN: '',
}),
close: () => rmSync(dir, { recursive: true, force: true }),
};
};
const assertUnixCleanup = (dir: string) => {
if (existsSync(join(dir, 'dirs')))
for (const path of readFileSync(join(dir, 'dirs'), 'utf8').trim().split('\n'))
expect(existsSync(path), `temporary directory left behind: ${path}`).toBe(false);
};
const runPrivateUnix = (
command: string,
input: string,
env: NodeJS.ProcessEnv,
history?: string,
readSignal?: string,
slow = false,
) =>
spawnSync('python3', ['-c', unixPTYRunner], {
input: JSON.stringify({ command, input, history, read_signal: readSignal, slow }),
env,
encoding: 'utf8',
timeout: 20000,
});
describe('credential-free Unix lifecycle execution', () => {
it.each(['0', '1000'])(
'keeps private input out of root/sudo argv, environment, output and history (uid=%s)',
(uid) => {
const fixture = makeUnixFixture();
const marker = join(fixture.dir, 'must-not-execute');
const token = `synthetic-' ; $(touch ${marker}) \`false\` \\value`;
try {
const command = buildUnixAgentInstallCommand({
baseUrl: "https://pulse.invalid/agent's path",
token,
extraArgs: ['--enable-docker', "--agent-id 'agent-42'", "--hostname 'node.example'"],
});
const history = join(fixture.dir, 'history');
const result = runPrivateUnix(command, token, fixture.env(uid), history);
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(readFileSync(join(fixture.dir, 'capture'), 'utf8')).toBe(token);
expect(existsSync(marker)).toBe(false);
expect(readFileSync(join(fixture.dir, 'secret-env'), 'utf8')).toBe(':');
const argv = readFileSync(join(fixture.dir, 'argv'), 'utf8');
expect(argv).toContain("--url\nhttps://pulse.invalid/agent's path\n");
expect(argv).toContain('--agent-id\nagent-42\n');
expect(argv).toContain('--hostname\nnode.example\n');
const hist = readFileSync(history, 'utf8');
expect(hist).toContain(command);
for (const text of [command, argv, hist, result.stdout, result.stderr])
expect(text).not.toContain(token);
assertUnixCleanup(fixture.dir);
} finally {
rmSync(dir, { recursive: true, force: true });
fixture.close();
}
},
);
it('preserves every character of the maximum-size accepted token', () => {
const fixture = makeUnixFixture();
try {
const input = 'a'.repeat(4096);
const result = runPrivateUnix(
buildUnixAgentInstallCommand({ baseUrl: 'https://pulse.invalid', token: 'present' }),
input,
fixture.env(),
);
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(readFileSync(join(fixture.dir, 'capture'), 'utf8')).toBe(input);
assertUnixCleanup(fixture.dir);
} finally {
fixture.close();
}
});
it('drains oversized input without echoing it or returning it to interactive history', () => {
const fixture = makeUnixFixture();
try {
const input = 'x'.repeat(10000) + '-OVERFLOW_HISTORY_MARKER';
const history = join(fixture.dir, 'history');
const result = runPrivateUnix(
buildUnixAgentInstallCommand({ baseUrl: 'https://pulse.invalid', token: 'present' }),
input,
fixture.env(),
history,
);
expect(result.status, result.stdout + result.stderr).not.toBe(0);
expect(result.stdout).toContain('at most 4096');
expect(existsSync(join(fixture.dir, 'capture'))).toBe(false);
for (const text of [result.stdout, readFileSync(history, 'utf8')]) {
expect(text).not.toContain('OVERFLOW_HISTORY_MARKER');
expect(text).not.toContain('x'.repeat(512));
}
assertUnixCleanup(fixture.dir);
} finally {
fixture.close();
}
});
it.each([
'fetch',
'preflight',
'sudo',
'install',
'HUP',
'TERM',
'blank',
'interrupt',
'no-terminal',
])('stops and cleans up on %s', (failure) => {
const fixture = makeUnixFixture();
try {
const command = buildUnixAgentInstallCommand({
baseUrl: 'https://pulse.invalid',
token: 'present',
});
const env = fixture.env(failure === 'sudo' ? '1000' : '0', failure);
const result =
failure === 'no-terminal'
? spawnSync('sh', ['-c', command], { env, encoding: 'utf8' })
: runPrivateUnix(
command,
failure === 'blank' ? '' : failure === 'interrupt' ? '\x03' : 'test-input-secret',
env,
);
expect(result.status, result.stdout + result.stderr).not.toBe(0);
if (!['install', 'HUP', 'TERM'].includes(failure))
expect(existsSync(join(fixture.dir, 'capture'))).toBe(false);
expect(result.stdout).not.toContain('test-input-secret');
expect(result.stdout).not.toContain('did not restore');
assertUnixCleanup(fixture.dir);
} finally {
fixture.close();
}
});
it.each(['HUP', 'TERM', 'INT'])(
'restores the terminal when %s interrupts private input',
(readSignal) => {
const fixture = makeUnixFixture();
try {
const command = buildUnixAgentInstallCommand({
baseUrl: 'https://pulse.invalid',
token: 'present',
});
const result = runPrivateUnix(command, '', fixture.env(), undefined, readSignal);
expect(result.status, result.stdout + result.stderr).not.toBe(0);
expect(result.stdout).not.toContain('did not restore');
expect(result.stdout).not.toContain('deadline expired');
expect(existsSync(join(fixture.dir, 'capture'))).toBe(false);
assertUnixCleanup(fixture.dir);
} finally {
fixture.close();
}
},
);
it('accepts deliberate slow token input without a timeout or truncation', () => {
const fixture = makeUnixFixture();
try {
const command = buildUnixAgentInstallCommand({
baseUrl: 'https://pulse.invalid',
token: 'present',
});
const input = 'deliberate-slow-input';
const result = runPrivateUnix(command, input, fixture.env(), undefined, undefined, true);
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(readFileSync(join(fixture.dir, 'capture'), 'utf8')).toBe(input);
expect(result.stdout).not.toContain(input);
assertUnixCleanup(fixture.dir);
} finally {
fixture.close();
}
});
it('supports non-terminal private-file input without deleting the operator file', () => {
const fixture = makeUnixFixture();
try {
const file = join(fixture.dir, "operator's token");
writeFileSync(file, 'private-file-token', { mode: 0o600 });
const command = buildUnixAgentInstallCommand({
baseUrl: 'https://pulse.invalid',
tokenFilePath: file,
});
expect(command).not.toContain('read -r');
expect(command).not.toContain('private-file-token');
const result = spawnSync('sh', ['-c', command], { env: fixture.env(), encoding: 'utf8' });
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(readFileSync(join(fixture.dir, 'capture'), 'utf8')).toBe('private-file-token');
expect(readFileSync(file, 'utf8')).toBe('private-file-token');
assertUnixCleanup(fixture.dir);
} finally {
fixture.close();
}
});
it.each(['missing', 'symlink', 'fifo', 'directory', 'public-file', 'public-parent'])(
'rejects unsafe private-file input: %s',
(kind) => {
const fixture = makeUnixFixture();
try {
const file = join(fixture.dir, 'provided-token');
const real = join(fixture.dir, 'actual-token');
writeFileSync(real, 'private-file-token', { mode: 0o600 });
if (kind === 'symlink') symlinkSync(real, file);
if (kind === 'fifo') execFileSync('mkfifo', [file]);
if (kind === 'directory') mkdirSync(file);
if (kind === 'public-file' || kind === 'public-parent') {
writeFileSync(file, 'private-file-token', {
mode: kind === 'public-file' ? 0o644 : 0o600,
});
if (kind === 'public-parent') chmodSync(fixture.dir, 0o755);
}
const command = buildUnixAgentInstallCommand({
baseUrl: 'https://pulse.invalid',
tokenFilePath: file,
});
const result = spawnSync('sh', ['-c', command], {
env: fixture.env(),
encoding: 'utf8',
timeout: 3000,
});
expect(result.status, result.stdout + result.stderr).not.toBe(0);
expect(existsSync(join(fixture.dir, 'capture'))).toBe(false);
assertUnixCleanup(fixture.dir);
} finally {
fixture.close();
}
},
);
@@ -42,7 +42,7 @@ describe('quickstart copy contract', () => {
}
});
it('keeps existing mobile security guidance aligned with retirement', () => {
it('keeps retired Relay guidance about existing pairings, not a current offer', () => {
const security = readRepoFile('SECURITY.md');
const publicSecurity = readRepoFile('frontend-modern/public/docs/SECURITY.md');
const screenshots = readRepoFile('docs/SCREENSHOTS.md');
@@ -54,13 +54,16 @@ describe('quickstart copy contract', () => {
}
for (const copy of [security, publicSecurity]) {
expect(copy).toContain('Existing Mobile Pairings (Retirement)');
expect(copy).toContain('31 March 2027');
expect(copy).toContain('### Existing Mobile Pairings (Retirement)');
expect(copy).toContain('Pulse Mobile and Relay retire on **31 March 2027**');
expect(copy).toContain('Relay is no longer sold');
expect(copy).toContain('receive Pro features at their current price');
expect(copy).toContain('Relay connects the app, not the');
expect(copy).toContain('Paired-app access remains license-gated until retirement');
expect(copy).toMatch(/Relay connects the app, not the\s+web UI/);
expect(copy).not.toContain('Relay Security (Relay and Above)');
expect(copy).not.toContain('Relay, Pro, legacy Pro+, or Cloud license');
}
expect(publicSecurity).toBe(security);
});
it('keeps public AI docs aligned with model-owned Patrol and Assistant reasoning', () => {
@@ -0,0 +1,8 @@
// Decode exactly the single-quoted Bash -c argument, including POSIX apostrophe
// escapes. Consumers can assert its actual source without depending on the
// extra outer quoting layer. Executable PTY tests separately pin real argv.
export const unixBootstrapTestSource = (command: string): string => {
const child = /bash -c ('(?:[^']|'"'"')*') pulse-bootstrap "\$install_script";/.exec(command);
if (!child) throw new Error('Expected a single-quoted private Unix bootstrap child');
return child[1].slice(1, -1).replaceAll(`'"'"'`, "'");
};
@@ -122,6 +122,8 @@ type BuildUnixAgentInstallCommandOptions = {
insecure?: boolean;
caCertPath?: string | null;
extraArgs?: string[];
// Existing private file for non-terminal hosts; never contains credential bytes.
tokenFilePath?: string | null;
};
type BuildWindowsAgentInstallCommandOptions = {
@@ -138,20 +140,25 @@ export const buildUnixAgentInstallCommand = ({
insecure = false,
caCertPath,
extraArgs = [],
tokenFilePath,
}: BuildUnixAgentInstallCommandOptions) => {
const normalizedBaseUrl = normalizeInstallerBaseUrl(baseUrl);
if (!normalizedBaseUrl.trim()) {
throw new Error('Pulse install endpoint URL is required.');
}
const normalizedCaCertPath = (caCertPath || '').trim();
const normalizedToken = (token || '').trim();
const needsToken = Boolean((token || '').trim() || tokenFilePath?.trim());
const normalizedTokenFilePath = (tokenFilePath || '').trim();
if (normalizedTokenFilePath && !normalizedTokenFilePath.startsWith('/')) {
throw new Error('Pulse token file path must be absolute.');
}
const normalizedExtraArgs = extraArgs.map((arg) => arg.trim()).filter((arg) => arg.length > 0);
const installRequiresInsecure = insecure || normalizedBaseUrl.startsWith('http://');
const curlFlags = insecure ? '-kfsSL' : '-fsSL';
// Build shell grammar explicitly: command fields such as pfSense's remove
// pasted newlines. Never flatten a multiline script (or quoted user data).
if (
[normalizedBaseUrl, normalizedToken, normalizedCaCertPath, ...normalizedExtraArgs].some(
[normalizedBaseUrl, normalizedTokenFilePath, normalizedCaCertPath, ...normalizedExtraArgs].some(
(value) => /[\r\n]/.test(value),
)
) {
@@ -167,57 +174,57 @@ export const buildUnixAgentInstallCommand = ({
].join(' ');
const installArgs = [
`--url ${shellQuoteArg(normalizedBaseUrl)}`,
...(normalizedToken ? ['--token-file "$token_file"'] : []),
...(needsToken ? ['--token-file "$token_file"'] : []),
...normalizedExtraArgs,
'--non-interactive',
].join(' ');
const rootTokenSetup = normalizedToken
? [
'token_dir=$(mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX);',
'token_file="$token_dir/token";',
'umask 077;',
`printf %s ${shellQuoteArg(normalizedToken)} > "$token_file";`,
]
: [];
const sudoTokenSetup = normalizedToken
? [
'token_dir=$(sudo mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX);',
'token_file="$token_dir/token";',
`printf %s ${shellQuoteArg(normalizedToken)} | sudo tee "$token_file" >/dev/null;`,
'sudo chmod 0600 "$token_file";',
]
: [];
// Keep this reader aligned with configapi/privateBootstrapCommand. Bash owns
// the silent bounded read in the privileged child, not the caller's shell or
// sudo argv. A canonical line read silently truncates long pasted tokens.
const privileged = ['set +xv;', 'set -eu;', 'umask 077;'];
if (normalizedTokenFilePath) {
privileged.push(
`token_file=${shellQuoteArg(normalizedTokenFilePath)};`,
'token_parent=${token_file%/*};',
'if [ ! -f "$token_file" ] || [ -L "$token_file" ] || [ ! -O "$token_file" ] || [ ! -d "$token_parent" ] || [ -L "$token_parent" ] || [ ! -O "$token_parent" ] || [ "$(stat -c %a "$token_file" 2>/dev/null || stat -f %Lp "$token_file")" != 600 ] || [ "$(stat -c %a "$token_parent" 2>/dev/null || stat -f %Lp "$token_parent")" != 700 ]; then echo "Use a root-owned 0600 regular token file in a root-owned 0700 directory. No installation was attempted." >&2; exit 1; fi;',
);
} else if (needsToken) {
privileged.push(
'token_dir=$(mktemp -d /tmp/pulse-agent-bootstrap.XXXXXX);',
'token_file="$token_dir/token";',
'unset pulse_token pulse_discard tty_state;',
'cleanup() { unset pulse_token pulse_discard; if [ -n "${tty_state:-}" ]; then stty "$tty_state" </dev/tty 2>/dev/null || :; fi; rm -f -- "$token_file"; rmdir -- "$token_dir"; };',
"trap cleanup EXIT; trap 'exit 129' HUP; trap 'exit 130' INT; trap 'exit 143' TERM;",
'if ! tty_state=$(stty -g </dev/tty); then echo "A terminal is required to enter the token. No installation was attempted." >&2; exit 1; fi;',
'stty -echo </dev/tty;',
`if ! IFS= read -r -s -p ${shellQuoteArg('Pulse agent token (paste at this prompt, not in the command): ')} -n 4097 pulse_token </dev/tty; then echo "Token input was interrupted. No installation was attempted." >&2; exit 1; fi;`,
'if [ "${#pulse_token}" -gt 4096 ]; then while IFS= read -r -s -n 4097 pulse_discard </dev/tty && [ "${#pulse_discard}" -eq 4097 ]; do :; done; unset pulse_discard; fi;',
'stty "$tty_state" </dev/tty; unset tty_state;',
"printf '\\n' >/dev/tty;",
'if [ -z "$pulse_token" ] || [ "${#pulse_token}" -gt 4096 ]; then echo "A non-empty token of at most 4096 characters is required." >&2; exit 1; fi;',
'printf %s "$pulse_token" > "$token_file"; unset pulse_token;',
);
}
privileged.push(`bash "$1" ${installArgs}${caCertArg}${insecureArg};`);
const child = `bash -c ${shellQuoteArg(privileged.join(' '))} pulse-bootstrap "$install_script";`;
return [
'(',
'set -e;',
'tmp_dir=$(mktemp -d);',
'token_dir="";',
'install_script="$tmp_dir/install.sh";',
'cleanup() {',
'rm -rf -- "$tmp_dir";',
'if [ -n "${token_dir:-}" ]; then',
'if [ "$(id -u)" -eq 0 ]; then',
'rm -rf -- "$token_dir";',
'elif command -v sudo >/dev/null 2>&1; then',
'sudo rm -rf -- "$token_dir" >/dev/null 2>&1 || true;',
'fi;',
'fi;',
'};',
'trap cleanup EXIT HUP INT TERM;',
'set +xv;',
'set -eu;',
'umask 077;',
'if [ "$(id -u)" -eq 0 ]; then :; elif command -v sudo >/dev/null 2>&1; then sudo -v; else echo "Root privileges required. Run as root (su -) and retry." >&2; exit 1; fi;',
'bootstrap_dir=$(mktemp -d /tmp/pulse-bootstrap.XXXXXX);',
'install_script="$bootstrap_dir/install.sh";',
'cleanup() { rm -f -- "$install_script"; rmdir -- "$bootstrap_dir"; };',
"trap cleanup EXIT; trap 'exit 129' HUP; trap 'exit 130' INT; trap 'exit 143' TERM;",
`curl ${curlFlags}${caCertArg} ${shellQuoteArg(`${normalizedBaseUrl}/install.sh`)} -o "$install_script";`,
'chmod +x "$install_script";',
`bash "$install_script" ${preflightArgs}${caCertArg}${insecureArg};`,
'if [ "$(id -u)" -eq 0 ]; then',
...rootTokenSetup,
`bash "$install_script" ${installArgs}${caCertArg}${insecureArg};`,
'elif command -v sudo >/dev/null 2>&1; then',
...sudoTokenSetup,
`sudo bash "$install_script" ${installArgs}${caCertArg}${insecureArg};`,
'else',
'echo "Root privileges required. Run as root (su -) and retry." >&2;',
'exit 1;',
'fi;',
// Detachment must not require a downloadable new agent binary.
...(!normalizedExtraArgs.includes('--uninstall')
? [`bash "$install_script" ${preflightArgs}${caCertArg}${insecureArg};`]
: []),
`if [ "$(id -u)" -eq 0 ]; then ${child} else sudo ${child} fi;`,
')',
].join(' ');
};