<?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: kijmoshi</title>
    <description>The latest articles on Forem by kijmoshi (@kijmoshi).</description>
    <link>https://forem.com/kijmoshi</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%2F980038%2F8858cff4-9440-4931-b55b-f00665ed42f1.png</url>
      <title>Forem: kijmoshi</title>
      <link>https://forem.com/kijmoshi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kijmoshi"/>
    <language>en</language>
    <item>
      <title>Big Companies steal your data, so I made a secure chat for you.</title>
      <dc:creator>kijmoshi</dc:creator>
      <pubDate>Sat, 14 Mar 2026 19:03:46 +0000</pubDate>
      <link>https://forem.com/kijmoshi/big-companies-steal-your-data-so-i-made-a-secure-chat-for-you-3of8</link>
      <guid>https://forem.com/kijmoshi/big-companies-steal-your-data-so-i-made-a-secure-chat-for-you-3of8</guid>
      <description>&lt;p&gt;Every team has this moment. Someone shares something sensitive in Slack. Someone else says "we shouldn't use this for that." Then comes the mass migration to a different app — one with a privacy policy nobody reads, on servers nobody controls, with an encryption model nobody has verified.&lt;/p&gt;

&lt;p&gt;Your options? Signal (great, but mobile-first and phone-number-tied). Telegram (not E2EE by default). Matrix/Element (self-hostable, but the setup is an afternoon project and the UI is a lot). Discord (no). A team email thread (no).&lt;/p&gt;

&lt;p&gt;All of them are either closed-source, require a phone number, live on someone else's server, or need a UI that feels like a full-time job to operate.&lt;/p&gt;

&lt;p&gt;I wanted something simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;zypher work alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Terminal. Encrypted. Your server or mine.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://zypher.kijmoshi.xyz" rel="noopener noreferrer"&gt;Zypher&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 30-Second Version
&lt;/h2&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; zypher-chat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spin up a server — one command clones, configures, and daemonizes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zypher server quickstart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zypher new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a conversation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ zypher work alice

  ╭─ alice ──────────────────────────────────╮
  │  alice  🟢 online                        │
  ╰──────────────────────────────────────────╯

  you › hey
  alice › hey! how's the project going?
  you › encrypting everything, obviously
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No phone number. No app store. No browser extension. Just a terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Use Signal / Telegram / Matrix?
&lt;/h2&gt;

&lt;p&gt;You can. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signal needs a phone number.&lt;/strong&gt; You can't register without a real mobile number. If your phone dies or you change numbers, you lose access. For a dev tool or internal team chat, that's an unnecessary external dependency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matrix/Element is powerful but heavy.&lt;/strong&gt; Spinning up a Synapse server is a real infrastructure project. The client is feature-rich but not CLI-native.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Discord is a product, not a protocol.&lt;/strong&gt; Not self-hostable, not open-source, not private.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It lives on someone else's server.&lt;/strong&gt; All of these options require trusting a third party with your data. Even if the app is open-source, the deployment isn't. Zypher is self-hosted by design — you control the infrastructure and the data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zypher is for when you want a terminal, a server you control, and actual end-to-end encryption — not a SaaS product that happens to call itself private.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Registration and Key Generation
&lt;/h3&gt;

&lt;p&gt;When you register on a server, Zypher generates two keypairs &lt;strong&gt;locally&lt;/strong&gt; — before anything touches the network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;Ed25519 identity key&lt;/strong&gt; — your permanent signing key. Every message you send is signed with it. Recipients verify the signature. The server cannot forge a message from you without your private key.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;X25519 pre-key&lt;/strong&gt; — a Diffie-Hellman key for key exchange. Other clients use it to compute a shared secret without ever transmitting one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The private keys never leave your machine. They're stored in &lt;code&gt;~/.zypher/config.json&lt;/code&gt;, mode 0600. The server only receives your public keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  DM Encryption — per-message ephemeral ratchet
&lt;/h3&gt;

&lt;p&gt;When you send a DM to Alice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Zypher generates a fresh &lt;strong&gt;ephemeral X25519 keypair&lt;/strong&gt; for this message only.&lt;/li&gt;
&lt;li&gt;It performs ECDH between your ephemeral private key and Alice's public pre-key, then runs the result through &lt;strong&gt;HKDF-SHA256&lt;/strong&gt; to derive a 256-bit AES key.&lt;/li&gt;
&lt;li&gt;The message is encrypted with &lt;strong&gt;AES-256-GCM&lt;/strong&gt; using a random IV.&lt;/li&gt;
&lt;li&gt;The ephemeral public key (the "ratchet key") ships alongside the ciphertext so Alice can reproduce the same ECDH and decrypt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The ratchet key is unique per message. Every message gets a fresh keypair. The server sees only ciphertext, nonces, and public keys — never plaintext. Even if a session key were ever compromised, past messages stay safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Group Encryption — shared key, individually wrapped
&lt;/h3&gt;

&lt;p&gt;Groups use a shared symmetric key, but that key is never sent in the clear. When you create a group or invite someone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A random 256-bit &lt;strong&gt;group key&lt;/strong&gt; is generated on your machine.&lt;/li&gt;
&lt;li&gt;For &lt;em&gt;each&lt;/em&gt; member, that group key is encrypted separately using their X25519 pre-key (same ECDH → AES-256-GCM flow as DMs).&lt;/li&gt;
&lt;li&gt;Each member receives their own encrypted bundle. The server stores these bundles but cannot read the group key inside any of them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Group messages are then encrypted with the shared group key + AES-256-GCM. Admin operations — kick, promote, rename — trigger a re-key: a new group key is generated and re-encrypted for each remaining member.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Server Actually Stores
&lt;/h3&gt;

&lt;p&gt;The server is a &lt;strong&gt;dumb relay with a mailbox&lt;/strong&gt;. It stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usernames, bcrypt password hashes, and &lt;em&gt;public&lt;/em&gt; keys (identity + pre-key)&lt;/li&gt;
&lt;li&gt;Encrypted ciphertext for offline delivery — it cannot read any of it&lt;/li&gt;
&lt;li&gt;Group membership and role metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It never sees plaintext. It never sees private keys. It never stores session keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client A                    Server                  Client B
   │                           │                         │
   │── ECDH(ephemeral,         │                         │
   │      B's pre-key)         │                         │
   │── AES-256-GCM(msg) ──────▶│── store ciphertext ────▶│
   │                           │                         │── ECDH(my pre-key,
   │                           │                         │      ratchet_key)
   │                           │                         │── AES-256-GCM decrypt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Things I Deliberately Didn't Build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No phone numbers.&lt;/strong&gt; Registration is username + password. Nothing else required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No centralized server.&lt;/strong&gt; &lt;code&gt;zypher server quickstart&lt;/code&gt; clones the server repo, configures it, and runs it as a local daemon. You own the infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No cloud accounts.&lt;/strong&gt; There's no "Zypher cloud." Every deployment is independent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No browser client.&lt;/strong&gt; It's a CLI. The terminal is the UI. If you want a GUI chat app, you're looking for Signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No multi-device key sync.&lt;/strong&gt; Keys live on the device that generated them. Offline messages are queued on the server and delivered once — then gone from the queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No metadata minimization theater.&lt;/strong&gt; The server knows who is messaging whom (routing requires it). What it doesn't know is what they're saying.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Ghost Mode
&lt;/h2&gt;

&lt;p&gt;Type &lt;code&gt;/ghost&lt;/code&gt; in any chat session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  !! GHOST MODE !!  This erases everything.

  Type GHOST to confirm: █
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm with the word &lt;code&gt;GHOST&lt;/code&gt;, and Zypher deletes &lt;code&gt;~/.zypher/&lt;/code&gt; entirely — config, private keys, account data, everything. The process exits immediately. Nothing remains on disk.&lt;/p&gt;

&lt;p&gt;One command, scorched-earth wipe. Useful when you need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Offline Messaging
&lt;/h2&gt;

&lt;p&gt;If Alice is offline when you send her a message, the server queues the encrypted ciphertext. When Alice reconnects, all queued messages are delivered in order and the queue is cleared. The server holds them only as long as it has to — it cannot read them, and they're deleted once delivered.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;The server is ~600 lines of JavaScript:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;express&lt;/code&gt; + &lt;code&gt;helmet&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;HTTP API with security headers and rate limiting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;socket.io&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Real-time message delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;node:sqlite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User accounts, offline queues, group metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bcrypt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Password hashing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jsonwebtoken&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Session authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The client is a single &lt;code&gt;index.js&lt;/code&gt; (~1,600 lines). No UI framework. One runtime dependency: &lt;code&gt;socket.io-client&lt;/code&gt;. All crypto — X25519, Ed25519, AES-256-GCM, HKDF — is Node.js built-in (&lt;code&gt;node:crypto&lt;/code&gt;). Nothing custom, nothing exotic.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
npm i &lt;span class="nt"&gt;-g&lt;/span&gt; zypher-chat

&lt;span class="c"&gt;# Quickstart: clones, configures, and launches a local server as a daemon&lt;/span&gt;
zypher server quickstart

&lt;span class="c"&gt;# Register on it&lt;/span&gt;
zypher new

&lt;span class="c"&gt;# Open a chat&lt;/span&gt;
zypher &amp;lt;servername&amp;gt; &amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requires Node.js 22+. Works on Linux, macOS, and Windows.&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/real-kijmoshi/zypher" rel="noopener noreferrer"&gt;github.com/real-kijmoshi/zypher&lt;/a&gt;&lt;br&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/zypher-chat" rel="noopener noreferrer"&gt;npmjs.com/package/zypher-chat&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Website: &lt;a href="https://zypher.kijmoshi.xyz" rel="noopener noreferrer"&gt;zypher.kijmoshi.xyz&lt;/a&gt;&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>security</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Got Tired of "How Do I Send You This File?" So I Built QuickDrop</title>
      <dc:creator>kijmoshi</dc:creator>
      <pubDate>Fri, 13 Mar 2026 21:03:06 +0000</pubDate>
      <link>https://forem.com/kijmoshi/i-got-tired-of-how-do-i-send-you-this-file-so-i-built-quickdrop-39i1</link>
      <guid>https://forem.com/kijmoshi/i-got-tired-of-how-do-i-send-you-this-file-so-i-built-quickdrop-39i1</guid>
      <description>&lt;h1&gt;
  
  
  I Got Tired of "How Do I Send You This File?" So I Built QuickDrop
&lt;/h1&gt;

&lt;p&gt;Every developer has been there. You need to send a folder to someone. Maybe it's a build artifact, a dataset, some photos from your phone, or a project directory.&lt;/p&gt;

&lt;p&gt;Your options? Upload to Google Drive and wait. Zip it and email it (if it's under 25 MB). Set up an S3 bucket. Use WeTransfer. Bluetooth. AirDrop (Apple only). USB stick.&lt;/p&gt;

&lt;p&gt;All of them require either an account, a cloud service, a size limit, or the same ecosystem.&lt;/p&gt;

&lt;p&gt;I wanted something dumber and faster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;machine-a&lt;span class="nv"&gt;$ &lt;/span&gt;quickdrop receive
machine-b&lt;span class="nv"&gt;$ &lt;/span&gt;quickdrop send ./photos &lt;span class="nt"&gt;--to&lt;/span&gt; AB3C4E7E:X9K2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. Files transferred. No accounts. No uploads. Encrypted end-to-end.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://www.npmjs.com/package/quickdrop" rel="noopener noreferrer"&gt;QuickDrop&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 30-Second Version
&lt;/h2&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; quickdrop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the &lt;strong&gt;receiving&lt;/strong&gt; machine, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;quickdrop receive ./downloads
&lt;span class="go"&gt;
 quickdrop - receive
--------------------------------------------
  LAN code       AB3C4E7E:X9K2  &amp;lt;- same network
  Tunnel code    quick-fox-42:X9K2  &amp;lt;- anywhere
  Password       X9K2  &amp;lt;- new every session
--------------------------------------------
  Waiting for sender...  (Ctrl+C to stop)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the &lt;strong&gt;sending&lt;/strong&gt; machine, paste the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;quickdrop send ./photos &lt;span class="nt"&gt;--to&lt;/span&gt; AB3C4E7E:X9K2
&lt;span class="go"&gt;
  ✓ DSC_001.jpg  (4.20 MB, 11.3 MB/s)
  ✓ DSC_002.jpg  (3.85 MB, 12.1 MB/s)
  ✓ raw/DSC_003.cr2  (18.40 MB, 11.8 MB/s)
--------------------------------------------
  ✓ 3 files  -  26.4 MB  -  2.3s  -  11.7 MB/s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One command on each side. The combined code &lt;code&gt;AB3C4E7E:X9K2&lt;/code&gt; encodes both the address and a one-time password, so the sender has zero prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Use scp/rsync?
&lt;/h2&gt;

&lt;p&gt;You absolutely can. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;scp/rsync need SSH access.&lt;/strong&gt; The receiver needs an SSH server running, a user account, firewall rules, open ports. QuickDrop needs nothing pre-configured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;scp doesn't work across NATs.&lt;/strong&gt; If you and your friend are on different WiFi networks, you can't &lt;code&gt;scp&lt;/code&gt; to them without port forwarding or a VPN. QuickDrop automatically tunnels through NAT when needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;scp requires knowing the IP.&lt;/strong&gt; QuickDrop encodes the address into a short alphanumeric code you can read over the phone or paste into Slack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;QuickDrop is for the use case where you want to send files to &lt;em&gt;a person&lt;/em&gt;, not deploy to &lt;em&gt;a server&lt;/em&gt;.&lt;/p&gt;




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

&lt;p&gt;There's no relay server storing your files. Here's what happens under the hood:&lt;/p&gt;

&lt;h3&gt;
  
  
  On the Same Network (LAN) -- direct, peer-to-peer
&lt;/h3&gt;

&lt;p&gt;QuickDrop opens a TCP server on the receiver and encodes its local IP + port into an 8-character base-36 code. The sender decodes the code, connects directly via TCP, and streams files at full LAN speed. No internet involved. This is true peer-to-peer -- nothing between the two machines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Across the Internet -- relayed, but end-to-end encrypted
&lt;/h3&gt;

&lt;p&gt;When sender and receiver are on different networks, QuickDrop spins up a &lt;a href="https://github.com/localtunnel/localtunnel" rel="noopener noreferrer"&gt;localtunnel&lt;/a&gt; WebSocket bridge. The receiver gets a public &lt;code&gt;*.loca.lt&lt;/code&gt; subdomain. The sender connects to that subdomain over WSS. Traffic is &lt;em&gt;relayed&lt;/em&gt; through localtunnel infrastructure, but every byte is AES-256-GCM encrypted before it enters the tunnel -- the relay sees only ciphertext and never stores anything.&lt;/p&gt;

&lt;p&gt;This is not peer-to-peer in the strict sense (there's no NAT hole-punching), but your files are never readable by the relay, and nothing is persisted on any server.&lt;/p&gt;

&lt;p&gt;Both codes (LAN and tunnel) are displayed automatically. Use whichever one works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encryption
&lt;/h3&gt;

&lt;p&gt;All file data is encrypted with &lt;strong&gt;AES-256-GCM&lt;/strong&gt; before it hits the wire. The 4-character session password is never transmitted -- both sides independently derive a 256-bit key using &lt;strong&gt;scrypt&lt;/strong&gt; (N=16384, r=8, p=1), then prove they have the same key via an &lt;strong&gt;HMAC-SHA256&lt;/strong&gt; handshake.&lt;/p&gt;

&lt;p&gt;Each message uses a fresh random IV, so identical files produce different ciphertext every transfer. GCM's authentication tag means any tampering or corruption is detected and the transfer aborts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wire format per message:
[4-byte length prefix][12-byte IV][16-byte auth tag][ciphertext]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is a 4-character password breakable? In theory, yes -- there are ~1.7 million possible 4-char alphanumeric passwords. But scrypt with these parameters takes ~100ms per attempt, making brute-force cost ~47 hours of CPU time. And the password changes every session. For a file transfer tool (not a bank vault), this is a practical tradeoff between security and usability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Things I Deliberately Didn't Build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No accounts.&lt;/strong&gt; No signup, no login, no API keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No cloud storage.&lt;/strong&gt; On LAN, files go directly between machines. Over the internet, traffic is relayed through an encrypted tunnel -- but nothing is stored on the relay, and the relay can't decrypt your data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No GUI.&lt;/strong&gt; It's a CLI tool. If you want a GUI, you're looking for AirDrop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No configuration files.&lt;/strong&gt; It works out of the box. The only persistent state is an optional contacts file at &lt;code&gt;~/.quickdrop/contacts.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No file size limits.&lt;/strong&gt; It streams in 64 KB chunks. Send 10 bytes or 10 GB.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contacts: The One Piece of State
&lt;/h2&gt;

&lt;p&gt;After a transfer, the sender is prompted to save the receiver as a named contact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Save "192.168.1.42" as a contact? Name (Enter to skip): workpc
  ✓ Saved! Next time: quickdrop send ./folder --to workpc
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quickdrop send ./logs &lt;span class="nt"&gt;--to&lt;/span&gt; workpc &lt;span class="nt"&gt;--password&lt;/span&gt; X9K2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Contacts are a flat JSON file. You can manage them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;quickdrop contacts              &lt;span class="c"&gt;# list&lt;/span&gt;
quickdrop contacts rename workpc office
quickdrop contacts remove office
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;The entire thing is ~500 lines of JavaScript across 7 files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cli.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Arg parsing, subcommand dispatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/sender.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Connect, authenticate, stream files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/receiver.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TCP server, WS bridge, auth, write files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utils/framing.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Length-prefixed message framing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utils/crypto.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AES-256-GCM, scrypt, HMAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utils/codes.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IP-to-base36 encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utils/contacts.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contact book persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two runtime dependencies: &lt;a href="https://github.com/websockets/ws" rel="noopener noreferrer"&gt;&lt;code&gt;ws&lt;/code&gt;&lt;/a&gt; for WebSocket support and &lt;a href="https://github.com/localtunnel/localtunnel" rel="noopener noreferrer"&gt;&lt;code&gt;localtunnel&lt;/code&gt;&lt;/a&gt; for NAT traversal. Everything else is Node.js built-in (&lt;code&gt;net&lt;/code&gt;, &lt;code&gt;crypto&lt;/code&gt;, &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;).&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; quickdrop

&lt;span class="c"&gt;# Or just run it once&lt;/span&gt;
npx quickdrop &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requires Node.js 18+. Works on Linux, macOS, and Windows.&lt;/p&gt;

&lt;p&gt;Test it on your own machine with two terminals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1&lt;/span&gt;
quickdrop receive ./test-output

&lt;span class="c"&gt;# Terminal 2 (paste the code from terminal 1)&lt;/span&gt;
quickdrop send ./some-folder &lt;span class="nt"&gt;--to&lt;/span&gt; &amp;lt;CODE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source code: &lt;a href="https://github.com/real-kijmoshi/quickdrop" rel="noopener noreferrer"&gt;github.com/real-kijmoshi/quickdrop&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/quickdrop" rel="noopener noreferrer"&gt;npmjs.com/package/quickdrop&lt;/a&gt;&lt;br&gt;
Website: &lt;a href="https://quickdrop.kijmoshi.xyz" rel="noopener noreferrer"&gt;quickdrop.kijmoshi.xyz&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;QuickDrop is ISC licensed. It scratches a very specific itch: sending files to someone without ceremony. If that itch is yours too, give it a shot.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>node</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>kijmoshi</dc:creator>
      <pubDate>Sat, 14 Feb 2026 19:27:24 +0000</pubDate>
      <link>https://forem.com/kijmoshi/-kkj</link>
      <guid>https://forem.com/kijmoshi/-kkj</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/kijmoshi" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F980038%2F8858cff4-9440-4931-b55b-f00665ed42f1.png" alt="kijmoshi"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/kijmoshi/whiteboard-for-github-copilot-cli-challenge-318f" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;whiteboard made in 2 hours for GitHub Copilot CLI Challenge&lt;/h2&gt;
      &lt;h3&gt;kijmoshi ・ Feb 14&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#devchallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#githubchallenge&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#cli&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#githubcopilot&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>whiteboard made in 2 hours for GitHub Copilot CLI Challenge</title>
      <dc:creator>kijmoshi</dc:creator>
      <pubDate>Sat, 14 Feb 2026 18:42:45 +0000</pubDate>
      <link>https://forem.com/kijmoshi/whiteboard-for-github-copilot-cli-challenge-318f</link>
      <guid>https://forem.com/kijmoshi/whiteboard-for-github-copilot-cli-challenge-318f</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built a &lt;strong&gt;real-time multi-user whiteboard web app&lt;/strong&gt; that allows multiple users to draw on the same canvas simultaneously. The app uses a shared online database so every stroke is synchronized instantly across all connected clients.&lt;/p&gt;

&lt;p&gt;The goal of this project was to explore real-time collaboration and see how quickly I could go from an idea to a working prototype using modern tooling. It's designed to be simple, lightweight, and easy to extend with features like rooms, undo, or AI-generated drawings in the future.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Github Repo&lt;/strong&gt; &lt;a href="https://github.com/real-kijmoshi/whiteboard/tree/main" rel="noopener noreferrer"&gt;https://github.com/real-kijmoshi/whiteboard/tree/main&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://coop-whiteboard.netlify.app" rel="noopener noreferrer"&gt;https://coop-whiteboard.netlify.app&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;The demo shows multiple users drawing at the same time, with brush color and size controls. Strokes appear live for everyone without refreshing the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;

&lt;p&gt;Using GitHub Copilot CLI made development significantly faster and smoother. I used it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate boilerplate project setup with Vite&lt;/li&gt;
&lt;li&gt;Help write Firebase configuration and real-time sync logic&lt;/li&gt;
&lt;li&gt;Quickly prototype canvas drawing functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of constantly searching for syntax or examples, I could describe what I wanted in natural language and get usable code immediately. This helped me stay focused on the project idea rather than getting stuck on implementation details. It allowed me to polish UI and UX without constantly worrying about pixel-perfect CSS.&lt;/p&gt;

&lt;p&gt;Copilot CLI felt like having a coding partner in the terminal that was especially useful when working with unfamiliar APIs like real-time database listeners and canvas rendering.&lt;/p&gt;

&lt;p&gt;Overall, it made experimentation faster and encouraged me to try features I might not have attempted otherwise. I was able to build this entire project in approximately 2 hours.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
  </channel>
</rss>
