Reply to note from Harry Binswanger <dmarc-noreply@xxxxxxxxxxxxx> Wed, 29 Apr 2026 12:41:17 -0400 > I know you always send out n versions of your latest Well, you know how that is. Notably, the latest iteration includes features I added after asking Claude to assess QuickNotes for security vulnerabilities. Download here: https://ammaze.net/xywwweb/dls/quicknotes.zip I must say that my back-and-forth with Claude about security issues was edifying. If you're interested, here's the transcript: --Quote-- I said: Question: How do you assess the app's safety? Are there security vulnerabilities, particularly around the [Windows] command-execution capabilities? Claude responded: Honest assessment, looking at it as if I were doing a code review for a security team: Threat model. The app's safety profile depends entirely on what you trust. If every note you work with was authored by you, the risk is essentially "you can hurt your own machine if you type the wrong command and run it" -- which is true of any terminal. The picture changes if you ever load notes that came from elsewhere (email, shared folder, copy-pasted from a web page, downloaded sample). The big surface: command execution. All five run paths -- run_selection_inline, run_current_line_inline, run_current_line_silent, run_current_line_as_command, and the Run-Command dialog itself -- call subprocess.run(command, shell=True). That means full cmd.exe interpretation: pipes, redirects, & chaining, %VAR% expansion, the works. A line in a note that says echo doing taxes & del /S /Q "%USERPROFILE%\Documents" does exactly what you'd think when you press F5 on it. Concrete attack scenarios: Phishing-style abuse: someone sends you a .md "tutorial" with a "run this for me" line that's actually destructive. You drop it in your notes folder, scroll past the explanation, hit F5 on the line. Copy-paste from compromised source: you copy what looks like a one-line snippet from a tutorial site that's been tampered with; the visible text is benign and trailing characters (after a newline or hidden in a long line) carry the payload. Multi-line selection misclick: Ctrl+Alt+R runs every line in the selection one at a time. A long innocuous-looking selection can include a destructive last line you didn't notice. Severity: high in absolute terms (full RCE [remote code execution] on your account if abused), but the attacker has to get you to push the key. There's no remote exploitation path I see -- nothing reaches across the network on its own. Hyperlinks in the preview. I bound <ButtonRelease-1> to webbrowser.open(url) for any link the markdown parser found. That accepts any URL scheme -- so file:///C:/..., ms-cxh:, tel:, custom protocol handlers, etc. all go through. Modern browsers block javascript: URLs from external launches, so that's typically not an RCE, but a bare-URL file:/// link can open a local file or directory in Explorer / File Explorer Preview, which has had vulnerabilities historically. Severity: low to medium. PDF rendering / styled HTML. I checked this carefully. _render_markdown_html runs everything user-supplied through html.escape(...) -- including link URLs (escape(url, quote=True)), code, prose text, headings. There's no path that lets a note inject <script> or attribute-based XSS into the rendered HTML. The CSS is generated from a fixed set of theme dicts, no user input. So Edge headless renders only HTML you control. Severity: low. Auto-create on typing. Just writes a file under the configured notes folder using safe_filename (strips [^\w\s.-]). No path-traversal vector. Severity: none. Config / frontmatter parsing. YAML-style frontmatter is parsed line-by-line by splitting on :; there's no eval or yaml.unsafe_load anywhere. Config is JSON via the stdlib json module. A corrupted config can crash startup, not run code. Severity: none. CDP for PDF export. Spawns Edge with a freshly-created throwaway profile, binds debugging to 127.0.0.1 on a random local port, talks WebSocket only to that port. Origin is locked via --remote-allow-origins=* only because we set our own Origin header in the handshake; we don't expose this port to anything else. Severity: low. What I'd recommend if you want to harden this: Optional confirmation before any command execution. A new Options toggle "Confirm before running commands" that pops a dialog showing the exact command and waiting for OK. Cheap, ~30 lines, and would stop nearly every accidental-or-tricked execution. URL-scheme allowlist for hyperlinks. In _on_link_click, only call webbrowser.open for http://, https://, and mailto:. Anything else -> ignore (or warn). Removes the file:// / custom-handler vector. A "lockdown" mode. Single toggle that disables every command-execution code path (greying out the Run menu items) for sessions where you're reading untrusted notes. Toggleable per session or persisted. Document the risk plainly in Help -> About: "QuickNotes can execute any line as a Windows command. Don't run commands from notes you didn't write." Optional: switch from shell=True to argv form where possible. Only effective for the dialog-typed commands, not free-form lines, but it would prevent shell metacharacter abuse for the simple cases. Risk-wise, items 1, 2, and 3 each take an hour or so. Item 4 is a paragraph. Item 5 is a meaningful refactor with limited benefit. Want me to add any of these? Item 1 + item 2 + item 4 together would substantially improve the safety story without changing how the tool feels day-to-day. I said: Thanks for the info. Let's go with Option 4 only (document the risk in Help -> About). I'd tweak the warning as follows: "QuickNotes can execute any line as a Windows command. Use care in composing commands, and don't run commands from notes you didn't write or don't trust." Do you have any further ideas about how the warning should read? Claude responded: Your wording is already in a good place -- clear, calm, action-oriented. A few small tweaks worth considering: Visual prefix. A leading SECURITY: or Note: flag pulls the eye to the warning when it's mixed in with the rest of the About text. Inside messagebox.showinfo, you can't do bold or color, so plain prefix + blank line is the only way to make it stand out. Mention hyperlinks too. The Run features are the bigger hole, but clickable hyperlinks in the preview also reach out (they call webbrowser.open on whatever URL the note specifies, including file://). Lumping it into one sentence keeps things short. Maybe name the trigger keys briefly. Useful so a reader who hasn't read Help -> Usage knows what surfaces are sensitive. Optional -- your current wording arguably benefits from staying generic. Combining those, three drafts in increasing length: A -- your wording with a prefix: SECURITY: QuickNotes can execute any line as a Windows command. Use care in composing commands, and don't run commands from notes you didn't write or don't trust. B -- your wording + hyperlinks mentioned: SECURITY: QuickNotes can execute any line as a Windows command and can open any hyperlink in your default browser. Use care in composing commands, and don't run commands or click links in notes you didn't write or don't trust. C -- fuller, naming the trigger keys: SECURITY: QuickNotes can execute any line as a Windows command (via F5, Alt+Enter, Ctrl+Enter, Ctrl+R, Ctrl+Alt+R, etc.) and can open any hyperlink in your default browser. Use care in composing commands, and don't run commands or click links in notes you didn't write or don't trust. My pick would be B -- it's still one sentence about the action and one about the caveat, but it covers both real surfaces. Want me to wire B in (or any of the three, or your original wording)? You said: I like B too. Let's go with it. [After testing the update] I said: I like it. This is an important addition, wouldn't you say? Claude responded: Yes, definitely -- though with a caveat about what it actually accomplishes. What the warning gets right: it sets the user's expectation correctly the very first time they read it. It tells them, in plain terms, the two specific surfaces that can affect their machine (executing lines, opening links). That alone is a meaningful uplift over a tool that just silently has those capabilities. Most security failures involving local tools come from people not realizing what the tool is capable of, not from the tool failing in some clever way. A warning that names the capabilities flips that on its head. What it doesn't do -- worth being honest about -- is prevent anything. It's the lightest possible mitigation: a passive document that helps only if a user reads it before they get into trouble. Someone who's already in muscle-memory mode hitting F5 on whatever line is under the cursor doesn't see the warning at the moment of risk. The confirmation prompt (option 1 from earlier) is the only mitigation that actually intervenes at the dangerous moment; the URL-scheme allowlist (option 2) is the only one that closes the click-anything-in-the-preview hole. So my honest framing: the warning is important because QuickNotes shipped command execution as a first-class feature without a confirmation step. It's the right minimum. If you ever want to bump from "minimum" to "reasonable," the next two cheapest steps would be: An Options checkbutton "Confirm before running commands" -- defaulting on for new users, off for you. ~30 lines. The URL-scheme allowlist -- silently ignore non-http(s) / mailto: link clicks in the preview. ~15 lines. Both keep day-to-day feel identical for someone who only writes their own notes, while removing essentially all of the realistic hostile-note attack surface. Available whenever you want them. Until then, the warning is a worthwhile addition on its own. I said: Points taken. I'd like the app to be reasonably safe without becoming a "nanny". So let's go with your two suggestions, except "Confirm before running commands" defaults to off for everyone. Claude responded: Adding both. Default OFF for the confirmation, allowlist silently ignores non-http(s) / mailto: clicks. [...] Both safeguards are wired in. Confirm before running commands -- checkbutton in the Options menu, defaults to OFF (no nanny), persisted in config under confirm_run_commands. When on, every run path checks _confirm_command(...) before executing: run_selection_inline (Ctrl+Alt+R) -- confirmation lists each line of a multi-line selection so you see exactly what will be run, e.g. "Run these 3 commands?" with each line on its own row. run_current_line_inline (Ctrl+Enter) -- single-line prompt. run_current_line_silent (F5 / Alt+Enter) -- single-line prompt. The Run-Command dialog's Run button -- same prompt, with the dialog itself as parent. In all cases, No simply cancels (status bar reports Run cancelled.); the editor isn't modified, no placeholder appears. URL-scheme allowlist -- _is_safe_url accepts only http://, https://, and mailto: (case-insensitive). All nine test cases verify cleanly: legitimate web/email links pass; file://, javascript:, ms-cxh:, ftp://, empty strings -- all blocked. Blocked clicks are silently ignored, no popup, no status spam. So [See file](file:///C:/Users/...) is now harmless to misclick. Help -> Usage describes both options in the DISPLAY OPTIONS section, and the PERSISTENCE list now includes Confirm-before-running-commands on/off. Revision stamped 2026-04-29T17:16:49. The About warning is still there as the umbrella reminder; the two new mitigations now actually intervene at the dangerous moments rather than just informing. --Unquote-- -- Carl Distefano cld@xxxxxxxxxx