- execute-command/upload/download accept host/port/username/password inline to open an ephemeral connection for a single call - add redactSecret/redactSecrets to mask credentials in logs and errors - logger log()/handleError() accept optional secrets[] for redaction - add SSH_CONFIG_MISSING error code and --password-from-env CLI flag - dynamic-mode startup: server runs with no static config
63 lines
2.3 KiB
JavaScript
63 lines
2.3 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert';
|
|
import { redactSecret, redactSecrets } from '../build/utils/redact.js';
|
|
import { SSHConnectionManager } from '../build/services/ssh-connection-manager.js';
|
|
|
|
test('redactSecret masks long secrets everywhere', () => {
|
|
const text = 'login with hunter2hunter2 then done';
|
|
assert.strictEqual(redactSecret(text, 'hunter2'), 'login with [REDACTED][REDACTED] then done');
|
|
});
|
|
|
|
test('redactSecret masks long secret even mid-word', () => {
|
|
assert.strictEqual(
|
|
redactSecret('password: secretvalue and secretvalueagain', 'secretvalue'),
|
|
'password: [REDACTED] and [REDACTED]again',
|
|
);
|
|
});
|
|
|
|
test('redactSecret masks short secrets only as whole tokens', () => {
|
|
// 2-3 char secrets are only replaced when they stand alone
|
|
const text = 'the cat sat on a mat';
|
|
assert.strictEqual(redactSecret(text, 'cat'), 'the [REDACTED] sat on a mat');
|
|
});
|
|
|
|
test('redactSecret ignores short secret inside a word', () => {
|
|
// "cat" inside "catastrophe" must not be destroyed
|
|
assert.strictEqual(redactSecret('a catastrophe', 'cat'), 'a catastrophe');
|
|
});
|
|
|
|
test('redactSecret ignores empty and 1-char secrets', () => {
|
|
assert.strictEqual(redactSecret('abc', ''), 'abc');
|
|
assert.strictEqual(redactSecret('abc', 'a'), 'abc');
|
|
assert.strictEqual(redactSecret('abc', undefined), 'abc');
|
|
assert.strictEqual(redactSecret('abc', null), 'abc');
|
|
assert.strictEqual(redactSecret('', 'secret'), '');
|
|
});
|
|
|
|
test('redactSecret escapes regex special characters', () => {
|
|
const text = 'tokens like a.b and aXb';
|
|
assert.strictEqual(redactSecret(text, 'a.b'), 'tokens like [REDACTED] and aXb');
|
|
});
|
|
|
|
test('redactSecrets redacts many secrets at once', () => {
|
|
const text = 'user=alice pass=topsecret key=pLskey-end';
|
|
assert.strictEqual(
|
|
redactSecrets(text, ['topsecret', 'pLskey']),
|
|
'user=alice pass=[REDACTED] key=[REDACTED]-end',
|
|
);
|
|
});
|
|
|
|
test('redactSecret returns original when no match', () => {
|
|
const text = 'nothing sensitive here';
|
|
assert.strictEqual(redactSecret(text, 'nope'), text);
|
|
});
|
|
|
|
test('dynamic mode config requires host and username', async () => {
|
|
const manager = SSHConnectionManager.getInstance();
|
|
|
|
await assert.rejects(
|
|
() => manager.executeCommandDynamic({ username: 'root' }, 'id'),
|
|
(err) => err?.code === 'SSH_CONFIG_MISSING',
|
|
);
|
|
});
|