No description
  • Scheme 85.6%
  • Shell 8%
  • Makefile 3.5%
  • C 2.8%
Find a file
ober edac02325a
Some checks failed
required-ci / required (push) Failing after 9m39s
Merge pull request #6
2026-09-12 21:19:23 -04:00
.forgejo Use FreeBSD Forgejo CI runner 2026-08-03 14:51:46 -06:00
.jerboa Harden Signal release evidence privacy 2026-06-26 19:58:49 -06:00
docs Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
scripts Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
signal Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
support Set up Forgejo CI/CD policy 2026-08-03 12:55:31 -06:00
tests Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
.gitignore Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
.gitsafeignore Set up Forgejo CI/CD policy 2026-08-03 12:55:31 -06:00
.jerbuild Find FreeBSD iconv library 2026-09-10 17:04:50 -06:00
.jpkgignore Update project configuration and documentation 2026-07-30 12:55:44 -06:00
AGENTS.md docs(agents): add checkout hygiene policy (work dirs under ~/work, cleanup when done) 2026-09-12 19:18:02 -06:00
handoff-corruption.md Update project configuration and documentation 2026-07-30 12:55:44 -06:00
jpkg.lock jpkg: complete binary package setup 2026-07-30 15:10:43 -06:00
jpkg.policy.sexp jpkg: complete binary package setup 2026-07-30 15:10:43 -06:00
jpkg.sexp Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
kimi3.md fix corruption 2026-07-23 18:38:57 -06:00
LICENSE Switch to MIT license 2026-07-21 13:42:19 -06:00
Makefile Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
no-blinky.md Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
README.md Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
SECURITY.md Use jsignal command and state directory 2026-09-10 16:20:45 -06:00
signal.md Update project configuration and documentation 2026-07-30 12:55:44 -06:00
VERSION Use jsignal command and state directory 2026-09-10 16:20:45 -06:00

jerboa-signal

A Signal client written in Jerboa, driving signal-cli (Java) as a JSON-RPC subprocess. All orchestration, arg parsing, and message routing is Jerboa; the Signal protocol itself is delegated to signal-cli's libsignal-service-java.

What it does

Bidirectional bridge between Signal and your shell:

jsignal send [-a +PHONE] RECIPIENT MESSAGE   # send one message, exit
jsignal listen [-a +PHONE]                   # stream inbound as NDJSON
jsignal tui [-a +PHONE]                      # terminal UI shell

-a is required only if multiple Signal accounts are linked to signal-cli.

Prerequisites

  • signal-cli 0.14.5 or newer on PATH (Homebrew may lag; the local scripts/signal-cli-jvm wrapper uses ~/.local/opt/signal-cli-0.14.5). Older 0.14.4.1 builds drop current sealed-sender receive envelopes with getServerGuid(...) must not be null. Release evidence currently reviews exactly signal-cli 0.14.5; a newer installed version must be reviewed before publication.
  • Jerboa build tools. make binary/make install use project-local ./jerbuild first, then .jerboa/bin, then PATH, and download the matching release artifact if none are available.

jsignal expects signal-cli to already be linked. Run this once:

signal-cli link -n "jerboa-signal"

It prints a sgnl://linkdevice?uuid=... URI. On your phone: Signal → Settings → Linked devices → "+" → scan/paste the URI. signal-cli will register the device and exit. Verify:

signal-cli listAccounts

Usage

make install                                   # installs ~/.local/bin/jsignal
jsignal send +15551234567 "hello world"
jsignal listen | jq .                          # pretty-print incoming envelopes
jsignal --trace /tmp/jsignal.trace tui

Without make install, run via the Makefile:

make run ARGS="send +15551234567 hello"
make run ARGS="listen"
make run-tui

The TUI needs the small termbox2 shim:

make tui-shim
./jsignal tui

The TUI opens signal-cli jsonRpc --receive-mode manual, then subscribes with subscribeReceive once the app reader is active, so new Signal events arrive while it is running without racing the UI startup. At startup it preloads active groups and known contacts into the conversation pane. The current interface keeps in-memory conversations for this session, routes incoming messages, edits, reactions, deletes, calls, receipts, and typing events into threads, and sends plain text from the composer to the selected conversation with Enter. Use Up/Down to change the selected conversation. Use Ctrl-N to open the new-message picker, type to filter contacts/groups or enter a +number, and press Enter to select. Use Ctrl-F to search message history across all conversations at once: type a query, use Up/Down to move through matches, and press Enter to jump to the conversation holding that message. Search matches message text and sender, and covers everything received or sent during the current session — history is in-memory only, so it does not include messages from before the TUI was opened. Use Ctrl-D to delete the selected conversation; a y/n prompt confirms first. For a direct contact this calls signal-cli removeContact --forget, which erases all local Signal data for that contact (identity keys, sessions, profile) — use it to remove someone entirely. For a group it just drops the conversation from the list. Deleted conversations are remembered in ~/.jsignal/removed-<account>.txt and stay hidden across relaunches, even though signal-cli (a linked device) keeps re-syncing your phone's contacts. If a removed person messages you again the thread reappears; to remove someone from your Signal account everywhere, delete them on your phone too. Use Ctrl-Q or Ctrl-C to quit.

Debug tracing

Runtime tracing is opt-in and writes a chmod 600 file:

jsignal --trace /tmp/jsignal.trace tui

The default trace records safe internal telemetry such as send phases, timings, byte counts, log backend selection, history replay counts, and encrypted log persistence stages. It does not include message bodies or raw Signal RPC frames.

For maximum debugging detail, including raw RPC frames and signal-cli verbose stderr, use:

jsignal --trace-all /tmp/jsignal.raw.trace tui

That file is sensitive: it can include message content and Signal identifiers. To force the encrypted log to be rewritten after every row while debugging durability issues, use:

jsignal --trace-all /tmp/jsignal.raw.trace --log-persist immediate tui

The default is batch: the encrypted container is rewritten after 64 rows, 1 MiB of new retained payload, or two seconds, whichever comes first, and at clean close. This avoids whole-database work for every message while bounding the crash window. --log-persist immediate forces a rewrite after every row; --log-persist close persists only at clean exit. Operators can lower the bounded batch thresholds with JERBOA_SIGNAL_LOG_BATCH_ROWS, JERBOA_SIGNAL_LOG_BATCH_BYTES, and JERBOA_SIGNAL_LOG_BATCH_MS.

Security

jerboa-signal is not a Signal protocol implementation. It delegates Signal cryptography, account state, server communication, and device linking to signal-cli. The local security boundary is the Jerboa orchestration layer: JSON-RPC framing, trace redaction, attachment export, encrypted local message logs, terminal UI FFI, and process execution.

Before using this on production accounts, read SECURITY.md, docs/threat-model.md, docs/credential-handling.md, and docs/ffi-boundary.md. Run make verify from a clean checkout and preserve make release-evidence output for release candidates. The release evidence includes SBOM/provenance, repeated binary reproducibility, a no-secret binary startup smoke, signal-cli advisory/version review, and explicit status fields for live Signal smokes that require a non-production linked account.

Encrypted message log

The TUI can log every message to an encrypted SQLite database so you keep a copy even when the sender later deletes it ("delete for everyone"). Logs use jsqlite for new logs and a Jerboa-native encrypted container around the database image; contents and metadata are unreadable without your passphrase. Existing SQLCipher logs are still opened through the legacy shim so old history continues to replay.

Enable it:

./jsignal tui                 # prompts: passphrase for encrypted message log

At startup the TUI asks for a passphrase on the controlling terminal. It fails closed if the exact terminal state cannot be saved, echo disabled, or the state restored. Enter one to log; leave it blank to skip logging for that session. For unattended use, prefer --log-key-fd FD with a dedicated inherited FD. JERBOA_SIGNAL_DB_KEY remains available but exposes the secret to the process environment.

The log lives at ~/.jsignal/messages-<account>.db. Each inbound notification and outbound send is one append-only row (with the full raw JSON); remote-deletes, edits, reactions, calls, attachments, and other displayable events are logged as new rows and never overwrite the original.

Retention is bounded by default to 20,000 rows, 128 MiB of retained payload, and 4 MiB per entry; oldest rows are pruned before an insert exceeds the row or payload budget. The limits can be lowered with JERBOA_SIGNAL_LOG_MAX_ROWS, JERBOA_SIGNAL_LOG_MAX_PAYLOAD_BYTES, and JERBOA_SIGNAL_LOG_MAX_ENTRY_BYTES.

To convert an older SQLCipher log to the current jsqlite container format:

jsignal migrate-log           # add -a +PHONE if you use a named account

The converter verifies the new row count before swapping files and keeps the old SQLCipher database beside it as messages-<account>.db.sqlcipher.bak.

Logging happens only while the TUI is open — messages that arrive while it is closed are not captured.

Architecture

┌─────────────────┐  argv      ┌──────────────────┐  NDJSON  ┌────────────┐
│ jsignal         │ ─────────► │ signal/main.ss   │ ───────► │ signal-cli │
│   (CLI argv)    │            │  (subcommands)   │          │  jsonRpc   │
└─────────────────┘            └──────────────────┘ ◄─────── └────────────┘
                                        │           NDJSON          │
                                        ▼                           ▼
                                  stdout / stderr            Signal servers

Modules:

  • signal/main.ss — entry point, argv parsing, subcommand dispatch
  • signal/rpc.ss — JSON-RPC client over signal-cli's stdin/stdout (NDJSON framing, id correlation, notification queue)
  • signal/rpc-actor.ss — long-lived JSON-RPC actor for the TUI
  • signal/cmd-send.ss — send subcommand
  • signal/cmd-listen.ss — listen subcommand
  • signal/cmd-tui.ss — TUI command entry point; see docs/TUI_PLAN.md
  • signal/store.ss — persistent local state (deleted-conversation list)
  • signal/capture.ss — normalizes a notification into an encrypted log row
  • signal/logdb.ss + signal/log_crypto.ss — encrypted log backend
  • signal/tui/ — termbox2 FFI and the first terminal shell

License

Apache-2.0.