MINARA

Signal

Privacy-focused alerts via the local signal-cli subprocess — no SDK, no Meta, no third-party relay.

🟢 Runtime-ready — Signal has no HTTP API, so Minara shells out to signal-cli locally. Send-only: Signal's protocol doesn't support editing messages, so the helper buffers tokens and sends once at finalize time.

What you get

  • End-to-end encrypted alerts via your own Signal account
  • No business-account requirement, no third-party relay
  • 4 096-char limit per message (helper default)
  • Per-message recipient override via channel — must be an E.164 number you're allowed to message

What you need first

signal-cli is a Java-based Signal client that handles the registration, key material, and send pipeline. Minara doesn't ship it — you install it once on the host and give Minara a phone number.

If signal-cli isn't on the host's PATH when Minara boots, the signal provider is silently omitted from the gateway map (the agent still starts, other providers still work). This makes Signal opt-in without breaking the boot flow for everyone else.

Setup

1. Install signal-cli

macOS (Homebrew):

brew install signal-cli

Linux / Docker: upstream repo has instructions — github.com/AsamK/signal-cli. Most distros need Java 17+ as a prereq.

Verify the install:

signal-cli --version

2. Register the sender number

Use a phone number you own that's not already linked to a Signal account (or unlink it from the Signal app first):

signal-cli -u +15555550100 register

Signal sends an SMS with a verification code. Complete registration:

signal-cli -u +15555550100 verify 123456

One-time step — the keys are cached on disk for future sends.

3. Send a test from the shell (sanity check)

Before wiring Minara, confirm signal-cli itself works:

signal-cli -u +15555550100 send -m "hello" +15555550200

You should see the message on the recipient's Signal app within a second. If this fails, Minara won't fix it — fix signal-cli first.

4. Configure Minara

minara auth messaging add signal

Or directly in your project .env file:

SIGNAL_CLI_NUMBER=+15555550100
SIGNAL_RECIPIENT=+15555550200
SIGNAL_CLI_BINARY=signal-cli

Both numbers must be in strict E.164 format — leading +, country code, digits only, no spaces or dashes. Minara re-validates the recipient on every send_message() call so a bad override can't slip through.

SIGNAL_CLI_BINARY is optional; set it to an absolute path if you installed signal-cli somewhere outside the default PATH lookup.

5. Test through Minara

minara auth messaging test signal

Security posture — why Signal is handled differently

Signal spawns a subprocess, which is in CLAUDE.md §4a Bucket B territory. The apps/agent/src/messaging/signal.ts module is exempt from guardCommand / detectJail because:

  1. The argv is static — signal-cli -u <number> send -m <body> -- <recipient> — and never concatenates LLM input into flags
  2. Both phone numbers are validated against a strict E.164 regex at factory time (sender) and on every send (recipient)
  3. The message body is passed via a distinct -m slot, never concatenated into a command string
  4. The -- terminator before the recipient blocks leading-dash flag-injection attempts even on validated input
  5. The child spawns with shell: false — no shell interpretation of special characters

No LLM-visible string ever reaches the argv. If you look at the source, this is the "Bucket B" annotation in the module's doc comment.

Overriding the recipient

send_message({
  provider: "signal",
  channel: "+15555559999",
  text: "Critical: position liquidation imminent",
})

Troubleshooting

Minara doesn't mount a signal provider at boot

  • signal-cli isn't on PATH. Run which signal-cli to confirm; fix the install, or set SIGNAL_CLI_BINARY to the absolute path
  • The env vars aren't set. Minara needs SIGNAL_CLI_NUMBER and SIGNAL_RECIPIENT both non-empty for the factory to return a gateway

"Rate limit exceeded (429)"

  • Signal has aggressive per-sender rate limits for automated use. Don't spam — space alerts out, or combine multiple events into one message

"Invalid E.164 format"

  • Both numbers must be +<country><number> with digits only. No spaces, no dashes, no parentheses. Minara rejects (202) 555-1234 — use +12025551234

"Unregistered user"

  • The recipient isn't on Signal, or their account has been deleted / unlinked. You can't send to a phone number unless Signal knows about it

Reference

On this page