Skip to content

Security: pwn-all/smp-plugin

Security

SECURITY.md

Security Policy

Reporting a Vulnerability

Do not open a public GitHub issue for security vulnerabilities.

Email the maintainers at the address in the repository's contact info. Include:

  • A clear description of the vulnerability
  • Steps to reproduce (proof-of-concept preferred)
  • Affected version and browser version
  • Your assessment of severity and exploitability

You can expect an initial response within 48 hours and a fix or mitigation plan within 14 days for critical issues.


Security Model

SMP operates on a detect-then-encrypt model. Its security properties rest on two guarantees:

  1. Detected raw secret strings are replaced before supported send actions continue. The message body sent to the chat app should contain {SC:...} placeholders and a SecNote URL instead of the original password, token, or key text.

  2. The SecNote server cannot read notes. The AES-256-GCM key is embedded in the SecNote URL fragment. URL fragments are not sent to the SecNote server in HTTP requests, so the SecNote server stores only ciphertext.

The generated SecNote URL is a bearer secret until first successful view or expiry. When the extension posts that URL into a chat, the messaging platform receives and stores the full message text, including the URL fragment key. SMP therefore protects against posting the original raw secret value, but it does not hide the generated SecNote link from the messaging platform. With the original unmodified SecNote server software, a viewed note is deleted; forwarding the same URL after that point should not reveal the note again.

What SMP protects against

  • Accidental posting of raw credential strings into supported chat composers
  • SecNote server operators reading note plaintext
  • Backend scanners and logs ingesting the original token/password format rather than a SecNote bearer URL

What SMP does not protect against

  • Messaging platform operators, compromised chat backends, link scanners, or anyone else who can read and redeem the generated SecNote URL before the intended recipient
  • An attacker with code execution in the browser (malicious extension, XSS, compromised OS)
  • A compromised SecNote server that returns a malicious URL or tampers with the view-count (one-time view is server-enforced, not cryptographically enforced)
  • Recipients forwarding the SecNote URL before first view, or copying/re-posting the revealed plaintext after viewing
  • Side-channel attacks against the editor DOM before the extension intercepts the send action

Cryptographic Design

Note encryption

Primitive Parameter Purpose
AES-GCM 256-bit key, 96-bit random IV Symmetric encryption of note plaintext
SHA-256 View token: SHA-256(encryptionKey) sent to server as a deletion proof
Ed25519 Server keypair Server signs every API response; extension verifies before trusting

The key derivation is direct random generation (crypto.getRandomValues). There is no password-based KDF; until first successful view or expiry, the link itself is the secret.

Proof-of-Work

Before creating a note the client must find a nonce such that:

SHA-256(challenge ‖ nonce)[0..difficulty_bits] === 0…0

Difficulty is set by the server per-request (up to 28 bits). This limits the rate at which an unauthenticated client can create notes.

Trust On First Use (TOFU)

On first connection to a new SecNote endpoint the extension:

  1. Receives the server's Ed25519 public key in the API response.
  2. Displays the key fingerprint to the user for manual verification.
  3. Stores the accepted key in chrome.storage.sync under the endpoint URL.
  4. On all subsequent requests, rejects responses signed by a different key.

Users may revoke trust and re-pin a new key through Settings → Trusted servers.

Optional pubkey pinning in links

When Pin pubkey is enabled, the server's Ed25519 public key is embedded in every generated SecNote URL. A receiving client can verify that the ciphertext was served by the expected key, detecting a server substitution attack.


Isolation Architecture

The extension uses two distinct JavaScript execution contexts per tab:

MAIN world (interceptor.js)

Runs with direct access to the page's JavaScript environment. Required to intercept fetch, XMLHttpRequest, WebSocket, sendBeacon, and the WhatsApp Lexical editor's internal React state. Because it runs in the page context it shares the same trust level as page scripts.

Attack surface: A page-level XSS, compromised page script, or malicious extension with DOM access can read secrets directly from the composer before SMP acts. SMP cannot protect against that class of attacker. The MAIN-world interceptor may briefly receive plaintext replacement values, but this does not materially increase exposure beyond the fact that the page already controls the editor. SMP limits blast radius by keeping encryption keys and SecNote API calls in the service worker, validating message origins, and keeping replacement state temporary.

Isolated world (bridge.js)

Runs in Chrome's content script sandbox. Has access to the DOM but not to page JavaScript. Holds no encryption keys (those are computed in the background service worker). Communicates with the page only via window.postMessage.

All postMessage communication between the two worlds uses the __secnote namespace prefix and is guarded by event.source === window or event.origin === window.location.origin checks so that cross-origin frames cannot inject commands.

Service worker (background.js)

Isolated from all page content. Holds the settings store and performs all cryptographic operations. Content scripts communicate with it only through chrome.runtime.sendMessage, which Chrome brokers — page scripts cannot send messages to the service worker directly.


postMessage Security

All cross-world and cross-frame postMessage calls in the extension follow these rules:

Direction Guard
MAIN ← isolated event.source === window check before handling any __secnote message
Isolated ← MAIN event.origin === window.location.origin at the top of the listener, before dispatching to any pending handler
Isolated → MAIN Target origin explicitly set to window.location.origin; never '*'
Parent ↔ iframe e.origin checked against known origin; e.source used for reply targeting

Failure to enforce these checks would allow a cross-origin frame (ad iframe, embedded widget) to resolve pending replacement handles with arbitrary data or inject text into the editor.


Content Security Considerations

Secret detector scope

dist/secretDetector.js runs in both worlds and scans editor text. It deliberately skips:

  • Fields with autocomplete="current-password" or type="password" (personal credentials)
  • Email input fields
  • Strings already containing {SC:…} placeholders (no re-encryption loops)
  • Strings matching common test/example/placeholder patterns (reduces false positives)

Data never sent to the SecNote server

  • The plaintext note content in unencrypted form
  • The AES-256-GCM encryption key
  • Browser cookies or session tokens
  • Any content outside the protected editor field

Data sent to the SecNote server

  • AES-256-GCM ciphertext of the note
  • SHA-256(encryptionKey) as a view token (cannot be reversed to recover the key)
  • The PoW solution for the server-issued challenge
  • A TTL preference

Data sent to the messaging platform

  • {SC:...} placeholders replacing the original secret values
  • The generated SecNote URL, including the fragment key
  • Any non-secret text the user typed in the same composer

Known Limitations

  1. One-time view is not cryptographically enforced. The server is trusted to delete the note after the first successful retrieval. With the original unmodified SecNote server software, forwarding a URL after it has been viewed should only forward an already-consumed link. A malicious, modified, or compromised server could serve the ciphertext multiple times instead. Pubkey pinning provides partial mitigation by verifying server identity.1

  2. The generated SecNote link is visible to the chat platform. The URL grants access to the encrypted note until it is viewed or expires. A chat operator, logged transcript, link scanner, or compromised backend can consume the one-time note before the recipient. After a successful view on the original unmodified server software, re-posting the URL should not reveal the note again; copying or re-posting the revealed plaintext remains outside SMP's control.

  3. WhatsApp Lexical integration requires MAIN-world access. The WhatsApp Web editor uses a React-backed Lexical state that rejects synthetic DOM writes. The extension must access the internal Lexical node tree directly, which requires page-context execution. This is an intentional trade-off for correctness.

  4. Auto-replace mode disables the confirmation step. When enabled, secrets are replaced without user review. This is convenient but removes the last human checkpoint before transmission. Recommended only in controlled environments.

  5. Custom site content scripts are injected dynamically. When a user adds a custom site, the extension requests optional host permission for that origin and injects content scripts. The user is prompted by Chrome's permission system before this happens.

  6. The secret detector runs client-side. Secrets are never sent for remote analysis. This also means detection accuracy depends entirely on the bundled pattern set and entropy thresholds — novel secret formats may not be detected.


Supported Versions

Version Supported
2.x (current) Yes
< 2.0 No

Footnotes

  1. For higher-assurance deployments, organizations should operate their own SecNote server under their existing access-control, logging, retention, and incident-response policies. Self-hosting reduces reliance on a third-party server operator, but one-time deletion remains server-enforced rather than cryptographically enforced.

There aren't any published security advisories