Files
omarchy/plugins/io.github.elevate08.qs-bitwarden-cli/tests/handoff-urls.test.js
T
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

153 lines
7.3 KiB
JavaScript

#!/usr/bin/env node
// Two places where data the panel did not write reaches the system: the
// session-handoff file path, and a vault item's URI on its way to xdg-open.
//
// node tests/handoff-urls.test.js
const fs = require("fs")
const path = require("path")
const { execFileSync } = require("child_process")
const os = require("os")
const Model = {}
new Function("exports", fs.readFileSync(path.join(__dirname, "..", "BitwardenModel.js"), "utf8")
.replace(/^\.pragma library\s*$/m, "") + `
exports.terminalLoginCommand = terminalLoginCommand
exports.sessionHandoffReadCommand = sessionHandoffReadCommand
exports.normalizeOpenableUrl = normalizeOpenableUrl
`)(Model)
let pass = 0
const failures = []
const check = (l, ok, d) => ok ? pass++ : failures.push(`${l}\n ${d}`)
// --- the session handoff file never falls back to a shared directory --------
// The key is written by a terminal and read by the panel. A world-writable
// fallback would let another user pre-create the directory and collect it.
const login = Model.terminalLoginCommand("login")[2]
const unlock = Model.terminalLoginCommand("unlock")[2]
const read = Model.sessionHandoffReadCommand(true)[2]
for (const [label, script] of [["terminal login", login], ["terminal unlock", unlock], ["handoff read", read]]) {
check(`${label} never falls back to /tmp`, !script.includes("/tmp"), script)
check(`${label} puts the key under XDG_RUNTIME_DIR`, script.includes("XDG_RUNTIME_DIR"), script)
}
check("the write side refuses to run without a runtime dir, rather than defaulting",
/XDG_RUNTIME_DIR:\?/.test(login), login)
check("mkdir and chmod are both checked, so a directory that is not ours aborts",
login.includes('mkdir -p "$d" || exit 1') && login.includes('chmod 700 "$d" || exit 1'), login)
check("the write side refuses a symlinked handoff directory",
login.includes('[ ! -L "$d" ]'), login)
check("umask is set before the directory is created, not after",
login.indexOf("umask 077") < login.indexOf("mkdir"), login)
check("the read side exits quietly instead, since it runs on every refresh",
read.includes('[ -n "$d" ] || exit 0') && !/XDG_RUNTIME_DIR:\?/.test(read), read)
check("the read side never follows a symlinked directory or handoff file",
read.includes('[ ! -L "$d" ]') && read.includes('[ ! -L "$f" ]'), read)
// Actually run the two scripts to prove the behaviour, with `bw` stubbed.
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "qsbw-handoff-"))
const bin = path.join(tmp, "bin")
fs.mkdirSync(bin)
fs.writeFileSync(path.join(bin, "bw"), "#!/usr/bin/env bash\necho STUBSESSIONKEY\n", { mode: 0o755 })
const runInner = (script, env) => {
try {
// stderr is swallowed: the no-runtime-dir case is *meant* to complain there.
return { out: execFileSync("bash", ["-c", script], { env, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(), code: 0 }
} catch (e) {
return { out: String(e.stdout || "").trim(), code: e.status }
}
}
const noRuntime = { PATH: bin + ":" + process.env.PATH, HOME: tmp }
const withRuntime = { ...noRuntime, XDG_RUNTIME_DIR: tmp }
// The inner script is what the terminal runs; pull it out of the quoted wrapper.
const innerLogin = login.match(/omarchy launch terminal -e bash -c '(.*)' \|\| alacritty/)[1].replace(/'\\''/g, "'")
const denied = runInner(innerLogin, noRuntime)
check("with no runtime dir the login script exits non-zero and writes no key",
denied.code !== 0 && !fs.existsSync(path.join(tmp, "qs-bitwarden-cli", "session-handoff")),
`exit ${denied.code}`)
const readNoRuntime = runInner(read, noRuntime)
check("with no runtime dir the read is silent and successful",
readNoRuntime.code === 0 && readNoRuntime.out === "", JSON.stringify(readNoRuntime))
// A pre-existing symlink must not redirect either side to a persistent or
// less-protected directory. XDG_RUNTIME_DIR is private, but refusing the
// redirect also makes a stale/misconfigured runtime fail closed.
const handoffDir = path.join(tmp, "qs-bitwarden-cli")
const redirectedDir = path.join(tmp, "redirected")
fs.mkdirSync(redirectedDir)
fs.symlinkSync(redirectedDir, handoffDir)
const symlinkWrite = runInner(innerLogin.replace(/omarchy-shell[^;]*;/, "true;").replace(/read -p[^;]*;?/, ""), withRuntime)
check("a symlinked handoff directory makes terminal login fail closed",
symlinkWrite.code !== 0 && !fs.existsSync(path.join(redirectedDir, "session-handoff")),
`exit ${symlinkWrite.code}`)
fs.writeFileSync(path.join(redirectedDir, "session-handoff"), "REDIRECTED")
const symlinkRead = runInner(read, withRuntime)
check("the panel does not read through a symlinked handoff directory",
symlinkRead.out === "", JSON.stringify(symlinkRead))
fs.unlinkSync(handoffDir)
// With a runtime dir, the round trip works and the directory is private.
fs.mkdirSync(path.join(tmp, "qs-bitwarden-cli"), { recursive: true, mode: 0o755 })
runInner(innerLogin.replace(/omarchy-shell[^;]*;/, "true;").replace(/read -p[^;]*;?/, ""), withRuntime)
check("the handoff directory ends up private to the user",
(fs.statSync(handoffDir).mode & 0o777) === 0o700,
"0" + (fs.statSync(handoffDir).mode & 0o777).toString(8))
const roundTrip = runInner(read, withRuntime)
check("the panel reads the key back", roundTrip.out === "STUBSESSIONKEY", JSON.stringify(roundTrip))
const secondRead = runInner(read, withRuntime)
check("and the key is consumed, so a second read gets nothing",
secondRead.out === "", JSON.stringify(secondRead))
fs.rmSync(tmp, { recursive: true, force: true })
// --- only web links are handed to xdg-open ----------------------------------
// A vault item's URI is data, and an org-shared item can be written by others.
const opens = (u) => Model.normalizeOpenableUrl(u)
for (const [input, expected] of [
["https://example.com", "https://example.com"],
["http://example.com/login", "http://example.com/login"],
["HTTPS://Example.COM", "HTTPS://Example.COM"],
["example.com", "https://example.com"],
["example.com:8443/login", "https://example.com:8443/login"],
["localhost:3000", "https://localhost:3000"],
["192.168.1.10:8006", "https://192.168.1.10:8006"]
]) {
const r = opens(input)
check(`${input} opens as ${expected}`, r.ok && r.url === expected, JSON.stringify(r))
}
for (const [input, scheme] of [
["file:///etc/passwd", "file"],
["ftp://files.example.com", "ftp"],
["javascript:alert(1)", "javascript"],
["data:text/html,<script>x</script>", "data"],
["mailto:someone@example.com", "mailto"],
["vnc://10.0.0.1", "vnc"],
["custom-app-handler://do-something", "custom-app-handler"]
]) {
const r = opens(input)
check(`${input} is refused`, !r.ok && r.scheme === scheme, JSON.stringify(r))
}
check("an empty URI is refused without naming a scheme",
!opens("").ok && opens("").scheme === "", JSON.stringify(opens("")))
check("whitespace is trimmed rather than https-ified",
opens(" https://example.com ").url === "https://example.com", JSON.stringify(opens(" https://example.com ")))
check("an ambiguous backslash web URL is refused instead of parsed differently by the browser",
!opens("https://evil.example\\@trusted.example").ok
&& opens("https://evil.example\\@trusted.example").reason === "ambiguous",
JSON.stringify(opens("https://evil.example\\@trusted.example")))
console.log(`${pass} passed, ${failures.length} failed`)
if (failures.length) { console.error("\nFAILURES:\n " + failures.join("\n ")); process.exit(1) }