<?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>DEV Community: nates.dev</title>
    <description>The latest articles on DEV Community by nates.dev (@nates670).</description>
    <link>https://dev.to/nates670</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4100435%2F88245d54-0d0f-4033-b9b9-6143da2d6847.jpg</url>
      <title>DEV Community: nates.dev</title>
      <link>https://dev.to/nates670</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nates670"/>
    <language>en</language>
    <item>
      <title>I Built a P2P Messenger Without a Central Message Database — What I Learned About WebRTC, E2EE and Serverless Architecture</title>
      <dc:creator>nates.dev</dc:creator>
      <pubDate>Sat, 29 Aug 2026 15:27:31 +0000</pubDate>
      <link>https://dev.to/nates670/i-built-a-p2p-messenger-without-a-central-message-database-what-i-learned-about-webrtc-e2ee-and-4jnl</link>
      <guid>https://dev.to/nates670/i-built-a-p2p-messenger-without-a-central-message-database-what-i-learned-about-webrtc-e2ee-and-4jnl</guid>
      <description>&lt;h1&gt;
  
  
  I Built a P2P Messenger Without a Central Message Database — What I Learned About WebRTC, E2EE and Serverless Architecture
&lt;/h1&gt;

&lt;p&gt;I wanted to answer a fairly simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How much of a modern messaging application can be moved away from a traditional centralized backend?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question eventually turned into &lt;strong&gt;OpenChat&lt;/strong&gt;, an open-source P2P communication application built around &lt;strong&gt;WebRTC, browser-native cryptography, MQTT signaling and serverless infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal was not to create a magical "unhackable" messenger or claim that decentralized architecture automatically makes an application private.&lt;/p&gt;

&lt;p&gt;The goal was much more practical:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build a usable real-time communication application where the central infrastructure does not store the users' conversations or act as the transport layer for normal message traffic.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article explains the architecture, the security model, the limitations I encountered, and some of the security problems I discovered while building it.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ The Architecture
&lt;/h2&gt;

&lt;p&gt;The traditional architecture for a messaging application usually looks something like this:&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
   |
   v
Application Server
   |
   v
Database
   |
   v
Application Server
   |
   v
Client B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is responsible for authentication, message routing, storage, synchronization, presence and often many other application-level responsibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenChat takes a different approach.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 MQTT
             Signaling only
              /         \
             v           v
        Browser A     Browser B
             \           /
              \         /
               WebRTC
            DataChannel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual communication between peers is handled by &lt;strong&gt;WebRTC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MQTT is used for &lt;strong&gt;signaling and peer discovery&lt;/strong&gt;. Its purpose is to help the browsers exchange the information required to establish their WebRTC connection.&lt;/p&gt;

&lt;p&gt;Once the connection is established, application data can travel through the &lt;strong&gt;WebRTC DataChannel&lt;/strong&gt; instead of passing through an application server.&lt;/p&gt;

&lt;p&gt;The distinction is important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;MQTT handles signaling. WebRTC handles the actual peer communication.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This also means that losing the signaling connection after a successful WebRTC session does not necessarily mean that the existing DataChannel immediately stops carrying data.&lt;/p&gt;




&lt;h2&gt;
  
  
  ☁️ What Does "Serverless" Actually Mean Here?
&lt;/h2&gt;

&lt;p&gt;One of the things I learned while building this project is that the word &lt;strong&gt;"serverless" can be misleading.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenChat still uses serverless functions.&lt;/p&gt;

&lt;p&gt;Those functions handle operations such as configuration and connection-related information.&lt;/p&gt;

&lt;p&gt;There is also external infrastructure involved in signaling and, depending on the network, TURN relay.&lt;/p&gt;

&lt;p&gt;So a more accurate description is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The application has no central message database or traditional application server responsible for routing normal chat traffic.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's more useful than simply saying "there are no servers."&lt;/p&gt;

&lt;p&gt;Architecture diagrams should describe what the infrastructure actually does rather than what sounds impressive.&lt;/p&gt;




&lt;h1&gt;
  
  
  🌐 Why WebRTC?
&lt;/h1&gt;

&lt;p&gt;WebRTC provides something that is extremely useful for this architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;browser-to-browser communication.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can establish connections between browsers even when both peers are behind NAT, although the exact connectivity depends on the network environment.&lt;/p&gt;

&lt;p&gt;The connection process roughly involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Discovering the other peer.&lt;/li&gt;
&lt;li&gt;Exchanging signaling information.&lt;/li&gt;
&lt;li&gt;Gathering ICE candidates.&lt;/li&gt;
&lt;li&gt;Performing connectivity checks.&lt;/li&gt;
&lt;li&gt;Establishing the WebRTC connection.&lt;/li&gt;
&lt;li&gt;Opening the DataChannel.&lt;/li&gt;
&lt;li&gt;Moving application data through the peer connection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difficult part is that browsers generally cannot simply connect directly to each other without first exchanging information.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;signaling&lt;/strong&gt; comes in.&lt;/p&gt;




&lt;h2&gt;
  
  
  📡 MQTT as the Signaling Layer
&lt;/h2&gt;

&lt;p&gt;I chose &lt;strong&gt;MQTT&lt;/strong&gt; for signaling because it provides a convenient asynchronous communication mechanism between clients.&lt;/p&gt;

&lt;p&gt;The important architectural boundary is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;MQTT is not the message transport.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It helps the peers find and negotiate with each other.&lt;/p&gt;

&lt;p&gt;After the WebRTC connection has been established, the messaging layer uses the peer connection.&lt;/p&gt;

&lt;p&gt;This gives the system a useful separation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Signaling layer
      |
      v
    MQTT
      |
      v
WebRTC negotiation
      |
      v
P2P connection
      |
      v
Application data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔐 End-to-End Encryption
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;P2P communication by itself does not automatically equal end-to-end encryption.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was another important distinction during development.&lt;/p&gt;

&lt;p&gt;WebRTC already provides transport security, but I wanted the application layer to have its own cryptographic model rather than treating the transport protocol as the entire security boundary.&lt;/p&gt;

&lt;p&gt;OpenChat uses browser-native cryptographic APIs for operations including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AES-GCM encryption&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ECDH P-256 key agreement&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ed25519 / ECDSA-P256 signatures&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PBKDF2-SHA-256&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cryptographically secure random number generation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser generates and handles the relevant cryptographic material.&lt;/p&gt;

&lt;p&gt;The basic conceptual model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plaintext
    |
    v
Application encryption
    |
    v
Encrypted message
    |
    v
WebRTC DataChannel
    |
    v
Encrypted message
    |
    v
Application decryption
    |
    v
Plaintext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two layers have different purposes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebRTC protects the transport.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application-level cryptography protects the application data model.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🪪 Identity Is Harder Than Encryption
&lt;/h1&gt;

&lt;p&gt;This turned out to be one of the most interesting security problems.&lt;/p&gt;

&lt;p&gt;Encryption can answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Can someone without the appropriate key decrypt this?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Identity asks a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How do I know that this public key actually belongs to the person I think I'm talking to?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A decentralized application without a central identity authority cannot magically solve that problem.&lt;/p&gt;

&lt;p&gt;Suppose Alice contacts Bob for the first time.&lt;/p&gt;

&lt;p&gt;Alice can establish a cryptographic relationship with the peer and verify future messages against that identity.&lt;/p&gt;

&lt;p&gt;But how does Alice know that the key belongs to the real Bob?&lt;/p&gt;

&lt;p&gt;There is no central authority in the architecture that can answer that question.&lt;/p&gt;

&lt;p&gt;This is why OpenChat uses &lt;strong&gt;persistent cryptographic identities and fingerprint verification.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For an important conversation, the identity fingerprint can be compared through another trusted channel.&lt;/p&gt;

&lt;p&gt;The limitation is intentional and documented:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The application cannot independently verify the real-world identity of a first-time contact.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not something that adding another JavaScript function magically fixes.&lt;/p&gt;

&lt;p&gt;It is a consequence of the &lt;strong&gt;trust model&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  🛡️ The Security Review Was Almost More Interesting Than the Original Development
&lt;/h1&gt;

&lt;p&gt;After getting the main architecture working, I started looking at it from the perspective of an attacker rather than a developer.&lt;/p&gt;

&lt;p&gt;That changed the project considerably.&lt;/p&gt;

&lt;p&gt;Several issues appeared that were not obvious from simply looking at whether messages were encrypted.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔴 Peer Impersonation
&lt;/h2&gt;

&lt;p&gt;One of the most important problems involved trusting identity information supplied by the peer.&lt;/p&gt;

&lt;p&gt;A field such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from: "user123"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't prove that the packet actually originated from the cryptographic identity associated with &lt;code&gt;user123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The application needed to connect the claimed identity to the cryptographic state of the connection.&lt;/p&gt;

&lt;p&gt;This led to additional &lt;strong&gt;identity ownership and key verification checks.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 Replay Attacks
&lt;/h2&gt;

&lt;p&gt;Another problem was &lt;strong&gt;replay&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An attacker doesn't always need to create a new valid message.&lt;/p&gt;

&lt;p&gt;Sometimes they can simply capture a previously valid application message and attempt to make the recipient process it again.&lt;/p&gt;

&lt;p&gt;This becomes particularly interesting for state-changing operations.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;group_update
group_kick
message_edit
message_delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal message ID deduplication mechanism is not necessarily enough for all of these operations.&lt;/p&gt;

&lt;p&gt;A previously valid state-changing message can have different consequences from a duplicate chat message.&lt;/p&gt;

&lt;p&gt;The solution involved tracking &lt;strong&gt;freshness and previously applied operations&lt;/strong&gt; where appropriate.&lt;/p&gt;

&lt;p&gt;During the review I also found that message editing needed its own replay protection rather than relying on the message-ID deduplication used for ordinary messages.&lt;/p&gt;

&lt;p&gt;That was a good example of why security reviews need to follow the actual code paths rather than just checking whether some generic "deduplication" exists somewhere in the application.&lt;/p&gt;




&lt;h1&gt;
  
  
  👥 Group Authorization
&lt;/h1&gt;

&lt;p&gt;Another issue involved &lt;strong&gt;group membership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A user could have an existing DataChannel connection even after their membership state changed.&lt;/p&gt;

&lt;p&gt;That creates an important distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Having a connection is not the same as having permission.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application therefore needs to validate authorization when processing group operations rather than assuming that an established WebRTC connection automatically grants continued access.&lt;/p&gt;

&lt;p&gt;This resulted in explicit membership checks for relevant operations.&lt;/p&gt;




&lt;h1&gt;
  
  
  🚫 Fail-Closed Cryptography
&lt;/h1&gt;

&lt;p&gt;One rule I became increasingly strict about was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If cryptographic verification or encryption fails, don't silently continue with plaintext.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A dangerous pattern in security-sensitive software is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try encryption
if encryption fails:
    send plaintext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That may make the application appear more reliable, but it silently destroys the security guarantee.&lt;/p&gt;

&lt;p&gt;The safer behavior is to &lt;strong&gt;fail the operation and inform the user.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This principle was applied to message encryption and local encrypted storage as well.&lt;/p&gt;

&lt;p&gt;Reliability is important, but silently violating the security model is worse than showing an error.&lt;/p&gt;




&lt;h1&gt;
  
  
  💾 Local Storage Is Part of the Threat Model
&lt;/h1&gt;

&lt;p&gt;It is easy to focus entirely on the network when building an E2EE application.&lt;/p&gt;

&lt;p&gt;But the browser itself is part of the security boundary.&lt;/p&gt;

&lt;p&gt;OpenChat uses encrypted local storage mechanisms for information that needs to persist locally.&lt;/p&gt;

&lt;p&gt;This creates another question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happens if encryption fails?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer should not be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Just store the plaintext instead."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Local storage therefore follows the same &lt;strong&gt;fail-closed principle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project also clears relevant temporary state when sessions end.&lt;/p&gt;




&lt;h1&gt;
  
  
  🔑 API Secrets and the Serverless Boundary
&lt;/h1&gt;

&lt;p&gt;At one point, a configuration endpoint exposed more information to the browser than it actually needed.&lt;/p&gt;

&lt;p&gt;This was an important architectural lesson.&lt;/p&gt;

&lt;p&gt;If a client only needs the result of a server-side computation, the client does not necessarily need the secret used to perform that computation.&lt;/p&gt;

&lt;p&gt;For example, OpenChat used a secret to derive a rotating signaling topic.&lt;/p&gt;

&lt;p&gt;Originally, the client received the secret and performed the derivation.&lt;/p&gt;

&lt;p&gt;That wasn't necessary.&lt;/p&gt;

&lt;p&gt;The architecture was changed so that the serverless function performs the HMAC derivation and sends the resulting topic to the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server
  |
  | secret
  v
Browser
  |
  | HMAC(secret, data)
  v
Topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server
  |
  | HMAC(secret, data)
  v
Browser
  |
  | already-derived topic
  v
MQTT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The P2P architecture remains unchanged, but the secret no longer needs to cross the &lt;strong&gt;server-to-client boundary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a good general rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't send a secret to a client merely because the client can perform a calculation with it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  🧱 Security Headers Still Matter
&lt;/h1&gt;

&lt;p&gt;A P2P architecture doesn't make browser security irrelevant.&lt;/p&gt;

&lt;p&gt;The application still has a normal web attack surface.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;XSS&lt;/li&gt;
&lt;li&gt;DOM injection&lt;/li&gt;
&lt;li&gt;unsafe URL schemes&lt;/li&gt;
&lt;li&gt;clickjacking&lt;/li&gt;
&lt;li&gt;content-type confusion&lt;/li&gt;
&lt;li&gt;excessive browser permissions&lt;/li&gt;
&lt;li&gt;insecure resource loading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenChat therefore uses browser-side input sanitization and security headers including a &lt;strong&gt;Content Security Policy&lt;/strong&gt; and other HTTP security headers.&lt;/p&gt;

&lt;p&gt;One important lesson here was that CSP should be treated as a &lt;strong&gt;defense layer&lt;/strong&gt; rather than a magical guarantee that XSS is impossible.&lt;/p&gt;

&lt;p&gt;The application still needs correct output encoding and safe DOM handling.&lt;/p&gt;




&lt;h1&gt;
  
  
  💥 Resource Exhaustion
&lt;/h1&gt;

&lt;p&gt;P2P does not automatically eliminate denial-of-service problems.&lt;/p&gt;

&lt;p&gt;In fact, some resource-exhaustion problems move directly onto the receiving device.&lt;/p&gt;

&lt;p&gt;A malicious peer could potentially send unexpectedly large or malformed data.&lt;/p&gt;

&lt;p&gt;The application therefore validates incoming DataChannel messages and applies limits to things such as &lt;strong&gt;message and file sizes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is especially important because the browser is now doing work that a traditional backend might otherwise have performed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The client is not just a UI anymore. It is part of the communication infrastructure.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  🔄 NAT Traversal and TURN
&lt;/h1&gt;

&lt;p&gt;Direct P2P connectivity isn't always possible.&lt;/p&gt;

&lt;p&gt;NAT configurations, firewalls and restrictive networks can prevent two peers from establishing a direct path.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;STUN and TURN&lt;/strong&gt; become important.&lt;/p&gt;

&lt;p&gt;A simplified model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        Direct connection

Peer A -------------------- Peer B

              |
              | if unavailable
              v

          TURN relay
         /           \
     Peer A         Peer B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates another important privacy distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;P2P does not mean that an intermediary can never relay traffic.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A TURN server may be involved when direct connectivity fails.&lt;/p&gt;

&lt;p&gt;The application-level encryption model therefore shouldn't depend on the assumption that every packet always travels directly from one device to another.&lt;/p&gt;




&lt;h1&gt;
  
  
  ⚖️ What I Would Not Claim
&lt;/h1&gt;

&lt;p&gt;After working on the project, I became much more careful with security claims.&lt;/p&gt;

&lt;p&gt;I would &lt;strong&gt;not&lt;/strong&gt; describe OpenChat as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Unbreakable&lt;/li&gt;
&lt;li&gt;❌ Completely anonymous&lt;/li&gt;
&lt;li&gt;❌ Immune to metadata analysis&lt;/li&gt;
&lt;li&gt;❌ Automatically secure because it is P2P&lt;/li&gt;
&lt;li&gt;❌ Equivalent to a professionally audited secure messenger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those claims would be misleading.&lt;/p&gt;

&lt;p&gt;There are still architectural limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  📭 No Central Offline Queue
&lt;/h3&gt;

&lt;p&gt;If both users aren't available at the same time, there is no central message database waiting to deliver everything later.&lt;/p&gt;

&lt;h3&gt;
  
  
  📱 Mobile Background Limitations
&lt;/h3&gt;

&lt;p&gt;Mobile browsers can suspend or terminate background activity, which can interrupt P2P communication and notifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  🪪 First-Contact Identity
&lt;/h3&gt;

&lt;p&gt;The application cannot independently establish that a first-time peer corresponds to a specific real-world person.&lt;/p&gt;

&lt;h3&gt;
  
  
  🕵️ Metadata
&lt;/h3&gt;

&lt;p&gt;Removing a central chat database does not mean that all metadata disappears.&lt;/p&gt;

&lt;p&gt;Signaling infrastructure and network infrastructure can still process connection-related information.&lt;/p&gt;

&lt;h3&gt;
  
  
  💻 Client Compromise
&lt;/h3&gt;

&lt;p&gt;If the user's device or browser environment is compromised, application-level encryption cannot magically protect the plaintext after it reaches the endpoint.&lt;/p&gt;

&lt;p&gt;These limitations are part of the &lt;strong&gt;threat model.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🧠 What I Learned
&lt;/h1&gt;

&lt;p&gt;The biggest lesson wasn't actually WebRTC.&lt;/p&gt;

&lt;p&gt;It was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;strong&gt;Architecture changes the security problems rather than eliminating them.&lt;/strong&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;A centralized application gives you problems such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database compromise
Server compromise
Central authentication
Central authorization
Message retention
Server-side abuse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A P2P architecture removes or reduces some of those problems.&lt;/p&gt;

&lt;p&gt;But it introduces or amplifies others:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Peer authentication
Identity verification
Client-side authorization
Untrusted input from peers
Resource exhaustion on endpoints
NAT traversal
Signaling security
Device compromise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no architecture where security problems disappear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They move.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🚀 OpenChat Today
&lt;/h1&gt;

&lt;p&gt;OpenChat is currently an open-source project built around this architecture.&lt;/p&gt;

&lt;p&gt;The source code is available on GitHub, and there is also a live browser demo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/nateS670/OpenChat" rel="noopener noreferrer"&gt;https://github.com/nateS670/OpenChat&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Demo
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;OpenChat:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://openchatt.vercel.app" rel="noopener noreferrer"&gt;https://openchatt.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is released under the &lt;strong&gt;MIT License.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm particularly interested in feedback from people who have experience with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebRTC&lt;/li&gt;
&lt;li&gt;Browser security&lt;/li&gt;
&lt;li&gt;Applied cryptography&lt;/li&gt;
&lt;li&gt;E2EE protocol design&lt;/li&gt;
&lt;li&gt;MQTT&lt;/li&gt;
&lt;li&gt;P2P systems&lt;/li&gt;
&lt;li&gt;NAT traversal&lt;/li&gt;
&lt;li&gt;Threat modeling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most useful feedback isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This looks cool."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Here's where I think your security assumption is wrong."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's exactly the kind of feedback that has already made the project substantially better.&lt;/p&gt;




&lt;h1&gt;
  
  
  💭 Final Thought
&lt;/h1&gt;

&lt;p&gt;I originally started OpenChat because I wanted to experiment with P2P communication in a real application.&lt;/p&gt;

&lt;p&gt;What started as a WebRTC project eventually became an exercise in understanding &lt;strong&gt;trust boundaries.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most important realization was that encryption is only one part of a secure communication system.&lt;/p&gt;

&lt;p&gt;You also have to ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who are you talking to?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is allowed to perform this action?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can an old valid message be replayed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when verification fails?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What information actually needs to leave the server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when the peer itself is malicious?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And perhaps most importantly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What assumptions am I making that the code never actually verifies?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those questions ended up shaping OpenChat much more than the original decision to use WebRTC.&lt;/p&gt;

&lt;p&gt;I'm still working on it, and I'm still expecting people to find things I've missed.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
