Files
Max Shcheglov b889154bb3 feat: add dynamic per-call SSH connections and secret redaction
- 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
2026-09-01 10:54:20 +07:00

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',
);
});