<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jonathan Park</title>
    <description>The latest articles on Forem by Jonathan Park (@jonathan_park_998f01b7215).</description>
    <link>https://forem.com/jonathan_park_998f01b7215</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3815906%2Feebb6c4d-b0ae-47c6-bb4e-506277206c75.png</url>
      <title>Forem: Jonathan Park</title>
      <link>https://forem.com/jonathan_park_998f01b7215</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jonathan_park_998f01b7215"/>
    <language>en</language>
    <item>
      <title>Why I Built a Password Manager That Never Touches the Internet</title>
      <dc:creator>Jonathan Park</dc:creator>
      <pubDate>Tue, 10 Mar 2026 04:25:36 +0000</pubDate>
      <link>https://forem.com/jonathan_park_998f01b7215/why-i-built-a-password-manager-that-never-touches-the-internet-3lan</link>
      <guid>https://forem.com/jonathan_park_998f01b7215/why-i-built-a-password-manager-that-never-touches-the-internet-3lan</guid>
      <description>&lt;p&gt;Every password manager I've used wants me to create an account. Sync to their cloud. Trust their servers. Pay a subscription.&lt;/p&gt;

&lt;p&gt;And then one day, LastPass gets breached. Again.&lt;/p&gt;

&lt;p&gt;I don't have anything against cloud-based password managers — they solve a real problem. But I kept thinking: what if you just... didn't need any of that?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Premise
&lt;/h2&gt;

&lt;p&gt;Most people don't need cross-device sync for every password. They need a secure place to store credentials that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doesn't require an account&lt;/li&gt;
&lt;li&gt;Doesn't phone home&lt;/li&gt;
&lt;li&gt;Works offline&lt;/li&gt;
&lt;li&gt;Can't be breached on a server because there is no server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built Password Notebook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero-Server Architecture
&lt;/h2&gt;

&lt;p&gt;Here's what "never touches the internet" means concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No backend.&lt;/strong&gt; It's a static site. HTML, CSS, JavaScript. That's the entire stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No API calls.&lt;/strong&gt; Open your network tab. Nothing goes out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No analytics.&lt;/strong&gt; No tracking pixels, no Google Analytics, no telemetry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No account.&lt;/strong&gt; You open it and start using it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your passwords are encrypted and stored in your browser's &lt;code&gt;localStorage&lt;/code&gt;. They exist on your device and nowhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Encryption Works
&lt;/h2&gt;

&lt;p&gt;When you set a master password, the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Derives a cryptographic key from your password using &lt;strong&gt;PBKDF2&lt;/strong&gt; with a high iteration count&lt;/li&gt;
&lt;li&gt;Encrypts your password vault using &lt;strong&gt;AES-GCM&lt;/strong&gt; via the Web Crypto API&lt;/li&gt;
&lt;li&gt;Stores the encrypted blob in &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified — the actual implementation has more safeguards&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyMaterial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;masterPassword&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PBKDF2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deriveKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deriveKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PBKDF2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;keyMaterial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;encrypt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;decrypt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All crypto is done via the browser's native &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API" rel="noopener noreferrer"&gt;Web Crypto API&lt;/a&gt; — no third-party crypto libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-offs (Honestly)
&lt;/h2&gt;

&lt;p&gt;This approach has real limitations, and I think it's important to be upfront about them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you give up:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No sync.&lt;/strong&gt; Your passwords live on one device, in one browser. Clear your browser data and they're gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No recovery.&lt;/strong&gt; Forget your master password? There's no "forgot password" flow. No server means no recovery email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No autofill.&lt;/strong&gt; This isn't a browser extension. You copy-paste.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorage isn't perfect.&lt;/strong&gt; It can be cleared by the browser, and it's accessible to JavaScript on the same origin. The encryption mitigates the latter, but it's worth knowing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero attack surface on the network.&lt;/strong&gt; No server to breach. No API to exploit. No database dump to worry about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No trust required.&lt;/strong&gt; You can read the source. It's all client-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant setup.&lt;/strong&gt; Open the page, set a master password, start saving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works offline.&lt;/strong&gt; Once loaded, it doesn't need a connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who Is This For?
&lt;/h2&gt;

&lt;p&gt;Not everyone. If you need sync across 5 devices, use Bitwarden (it's great and open source).&lt;/p&gt;

&lt;p&gt;Password Notebook is for people who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want a simple, local credential store on a single machine&lt;/li&gt;
&lt;li&gt;Don't trust cloud services with their passwords&lt;/li&gt;
&lt;li&gt;Need something for a shared computer where installing software isn't an option&lt;/li&gt;
&lt;li&gt;Want a secondary/backup password reference&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Free, no sign-up, no download: &lt;a href="https://passwords.impulsestudios.cc" rel="noopener noreferrer"&gt;passwords.impulsestudios.cc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your data stays on your device. I literally can't see it even if I wanted to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're curious about the implementation or want to audit the crypto, the source is all in the browser. View source and read it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Built a Markdown-to-Rich-Text Converter Because Copy-Paste Shouldn't Be This Hard</title>
      <dc:creator>Jonathan Park</dc:creator>
      <pubDate>Tue, 10 Mar 2026 04:25:23 +0000</pubDate>
      <link>https://forem.com/jonathan_park_998f01b7215/i-built-a-markdown-to-rich-text-converter-because-copy-paste-shouldnt-be-this-hard-p8h</link>
      <guid>https://forem.com/jonathan_park_998f01b7215/i-built-a-markdown-to-rich-text-converter-because-copy-paste-shouldnt-be-this-hard-p8h</guid>
      <description>&lt;p&gt;If you write in Markdown, you've felt this pain.&lt;/p&gt;

&lt;p&gt;You craft a perfectly formatted document — headers, bold text, code blocks, lists — then you need to paste it into Google Docs. Or Slack. Or an email. Or a CMS with a rich text editor.&lt;/p&gt;

&lt;p&gt;So you copy. You paste. And you get... a wall of plain text with asterisks and hash symbols staring back at you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workaround Tax
&lt;/h2&gt;

&lt;p&gt;The usual workarounds are embarrassing for 2026:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Render in a preview, copy from there&lt;/strong&gt; — Sometimes works. Mostly doesn't preserve formatting cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export to HTML, open in browser, copy from browser&lt;/strong&gt; — Three steps too many.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Just reformat it manually&lt;/strong&gt; — No.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a paid app&lt;/strong&gt; — For... copying and pasting?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I kept running into this during my own workflow. I write everything in Markdown. READMEs, docs, blog drafts, notes. But half the people I work with live in Google Docs and Notion. Every time I needed to share something, I'd lose 5 minutes reformatting.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I Built MarkdownPaste
&lt;/h2&gt;

&lt;p&gt;The idea is dead simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paste or type your Markdown&lt;/li&gt;
&lt;li&gt;Click "Copy as Rich Text"&lt;/li&gt;
&lt;li&gt;Paste into any rich text editor with full formatting&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No account. No download. No electron app eating 400MB of RAM.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Actually Works
&lt;/h2&gt;

&lt;p&gt;Under the hood, this uses the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API" rel="noopener noreferrer"&gt;Clipboard API&lt;/a&gt; — specifically, the ability to write multiple MIME types to the clipboard at once.&lt;/p&gt;

&lt;p&gt;When you click "Copy," the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parses the Markdown&lt;/strong&gt; into an HTML string (using a lightweight parser)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates a &lt;code&gt;ClipboardItem&lt;/code&gt;&lt;/strong&gt; with both &lt;code&gt;text/html&lt;/code&gt; and &lt;code&gt;text/plain&lt;/code&gt; representations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writes to the clipboard&lt;/strong&gt; via &lt;code&gt;navigator.clipboard.write()&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClipboardItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;plainText&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: most rich text editors (Google Docs, Outlook, Slack, Notion) check for &lt;code&gt;text/html&lt;/code&gt; on the clipboard first. If it's there, they render it as formatted text. If not, they fall back to &lt;code&gt;text/plain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By writing both, you get rich formatting where it's supported and clean plaintext everywhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Gotchas
&lt;/h3&gt;

&lt;p&gt;A few things I learned building this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;navigator.clipboard.write()&lt;/code&gt; requires a secure context&lt;/strong&gt; (HTTPS) and a user gesture (click). You can't just fire it on page load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some browsers are stricter than others.&lt;/strong&gt; Firefox had partial support for a while. It's solid now, but I added a fallback using &lt;code&gt;document.execCommand('copy')&lt;/code&gt; with a hidden contenteditable div for older browsers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code blocks need special handling.&lt;/strong&gt; If you just dump &lt;code&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/code&gt; into the clipboard, some editors strip the formatting. Adding inline styles as a fallback helps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What It Handles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Headers, bold, italic, strikethrough&lt;/li&gt;
&lt;li&gt;Ordered and unordered lists&lt;/li&gt;
&lt;li&gt;Code blocks with syntax hints&lt;/li&gt;
&lt;li&gt;Links and images&lt;/li&gt;
&lt;li&gt;Tables&lt;/li&gt;
&lt;li&gt;Blockquotes&lt;/li&gt;
&lt;li&gt;Nested formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It runs entirely in the browser. No server. Your text never leaves your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;It's free, no sign-up required: &lt;a href="https://tools.impulsestudios.cc" rel="noopener noreferrer"&gt;tools.impulsestudios.cc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you write in Markdown and regularly need to paste into rich text editors, this saves a few minutes every time. Nothing revolutionary — just one less paper cut in the workflow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of the Impulse Studios free tools collection. If you find a bug or want a feature, open an issue or just tell me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
