Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions server/src/bitcoin-socket-exists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { execFileSync } from 'node:child_process';
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { bitcoinSocketExistsScript } from './bitcoin-socket-exists.js';

test('bitcoinSocketExistsScript must not execute shell commands embedded in the socket path', () => {
const tmp = mkdtempSync(path.join(tmpdir(), 'sock-inj-'));
const marker = path.join(tmp, 'pwned');

const maliciousPath = '/tmp/$(touch ' + marker + ')/node.sock';

try {
execFileSync(process.execPath, ['-e', bitcoinSocketExistsScript, maliciousPath], {
stdio: 'ignore',
});
} catch {
// A non-zero exit (socket absent) is expected and irrelevant here.
}

const injected = existsSync(marker);
rmSync(tmp, { recursive: true, force: true });

assert.equal(
injected,
false,
'socket path was interpreted as a shell command (command injection via execSync)',
);
});

test('bitcoinSocketExistsScript exits 0 when the socket exists and non-zero otherwise', () => {
const tmp = mkdtempSync(path.join(tmpdir(), 'sock-exists-'));
try {
const socketPath = path.join(tmp, 'node.sock');
assert.throws(
() =>
execFileSync(process.execPath, ['-e', bitcoinSocketExistsScript, socketPath], {
stdio: 'ignore',
}),
/Command failed/,
'missing socket path should exit non-zero',
);
writeFileSync(socketPath, '');
execFileSync(process.execPath, ['-e', bitcoinSocketExistsScript, socketPath], {
stdio: 'ignore',
});
} finally {
rmSync(tmp, { recursive: true, force: true });
}
});
Comment on lines +33 to +52

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clanker suggestion:

This test name says it covers both success and failure, but it only asserts the missing-path failure case. Could we also create the path and assert the script exits successfully? That would catch regressions where the probe always returns non-zero.
Suggested change
test('bitcoinSocketExistsScript exits 0 when the socket exists and non-zero otherwise', () => {
const tmp = mkdtempSync(path.join(tmpdir(), 'sock-exists-'));
try {
const socketPath = path.join(tmp, 'node.sock');
execFileSync(process.execPath, ['-e', bitcoinSocketExistsScript, socketPath], {
stdio: 'ignore',
});
assert.fail('expected non-zero exit for missing socket');
} catch (err) {
assert.ok(
err && typeof err === 'object' && 'status' in err && err.status !== 0,
'missing socket should exit non-zero',
);
}
});
test('bitcoinSocketExistsScript exits 0 when the socket path exists and non-zero otherwise', () => {
const tmp = mkdtempSync(path.join(tmpdir(), 'sock-exists-'));
try {
const socketPath = path.join(tmp, 'node.sock');
assert.throws(
() => execFileSync(process.execPath, ['-e', bitcoinSocketExistsScript, socketPath], {
stdio: 'ignore',
}),
/Command failed/,
'missing socket path should exit non-zero',
);
writeFileSync(socketPath, '');
execFileSync(process.execPath, ['-e', bitcoinSocketExistsScript, socketPath], {
stdio: 'ignore',
});
} finally {
rmSync(tmp, { recursive: true, force: true });
}
});

@lucasbalieiro lucasbalieiro Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amended the suggestion

7 changes: 5 additions & 2 deletions server/src/bitcoin-socket-exists.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
export const bitcoinSocketExistsScript = `const fs = require('fs');
const { execSync } = require('child_process');

const socketPath = process.argv[1];
const dir = require('path').dirname(socketPath);

try {
console.log('DEBUG: Checking path:', socketPath);
console.log('DEBUG: Directory contents:', execSync('ls -la ' + dir).toString());
// DO NOT REMOVE: sadly, on Docker Desktop for macOS, a host Unix socket is not
// visible to fs.existsSync until its parent directory has been read.
// This readdirSync populates the file-sharing cache that the existence
// check below relies on.
fs.readdirSync(dir);
console.log('DEBUG: exists:', fs.existsSync(socketPath));
} catch(e) {
console.log('DEBUG ERROR:', e.message);
Expand Down
Loading