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
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env node
|
||||
// Verifies that all data streams read by the long-lived shell process
|
||||
// are capped on the producer side to prevent unbounded buffering.
|
||||
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const os = require("os")
|
||||
const { execFileSync, spawnSync } = require("child_process")
|
||||
|
||||
const Model = {}
|
||||
const code = fs.readFileSync(path.join(__dirname, "..", "BitwardenModel.js"), "utf8")
|
||||
.replace(/^\.pragma library\s*$/m, "")
|
||||
|
||||
new Function("exports", code + `
|
||||
exports.listCommand = listCommand
|
||||
exports.getItemCommand = getItemCommand
|
||||
exports.listSendsCommand = listSendsCommand
|
||||
exports.listFoldersCommand = listFoldersCommand
|
||||
exports.listOrganizationsCommand = listOrganizationsCommand
|
||||
exports.listOrgCollectionsCommand = listOrgCollectionsCommand
|
||||
exports.getTotpCommand = getTotpCommand
|
||||
exports.statusCommand = statusCommand
|
||||
exports.generateCommand = generateCommand
|
||||
exports.generateServeRequestCommand = generateServeRequestCommand
|
||||
exports.createSendCommand = createSendCommand
|
||||
exports.createItemCommand = createItemCommand
|
||||
exports.editItemCommand = editItemCommand
|
||||
exports.deleteItemCommand = deleteItemCommand
|
||||
exports.createFolderCommand = createFolderCommand
|
||||
exports.attachmentDownloadCommand = attachmentDownloadCommand
|
||||
exports.sessionHandoffReadCommand = sessionHandoffReadCommand
|
||||
exports.associationsReadCommand = associationsReadCommand
|
||||
exports.keyringLookupCommand = keyringLookupCommand
|
||||
exports.keyringLookupMasterPasswordCommand = keyringLookupMasterPasswordCommand
|
||||
exports.pinUnlockCommand = pinUnlockCommand
|
||||
exports.dependencyCheckCommand = dependencyCheckCommand
|
||||
exports.buildCappedCommand = buildCappedCommand
|
||||
exports.syncCommand = syncCommand
|
||||
exports.deleteSendCommand = deleteSendCommand
|
||||
exports.settingWriteCommand = settingWriteCommand
|
||||
`)(Model)
|
||||
|
||||
let pass = 0
|
||||
const failures = []
|
||||
const check = (label, ok, detail) => {
|
||||
if (ok) {
|
||||
pass++
|
||||
} else {
|
||||
failures.push(`${label}\n ${detail}`)
|
||||
}
|
||||
}
|
||||
|
||||
const flat = (cmd) => (Array.isArray(cmd) ? cmd.join(" ") : String(cmd))
|
||||
|
||||
// 1. Vault item list stream is capped
|
||||
const listCmd = Model.listCommand()
|
||||
check("listCommand produces bash pipeline with head -c byte cap",
|
||||
flat(listCmd).includes("bw list items") && flat(listCmd).includes("head -c 16777216"),
|
||||
flat(listCmd))
|
||||
check("listCommand caps diagnostic stderr stream",
|
||||
flat(listCmd).includes("exec 2> >(head -c 8192 >&2)"),
|
||||
flat(listCmd))
|
||||
|
||||
// 2. Vault item detail stream is capped
|
||||
const getItemCmd = Model.getItemCommand("12345-abc")
|
||||
check("getItemCommand caps item detail output to 4MB",
|
||||
flat(getItemCmd).includes("bw get item -- 12345-abc") && flat(getItemCmd).includes("head -c 4194304"),
|
||||
flat(getItemCmd))
|
||||
check("getItemCommand caps stderr stream",
|
||||
flat(getItemCmd).includes("exec 2> >(head -c 8192 >&2)"),
|
||||
flat(getItemCmd))
|
||||
|
||||
// 3. Bitwarden send list stream is capped
|
||||
const sendsCmd = Model.listSendsCommand()
|
||||
check("listSendsCommand caps send list output to 8MB",
|
||||
flat(sendsCmd).includes("bw send list") && flat(sendsCmd).includes("head -c 8388608"),
|
||||
flat(sendsCmd))
|
||||
|
||||
// 4. Folder list stream is capped
|
||||
const foldersCmd = Model.listFoldersCommand()
|
||||
check("listFoldersCommand caps folder list output to 2MB",
|
||||
flat(foldersCmd).includes("bw list folders") && flat(foldersCmd).includes("head -c 2097152"),
|
||||
flat(foldersCmd))
|
||||
|
||||
// 5. Organization list stream is capped
|
||||
const orgsCmd = Model.listOrganizationsCommand()
|
||||
check("listOrganizationsCommand caps org list output to 2MB",
|
||||
flat(orgsCmd).includes("bw list organizations") && flat(orgsCmd).includes("head -c 2097152"),
|
||||
flat(orgsCmd))
|
||||
|
||||
// 6. Organization collections stream is capped
|
||||
const orgColsCmd = Model.listOrgCollectionsCommand("org-99")
|
||||
check("listOrgCollectionsCommand caps collections output to 2MB",
|
||||
flat(orgColsCmd).includes("bw list org-collections --organizationid org-99") && flat(orgColsCmd).includes("head -c 2097152"),
|
||||
flat(orgColsCmd))
|
||||
|
||||
// 7. Status and unlock streams are capped
|
||||
const statusCmd = Model.statusCommand()
|
||||
check("statusCommand caps status json output to 64KB",
|
||||
flat(statusCmd).includes("bw status") && flat(statusCmd).includes("head -c 65536"),
|
||||
flat(statusCmd))
|
||||
|
||||
// 8. TOTP code stream is capped
|
||||
const totpCmd = Model.getTotpCommand("item-55")
|
||||
check("getTotpCommand caps totp output to 4KB",
|
||||
flat(totpCmd).includes("bw get totp --raw -- item-55") && flat(totpCmd).includes("head -c 4096"),
|
||||
flat(totpCmd))
|
||||
|
||||
// 9. Session handoff file reader is size-bounded
|
||||
const handoffCmd = Model.sessionHandoffReadCommand(true)
|
||||
check("sessionHandoffReadCommand bounds file reading with head -c 4096",
|
||||
flat(handoffCmd).includes("head -c 4096") && !flat(handoffCmd).includes("cat \"$f\""),
|
||||
flat(handoffCmd))
|
||||
|
||||
// 10. Associations file reader is size-bounded
|
||||
const assocCmd = Model.associationsReadCommand()
|
||||
check("associationsReadCommand bounds file reading with head -c 1048576",
|
||||
flat(assocCmd).includes("head -c 1048576") && !flat(assocCmd).includes("cat \"$ASSOC_FILE\""),
|
||||
flat(assocCmd))
|
||||
|
||||
// 11. Keyring lookups and PIN unlock are size-bounded
|
||||
const keyringCmd = Model.keyringLookupCommand()
|
||||
check("keyringLookupCommand bounds secret-tool output to 4KB",
|
||||
flat(keyringCmd).includes("head -c 4096") && flat(keyringCmd).includes("head -c 128"),
|
||||
flat(keyringCmd))
|
||||
|
||||
const pinCmd = Model.pinUnlockCommand()
|
||||
check("pinUnlockCommand bounds both ciphertext lookup and decrypted password",
|
||||
flat(pinCmd).includes("head -c 8192") && flat(pinCmd).includes("head -c 4096"),
|
||||
flat(pinCmd))
|
||||
|
||||
// 12. Password generator output is capped
|
||||
const genPassCmd = Model.generateCommand({ length: 32 })
|
||||
check("generateCommand caps password output to 4KB",
|
||||
flat(genPassCmd).includes("bw generate") && flat(genPassCmd).includes("head -c 4096"),
|
||||
flat(genPassCmd))
|
||||
|
||||
// 12b. Generator serve request stream is capped on the producer side
|
||||
const serveReqCmd = Model.generateServeRequestCommand({ length: 24 })
|
||||
check("generateServeRequestCommand bounds loopback response stream with head -c 65536",
|
||||
flat(serveReqCmd).includes("curl -q -s -S") && flat(serveReqCmd).includes("head -c 65536"),
|
||||
flat(serveReqCmd))
|
||||
|
||||
// 13. Create/Edit/Delete commands are capped
|
||||
const createFolderCmd = Model.createFolderCommand("test")
|
||||
check("createFolderCommand caps stderr and response",
|
||||
flat(createFolderCmd).includes("exec 2> >(head -c 8192 >&2)") && flat(createFolderCmd).includes("head -c 65536"),
|
||||
flat(createFolderCmd))
|
||||
|
||||
// The save commands cap their response the way sanitizedListCommand does:
|
||||
// read one byte past the ceiling, then refuse anything that reached it. A
|
||||
// bare `head -c <max>` cannot tell a stream that fit from one that was cut,
|
||||
// and these two now carry a sanitising stage whose output must be whole or
|
||||
// discarded. See the same idiom asserted for the item list in ssh-items.
|
||||
const capsResponse = cmd =>
|
||||
flat(cmd).includes("head -c 65537") && flat(cmd).includes('-gt 65536')
|
||||
|
||||
const createItemCmd = Model.createItemCommand({ organizationId: "org-1" })
|
||||
check("createItemCommand caps stderr and response",
|
||||
flat(createItemCmd).includes("exec 2> >(head -c 8192 >&2)") && capsResponse(createItemCmd),
|
||||
flat(createItemCmd))
|
||||
|
||||
const editItemCmd = Model.editItemCommand("item-1")
|
||||
check("editItemCommand caps stderr and response",
|
||||
flat(editItemCmd).includes("exec 2> >(head -c 8192 >&2)") && capsResponse(editItemCmd),
|
||||
flat(editItemCmd))
|
||||
|
||||
const deleteItemCmd = Model.deleteItemCommand("item-1")
|
||||
check("deleteItemCommand caps stderr and response",
|
||||
flat(deleteItemCmd).includes("exec 2> >(head -c 8192 >&2)") && flat(deleteItemCmd).includes("head -c 65536"),
|
||||
flat(deleteItemCmd))
|
||||
|
||||
// 14. Live execution check: verify head -c truncation behaviour on huge stream
|
||||
const hugeScript = "yes 'unbounded streaming line' | head -c 1024"
|
||||
const hugeOut = execFileSync("bash", ["-c", hugeScript], { encoding: "utf8" })
|
||||
check("head -c strictly bounds incoming stream to exact byte count",
|
||||
Buffer.byteLength(hugeOut, "utf8") === 1024,
|
||||
`Expected 1024 bytes, got ${Buffer.byteLength(hugeOut, "utf8")}`)
|
||||
|
||||
// 15. Live execution check: verify stderr bounding does not corrupt stdout
|
||||
const stderrScript = "exec 2> >(head -c 100 >&2); echo 'stdout data'; (echo 'short stderr error' >&2)"
|
||||
const proc = execFileSync("bash", ["-c", stderrScript], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
|
||||
check("capped stderr does not leak into stdout",
|
||||
proc.trim() === "stdout data",
|
||||
`stdout was: ${JSON.stringify(proc)}`)
|
||||
|
||||
// 16. A cap must not swallow the producer's exit status. `head -c` closes the
|
||||
// pipe and exits 0, so without `pipefail` every failing bw command would reach
|
||||
// the panel as a success and the UI would report "Item deleted" for a delete
|
||||
// that never happened.
|
||||
const cappedBuilders = [
|
||||
["listCommand", Model.listCommand()],
|
||||
["getItemCommand", Model.getItemCommand("x")],
|
||||
["deleteItemCommand", Model.deleteItemCommand("x")],
|
||||
["deleteSendCommand", Model.deleteSendCommand("x")],
|
||||
["syncCommand", Model.syncCommand()],
|
||||
["createItemCommand", Model.createItemCommand({})],
|
||||
["editItemCommand", Model.editItemCommand("x")],
|
||||
["createSendCommand", Model.createSendCommand()],
|
||||
["createFolderCommand", Model.createFolderCommand("x")],
|
||||
["settingWriteCommand", Model.settingWriteCommand("autoLockMinutes", 5, "int")],
|
||||
]
|
||||
for (const [name, cmd] of cappedBuilders) {
|
||||
check(`${name} restores the producer's exit status with pipefail`,
|
||||
flat(cmd).includes("set -o pipefail"), flat(cmd))
|
||||
check(`${name} does not report truncation (SIGPIPE 141) as a failure`,
|
||||
flat(cmd).includes('case "$__rc" in 141) __rc=0 ;; esac'), flat(cmd))
|
||||
}
|
||||
|
||||
// 17. Live execution check, with a stub `bw`: a failing command must exit
|
||||
// non-zero through the cap, and a stream large enough to hit the cap must not
|
||||
// be mistaken for a failure.
|
||||
const stubDir = fs.mkdtempSync(path.join(os.tmpdir(), "qsbw-stream-"))
|
||||
fs.writeFileSync(path.join(stubDir, "bw"), [
|
||||
"#!/bin/bash",
|
||||
'case "$*" in',
|
||||
' *boom*) echo "error: bad request" >&2; exit 1 ;;',
|
||||
" *big*) yes '{\"x\":\"aaaaaaaaaaaaaaaaaaaa\"}' ;;",
|
||||
" *) echo '{\"ok\":true}' ;;",
|
||||
"esac",
|
||||
"",
|
||||
].join("\n"))
|
||||
fs.chmodSync(path.join(stubDir, "bw"), 0o755)
|
||||
const stubEnv = Object.assign({}, process.env, { PATH: stubDir + path.delimiter + process.env.PATH })
|
||||
|
||||
const runScript = (script) => {
|
||||
const r = spawnSync("bash", ["-c", script], {
|
||||
env: stubEnv, encoding: "utf8", maxBuffer: 64 * 1024 * 1024,
|
||||
})
|
||||
return { code: r.status, stdout: r.stdout || "", stderr: r.stderr || "" }
|
||||
}
|
||||
|
||||
const failRun = runScript(Model.deleteItemCommand("boom")[2])
|
||||
check("a failing bw command exits non-zero through the cap",
|
||||
failRun.code === 1, `exit ${failRun.code}, stderr ${JSON.stringify(failRun.stderr)}`)
|
||||
check("a failing bw command still delivers its stderr to the panel",
|
||||
failRun.stderr.includes("bad request"), JSON.stringify(failRun.stderr))
|
||||
|
||||
const okRun = runScript(Model.deleteItemCommand("fine")[2])
|
||||
check("a succeeding bw command exits zero through the cap",
|
||||
okRun.code === 0, `exit ${okRun.code}`)
|
||||
|
||||
// `bw big` never stops printing: only the cap ends it, and the SIGPIPE that
|
||||
// follows must not be reported as a failed vault read.
|
||||
const truncRun = runScript(Model.getItemCommand("big")[2])
|
||||
check("hitting the cap is not reported as a failure",
|
||||
truncRun.code === 0, `exit ${truncRun.code}`)
|
||||
check("hitting the cap truncates at exactly the limit",
|
||||
Buffer.byteLength(truncRun.stdout, "utf8") === 4 * 1024 * 1024,
|
||||
`got ${Buffer.byteLength(truncRun.stdout, "utf8")} bytes`)
|
||||
|
||||
fs.rmSync(stubDir, { recursive: true, force: true })
|
||||
|
||||
console.log(`${pass} passed, ${failures.length} failed`)
|
||||
if (failures.length) {
|
||||
console.error("\nFAILURES:\n " + failures.join("\n "))
|
||||
process.exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user