diff --git a/tailscalectl/lib.js b/tailscalectl/lib.js index f4cd31e..bd45288 100644 --- a/tailscalectl/lib.js +++ b/tailscalectl/lib.js @@ -26,6 +26,12 @@ function findActiveExitNode(peerMap) { return "" } +var safeClipboardCmds = ["dms cl copy", "wl-copy", "clipmanctl copy", "none"] + +function validateClipboardCmd(cmd) { + return typeof cmd === "string" && safeClipboardCmds.includes(cmd) +} + function errorMessage(cmd) { var messages = { "up": "Failed to connect to Tailscale", @@ -40,5 +46,5 @@ function errorMessage(cmd) { // CommonJS export for Node.js tests (ignored by QML) if (typeof module !== "undefined" && module.exports) { - module.exports = { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage } + module.exports = { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardCmd } } diff --git a/test/lib.test.js b/test/lib.test.js index ce9897b..1adbf28 100644 --- a/test/lib.test.js +++ b/test/lib.test.js @@ -1,7 +1,7 @@ import { test } from "node:test" import assert from "node:assert" import lib from "../tailscalectl/lib.js" -const { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage } = lib +const { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardCmd } = lib test("parsePeers extracts exitNode from ExitNodeOption", () => { const peerMap = { @@ -74,3 +74,25 @@ test("errorMessage returns generic message for unknown command", () => { const msg = errorMessage("unknown", 1) assert.strictEqual(msg, "Tailscale command failed") }) + +// --- validateClipboardCmd --- + +test("validateClipboardCmd accepts whitelisted values", () => { + assert.strictEqual(validateClipboardCmd("dms cl copy"), true) + assert.strictEqual(validateClipboardCmd("wl-copy"), true) + assert.strictEqual(validateClipboardCmd("clipmanctl copy"), true) + assert.strictEqual(validateClipboardCmd("none"), true) +}) + +test("validateClipboardCmd rejects malicious inputs", () => { + assert.strictEqual(validateClipboardCmd("rm -rf /"), false) + assert.strictEqual(validateClipboardCmd("echo hi; malicious"), false) + assert.strictEqual(validateClipboardCmd("$(whoami)"), false) + assert.strictEqual(validateClipboardCmd("wl-copy || rm -rf /"), false) +}) + +test("validateClipboardCmd rejects empty and falsy inputs", () => { + assert.strictEqual(validateClipboardCmd(""), false) + assert.strictEqual(validateClipboardCmd(null), false) + assert.strictEqual(validateClipboardCmd(undefined), false) +})