fix: redact nordvpn path metadata by default

This commit is contained in:
Stefano Fiorini
2026-03-12 02:37:16 -05:00
parent 60f425a4fc
commit 7d8eb89911
2 changed files with 62 additions and 13 deletions

View File

@@ -37,9 +37,29 @@ const MAC_WG_HELPER_PATH = path.join(
const CLIENT_IPV4 = "10.5.0.2"; const CLIENT_IPV4 = "10.5.0.2";
const DNS_FALLBACK_RESOLVERS = ["1.1.1.1", "8.8.8.8"]; const DNS_FALLBACK_RESOLVERS = ["1.1.1.1", "8.8.8.8"];
const NORDVPN_MAC_DNS_SERVERS = ["103.86.96.100", "103.86.99.100"]; const NORDVPN_MAC_DNS_SERVERS = ["103.86.96.100", "103.86.99.100"];
const REDACTED_PATH_KEYS = new Set(["cliPath", "appPath", "configPath", "helperPath", "tokenSource"]);
function printJson(payload, exitCode = 0, errorStream = false) { function sanitizeOutputPayload(payload) {
const body = `${JSON.stringify(payload, null, 2)}\n`; if (Array.isArray(payload)) {
return payload.map((value) => sanitizeOutputPayload(value));
}
if (!payload || typeof payload !== "object") {
return payload;
}
const sanitized = {};
for (const [key, value] of Object.entries(payload)) {
if (REDACTED_PATH_KEYS.has(key)) {
sanitized[key] = null;
continue;
}
sanitized[key] = sanitizeOutputPayload(value);
}
return sanitized;
}
function printJson(payload, exitCode = 0, errorStream = false, debugOutput = false) {
const body = `${JSON.stringify(debugOutput ? payload : sanitizeOutputPayload(payload), null, 2)}\n`;
(errorStream ? process.stderr : process.stdout).write(body); (errorStream ? process.stderr : process.stdout).write(body);
process.exit(exitCode); process.exit(exitCode);
} }
@@ -1256,14 +1276,16 @@ async function loginNordvpn(installProbe) {
async function main() { async function main() {
const args = parseArgs(process.argv.slice(2)); const args = parseArgs(process.argv.slice(2));
const debugOutput = Boolean(args.debug);
const emitJson = (payload, exitCode = 0, errorStream = false) => printJson(payload, exitCode, errorStream, debugOutput);
const action = args._[0]; const action = args._[0];
if (!action || args.help) { if (!action || args.help) {
printJson(usage(), action ? 0 : 1, !action); emitJson(usage(), action ? 0 : 1, !action);
} }
const platform = detectPlatform(); const platform = detectPlatform();
if (!["darwin", "linux"].includes(platform)) { if (!["darwin", "linux"].includes(platform)) {
printJson({ error: `Unsupported platform: ${platform}` }, 1, true); emitJson({ error: `Unsupported platform: ${platform}` }, 1, true);
} }
const installProbe = await probeInstallation(platform); const installProbe = await probeInstallation(platform);
@@ -1271,13 +1293,13 @@ async function main() {
try { try {
if (action === "status") { if (action === "status") {
const ipInfo = await getPublicIpInfo(); const ipInfo = await getPublicIpInfo();
printJson(buildStateSummary(installProbe, ipInfo)); emitJson(buildStateSummary(installProbe, ipInfo));
} }
if (action === "install") { if (action === "install") {
const result = await installNordvpn(installProbe); const result = await installNordvpn(installProbe);
const refreshed = await probeInstallation(platform); const refreshed = await probeInstallation(platform);
printJson({ emitJson({
action, action,
...result, ...result,
state: buildStateSummary(refreshed, await getPublicIpInfo()), state: buildStateSummary(refreshed, await getPublicIpInfo()),
@@ -1287,7 +1309,7 @@ async function main() {
if (action === "login") { if (action === "login") {
const result = await loginNordvpn(installProbe); const result = await loginNordvpn(installProbe);
const refreshed = await probeInstallation(platform); const refreshed = await probeInstallation(platform);
printJson({ emitJson({
action, action,
...result, ...result,
state: buildStateSummary(refreshed, await getPublicIpInfo()), state: buildStateSummary(refreshed, await getPublicIpInfo()),
@@ -1298,7 +1320,7 @@ async function main() {
const target = args.country || args.city ? buildConnectTarget(args) : null; const target = args.country || args.city ? buildConnectTarget(args) : null;
const verified = await verifyConnectionWithRetry(target); const verified = await verifyConnectionWithRetry(target);
const refreshed = await probeInstallation(platform); const refreshed = await probeInstallation(platform);
printJson( emitJson(
{ {
action, action,
requestedTarget: target, requestedTarget: target,
@@ -1332,13 +1354,13 @@ async function main() {
} }
if (connectResult.manualActionRequired) { if (connectResult.manualActionRequired) {
printJson({ action, requestedTarget: target, ...connectResult }); emitJson({ action, requestedTarget: target, ...connectResult });
} }
const verified = await verifyConnectionWithRetry(target, { attempts: 6, delayMs: 2000 }); const verified = await verifyConnectionWithRetry(target, { attempts: 6, delayMs: 2000 });
const refreshed = await probeInstallation(platform); const refreshed = await probeInstallation(platform);
const connectState = normalizeSuccessfulConnectState(buildStateSummary(refreshed, verified.ipInfo), connectResult, verified); const connectState = normalizeSuccessfulConnectState(buildStateSummary(refreshed, verified.ipInfo), connectResult, verified);
printJson( emitJson(
{ {
action, action,
requestedTarget: target, requestedTarget: target,
@@ -1355,16 +1377,16 @@ async function main() {
if (action === "disconnect") { if (action === "disconnect") {
const result = await disconnectNordvpn(installProbe); const result = await disconnectNordvpn(installProbe);
const refreshed = await probeInstallation(platform); const refreshed = await probeInstallation(platform);
printJson({ emitJson({
action, action,
...result, ...result,
state: buildStateSummary(refreshed, await getPublicIpInfo()), state: buildStateSummary(refreshed, await getPublicIpInfo()),
}); });
} }
printJson({ error: `Unknown action: ${action}`, ...usage() }, 1, true); emitJson({ error: `Unknown action: ${action}`, ...usage() }, 1, true);
} catch (error) { } catch (error) {
printJson({ error: error.message || String(error), action }, 1, true); emitJson({ error: error.message || String(error), action }, 1, true);
} }
} }

View File

@@ -23,6 +23,8 @@ module.exports = {
typeof isMacTailscaleActive === "function" ? isMacTailscaleActive : undefined, typeof isMacTailscaleActive === "function" ? isMacTailscaleActive : undefined,
normalizeSuccessfulConnectState: normalizeSuccessfulConnectState:
typeof normalizeSuccessfulConnectState === "function" ? normalizeSuccessfulConnectState : undefined, typeof normalizeSuccessfulConnectState === "function" ? normalizeSuccessfulConnectState : undefined,
sanitizeOutputPayload:
typeof sanitizeOutputPayload === "function" ? sanitizeOutputPayload : undefined,
shouldAttemptMacWireguardDisconnect: shouldAttemptMacWireguardDisconnect:
typeof shouldAttemptMacWireguardDisconnect === "function" ? shouldAttemptMacWireguardDisconnect : undefined, typeof shouldAttemptMacWireguardDisconnect === "function" ? shouldAttemptMacWireguardDisconnect : undefined,
detectMacWireguardActiveFromIfconfig: detectMacWireguardActiveFromIfconfig:
@@ -202,6 +204,31 @@ test("normalizeSuccessfulConnectState marks the connect snapshot active after ve
assert.equal(state.wireguard.endpoint, "de1227.nordvpn.com:51820"); assert.equal(state.wireguard.endpoint, "de1227.nordvpn.com:51820");
}); });
test("sanitizeOutputPayload redacts local path metadata from normal JSON output", () => {
const { sanitizeOutputPayload } = loadInternals();
assert.equal(typeof sanitizeOutputPayload, "function");
const sanitized = sanitizeOutputPayload({
cliPath: "/opt/homebrew/bin/nordvpn",
appPath: "/Applications/NordVPN.app",
wireguard: {
configPath: "/Users/stefano/.nordvpn-client/wireguard/nordvpnctl.conf",
helperPath: "/Users/stefano/.openclaw/workspace/skills/nordvpn-client/scripts/nordvpn-wireguard-helper.sh",
authCache: {
tokenSource: "default:/Users/stefano/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt",
},
endpoint: "jp454.nordvpn.com:51820",
},
});
assert.equal(sanitized.cliPath, null);
assert.equal(sanitized.appPath, null);
assert.equal(sanitized.wireguard.configPath, null);
assert.equal(sanitized.wireguard.helperPath, null);
assert.equal(sanitized.wireguard.authCache.tokenSource, null);
assert.equal(sanitized.wireguard.endpoint, "jp454.nordvpn.com:51820");
});
test("isMacTailscaleActive treats Running backend as active", () => { test("isMacTailscaleActive treats Running backend as active", () => {
const { isMacTailscaleActive } = loadInternals(); const { isMacTailscaleActive } = loadInternals();
assert.equal(typeof isMacTailscaleActive, "function"); assert.equal(typeof isMacTailscaleActive, "function");