---
title: "Off-the-Record — Mango 3.1 Grove extension"
description: "Experimental OTRv3 private-message encryption with OTRv2 interoperability, fingerprints, and SMP verification."
url: "https://mangoirc.chat/extensions/otr"
language: "en"
---

[← All extensions](https://mangoirc.chat/extensions)

Interoperability · Root

# Off-the-Record

by [Mango](https://mangoirc.chat/)

Experimental OTRv3 private-message encryption with OTRv2 interoperability, fingerprints, and SMP verification.

Install in Mango 3.1 61 KB · Mango 3.1

Version

0.1.5

Runs as

Server Root

Grove API

1

Updated

2026-09-06

Requested access

## 8 permissions

- **Add slash commands**Registers its own commands and can show native command-result cards.
- **Filter received messages**Can transform or hide incoming message text before Mango displays it.
- **Filter sent messages**Can transform or block outgoing message text before it reaches IRC.
- **messages.protect**A Grove permission declared by this extension.
- **query.send**A Grove permission declared by this extension.
- **query.open**A Grove permission declared by this extension.
- **Keep local data**Stores extension data on this device in its own isolated space.
- **Keep secrets in Keychain**Stores private values in a Keychain namespace reserved for this extension.

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 ` /otr ` commands, with no tab, pane, or web UI.

## Security warning

The bundled [` arlolra/otr `](https://github.com/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:

```text
/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:

```sh
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.json**app.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": []
}
```
