fix: redact nordvpn path metadata by default
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user