Interoperability · Root
Off-the-Record
Experimental OTRv3 private-message encryption with OTRv2 interoperability, fingerprints, and SMP verification.
- Version
- 0.1.5
- Runs as
- Server Root
- Grove API
- 1
- Updated
- 2026-09-06
Requested access
8 permissions
Network access
No internet access
Its manifest declares no external hosts. Grove’s sandbox blocks network requests outside the package.
From the extension
README
Off-the-Record — a Grove Root
This experimental server-scoped Root adds live one-to-one Off-the-Record Messaging to Mango. It negotiates OTRv3 when both peers support it and keeps OTRv2 enabled as an interoperability fallback. The implementation is pure JavaScript/TypeScript; it does not use WASM.
The Root provides:
- OTRv3 authenticated key exchange and encrypted private messages;
- OTRv2 fallback for older IRC clients and plugins;
- long-lived DSA identity storage in Mango’s per-network Keychain namespace;
- peer fingerprints with explicit out-of-band trust;
- Socialist Millionaires’ Protocol (SMP) questions and answers;
- bounded IRC fragmentation and hidden protocol-control messages;
- a host-owned fail-closed policy from negotiation until the user explicitly ends the session;
- native Grove cards plus
/otrcommands, with no tab, pane, or web UI.
Security warning
The bundled arlolra/otr 0.2.16 library
explicitly warns that it has not been properly vetted by security researchers.
This integration has not received an independent security audit either. Treat
it as experimental interoperability software, not as a security guarantee for
high-risk conversations.
Use TLS as well. Verify a fingerprint over another authenticated channel or use SMP before treating a peer as authenticated. OTR protects message contents, not IRC metadata such as the server, account, nicknames, timing, or message sizes.
Why OTRv3/v2, not OTRv4
OTRv3 is the newest version supported by the mature JavaScript implementation used here and remains interoperable with established IRC OTR plugins. OTRv2 is retained only as a fallback. OTRv4 is a different protocol and is not wire compatible with v2/v3; there is no comparably established pure-JavaScript IRC implementation suitable for embedding in a Grove extension today. Adding a partial or novel v4 implementation would be riskier than shipping a clearly labelled v3 interoperability extension.
Commands
Run commands inside a private query, or supply a nickname where shown:
/otr start [nickname]
/otr end [nickname]
/otr status [nickname]
/otr verify [nickname]
/otr unverify [nickname]
/otr smp <secret>
/otr answer <secret>
/otr forget [nickname]
/otr help
The Root has no separate UI. Start it for the IRC connection, enable autostart,
then run /otr start in a private query. Mango displays negotiation results,
fingerprints, trust state, and common controls as native cards in that query;
/otr status brings the current card back at any time. Incoming SMP questions
also appear as cards. Enter SMP secrets with /otr smp or /otr answer because
native cards intentionally do not expose extension-owned password fields. These
commands are handled locally by Grove and are never sent as IRC messages.
verify records trust only for the peer fingerprint currently shown. If that
fingerprint changes, trust is cleared and Mango asks for verification again.
SMP secrets are held only for the live operation and are never persisted.
Session and history behavior
OTR is a live session protocol. The long-lived identity and remembered fingerprints survive relaunch, but ephemeral encryption state does not. Restart OTR after either client reconnects, relaunches, or reports that the session ended. A bouncer may retain the encrypted wire packets, but an old session’s ciphertext generally cannot be decrypted after its ephemeral state is gone. Mango hides OTR protocol packets during server history/replay and never lets replayed history advance the live cryptographic state or emit network responses.
Mango’s device-local startup cache retains up to 200 visible rows per conversation. For a successfully protected row it stores both the decrypted display text and the authoritative IRC ciphertext inside its SQLCipher-encrypted database. This keeps recent decrypted messages readable after relaunch without trying to feed old ciphertext into a new forward-secret OTR session. If a bouncer or IRC server returns ciphertext that was not already decrypted and cached by this device, Mango hides it. Cross-buffer previews and native local notifications run after the full inbound filter chain: decrypted messages use their final display text, while hidden protocol frames remain silent.
Diagnostics
The running Root writes privacy-safe activity to Mango’s Grove Console. It reports runtime and identity readiness, command handling, protocol-frame flow, negotiation transitions, protected-message transforms, SMP state, and fail-closed policy updates. It includes peer nicknames and numeric frame counts where useful, but never logs message text, OTR packet contents, fingerprints, private keys, or SMP secrets.
Use the instance’s Console view for one IRC connection, or Mango’s global Grove
Console to compare both sides of a test. A successful negotiation ends with an
Authenticated key exchange completed entry on each client.
When a peer changes nickname, Mango moves its protected-query policy to the new query and resets the host status to negotiating. Restart OTR with the new nick. Until the owning Root successfully protects messages again or the user explicitly ends protection, Mango blocks plaintext in that query.
Fail-closed behavior and compatibility
As soon as negotiation starts, the Root publishes a messages.protect policy
for the query. Mango then requires this exact extension instance to return a
terminal protected result for incoming and outgoing private messages. If the
Root is stopped, crashes, misses its deadline, loses its filters, or cannot
load its Keychain identity, the host blocks the message instead of silently
sending plaintext. Ending the session explicitly removes that query policy.
This uses the additive Grove protection API. Existing extensions and ordinary inbound/outbound filters keep their previous behavior and 150 ms fail-open deadline. A protecting Root receives the separate two-second security-filter deadline, and its protected result terminates the chain so another formatter cannot rewrite ciphertext.
Build and test
Node 18 or later is required:
npm install
npm test
npm run build
npm run package
The tests establish real in-memory OTRv3 and OTRv2 sessions, exchange
fragmented UTF-8 ciphertext, and complete SMP using the pinned upstream
implementation. npm run package creates otr.grove from the verified dist/
contents. The build copies the upstream browser distribution and its MPL-2.0
license into the package; it does not bundle the optional Node native worker.
manifest.jsonapp.mango.grove.otr
{
"grove": 1,
"id": "app.mango.grove.otr",
"name": "Off-the-Record",
"version": "0.1.5",
"description": "Experimental OTRv3 private-message encryption with OTRv2 interoperability, fingerprints, and SMP verification.",
"author": {
"name": "Mango",
"url": "https://mangoirc.chat"
},
"readme": "README.md",
"entry": "index.html",
"presentation": {
"surfaces": [
"headless"
],
"scope": "server"
},
"capabilities": [
"commands.register",
"messages.filter.inbound",
"messages.filter.outbound",
"messages.protect",
"query.send",
"query.open",
"storage",
"storage.secure"
],
"hostPermissions": []
}