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-clilocally. 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-cliLinux / Docker: upstream repo has instructions — github.com/AsamK/signal-cli. Most distros need Java 17+ as a prereq.
Verify the install:
signal-cli --version2. 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 registerSignal sends an SMS with a verification code. Complete registration:
signal-cli -u +15555550100 verify 123456One-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" +15555550200You 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 signalOr directly in your project .env file:
SIGNAL_CLI_NUMBER=+15555550100
SIGNAL_RECIPIENT=+15555550200
SIGNAL_CLI_BINARY=signal-cliBoth 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 signalSecurity 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:
- The argv is static —
signal-cli -u <number> send -m <body> -- <recipient>— and never concatenates LLM input into flags - Both phone numbers are validated against a strict E.164 regex at factory time (sender) and on every send (recipient)
- The message body is passed via a distinct
-mslot, never concatenated into a command string - The
--terminator before the recipient blocks leading-dash flag-injection attempts even on validated input - 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-cliisn't on PATH. Runwhich signal-clito confirm; fix the install, or setSIGNAL_CLI_BINARYto the absolute path- The env vars aren't set. Minara needs
SIGNAL_CLI_NUMBERandSIGNAL_RECIPIENTboth 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
- Env vars:
SIGNAL_CLI_NUMBER,SIGNAL_RECIPIENT,SIGNAL_CLI_BINARY - Source:
apps/agent/src/messaging/signal.ts signal-clidocs: github.com/AsamK/signal-cli