secured-signal-api

command module
v1.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 21, 2025 License: MIT Imports: 8 Imported by: 0

README

Secure Proxy for Signal REST API

Secure Proxy for Signal Messenger REST API

token-based authentication, endpoint restrictions, placeholders, flexible configuration

🔒 Secure · ⭐️ Configurable · 🚀 Easy to Deploy with Docker

Contents

Getting Started

Prerequisites: You need Docker and Docker Compose installed.

Get the latest version of the docker-compose.yaml file:

services:
  signal-api:
    image: bbernhard/signal-cli-rest-api:latest
    container_name: signal-api
    environment:
      - MODE=normal
    volumes:
      - ./data:/home/.local/share/signal-cli
    restart: unless-stopped
    networks:
      backend:
        aliases:
          - signal-api

  secured-signal:
    image: ghcr.io/codeshelldev/secured-signal-api:latest
    container_name: secured-signal
    environment:
      API__URL: http://signal-api:8080
      SETTINGS__VARIABLES__RECIPIENTS:
        '[+123400002, +123400003, +123400004]'
      SETTINGS__VARIABLES__NUMBER: "+123400001"
      API__TOKENS: '[LOOOOOONG_STRING]'
    ports:
      - "8880:8880"
    restart: unless-stopped
    networks:
      backend:
        aliases:
          - secured-signal-api

networks:
  backend:

And add secure Token(s) to api.tokens. See API TOKENs.

[!IMPORTANT] In this documentation, we use sec-signal-api:8880 as the host for simplicity. Replace it with your actual container/host IP, port, or hostname.

Reverse Proxy

Traefik

Take a look at the traefik implementation:

services:
  secured-signal:
    image: ghcr.io/codeshelldev/secured-signal-api:latest
    container_name: secured-signal
    environment:
      API__URL: http://signal-api:8080
      SETTINGS__VARIABLES__RECIPIENTS:
        '[+123400002,+123400003,+123400004]'
      SETTINGS__VARIABLES__NUMBER: "+123400001"
      API__TOKENS: '[LOOOOOONG_STRING]'
    labels:
      - traefik.enable=true
      - traefik.http.routers.signal-api.rule=Host(`signal-api.mydomain.com`)
      - traefik.http.routers.signal-api.entrypoints=websecure
      - traefik.http.routers.signal-api.tls=true
      - traefik.http.routers.signal-api.tls.certresolver=cloudflare
      - traefik.http.routers.signal-api.service=signal-api-svc
      - traefik.http.services.signal-api-svc.loadbalancer.server.port=8880
      - traefik.docker.network=proxy
    restart: unless-stopped
    networks:
      proxy:
      backend:
        aliases:
          - secured-signal-api

networks:
  backend:
  proxy:
    external: true
NGINX Proxy

This is the NGINX docker-compose.yaml file:

services:
  secured-signal:
    image: ghcr.io/codeshelldev/secured-signal-api:latest
    container_name: secured-signal-api
    environment:
      API__URL: http://signal-api:8080
      SETTINGS__VARIABLES__RECIPIENTS: "[+123400002,+123400003,+123400004]"
      SETTINGS__VARIABLES__NUMBER: "+123400001"
      API__TOKENS: "[LOOOOOONG_STRING]"
    restart: unless-stopped
    networks:
      backend:
        aliases:
          - secured-signal-api

  nginx:
    image: nginx:latest
    container_name: secured-signal-proxy
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      # Load SSL certificates: cert.key, cert.crt
      - ./certs:/etc/nginx/ssl
    ports:
      - "443:443"
      - "80:80"
    restart: unless-stopped
    networks:
      frontend:
      backend:

networks:
  backend:
  frontend:

Create a nginx.conf file in the docker-compose.yaml folder and mount it to etc/nginx/conf.d/default.conf:

server {
    # Allow SSL on Port 443
    listen 443 ssl;

    # Add allowed hostnames which nginx should respond to
    # `_` for any
    server_name localhost;

    ssl_certificate /etc/nginx/ssl/cert.crt;
    ssl_certificate_key /etc/nginx/ssl/cert.key;

    location / {
        # Use whatever network alias you set in the docker-compose file
        proxy_pass http://secured-signal-api:8880;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Fowarded-Proto $scheme;
    }
}

# Redirect HTTP to HTTPs
server {
    listen 80;
    server_name localhost;
    return 301 https://$host$request_uri;
}

Lastly add your cert.key and cert.crt into your certs/ folder and mount it to /etc/nginx/ssl.

Setup

Before you can send messages via Secured Signal API you must first set up Signal rAPI

  1. Register or link a Signal account with signal-cli-rest-api

  2. Deploy secured-signal-api with at least one API token

  3. Confirm you can send a test message (see Usage)

[!TIP] Run setup directly with Signal rAPI. Setup requests via Secured Signal API are blocked. See Blocked Endpoints.

Usage

Secured Signal API provides 3 Ways to Authenticate

Auth

Method Example
Bearer Auth Add Authorization: Bearer API_TOKEN to headers
Basic Auth Add Authorization: Basic BASE64_STRING (api:API_TOKEN)
Query Auth Append @authorization=API_TOKEN to request URL

Example

To send a message to +123400002:

curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer API_TOKEN" -d '{"message": "Hello World!", "recipients": ["+123400002"]}' http://sec-signal-api:8880/v2/send

Advanced

Placeholders

If you are not comfortable / don't want to hardcode your Number for example and/or Recipients in you, may use Placeholders in your Request.

How to use:

Type Example Note
Body {{@data.key}}
Header {{#Content_Type}} - becomes _
Variable {{.VAR}} always uppercase

Where to use:

Type Example
Body {"number": "{{ .NUMBER }}", "recipients": "{{ .RECIPIENTS }}"}
Query http://sec-signal-api:8880/v1/receive/?@number={{.NUMBER}}
Path http://sec-signal-api:8880/v1/receive/{{.NUMBER}}

You can also combine them:

{
	"content": "{{.NUMBER}} -> {{.RECIPIENTS}}"
}
KeyValue Pair Injection

In some cases you may not be able to access / modify the Request Body, in that case specify needed values in the Request Query:

http://sec-signal-api:8880/?@key=value

In order to differentiate Injection Queries and regular Queries you have to add @ in front of any KeyValue Pair assignment.

Supported types include strings, ints, arrays and json dictionaries. See Formatting.

Best Practices

  • Always use API tokens in production
  • Run behind a TLS-enabled Reverse Proxy (Traefik, Nginx, Caddy)
  • Be cautious when overriding Blocked Endpoints
  • Use per-token overrides to enforce least privilege

Configuration

There are multiple ways to configure Secured Signal API, you can optionally use config.yml aswell as Environment Variables to override the config.

Config Files

Config files allow YML formatting and also ${ENV} to get Environment Variables.

To change the internal config file location set CONFIG_PATH in your Environment to an absolute path including the filename.extension. (default: /config/config.yml)

This example config shows all of the individual settings that can be applied:

# Example Config (all configurations shown)
service:
  port: 8880

api:
  url: http://signal-api:8080
  tokens: [token1, token2]

logLevel: info

settings:
  messageTemplate: |
    You've got a Notification:
    {{}} 
    At {{.timestamp}} on {{.date}}.
    Send using {{.NUMBER}}.

  variables:
    number: "+123400001"
    recipients: ["+123400002", "group.id", "user.id"]

  dataAliases: 
    "": [{ alias: "msg", score: 100 }]

  blockedEndpoints:
    - /v1/about
  allowedEndpoints:
    - /v2/send
Token Configs

You can also override the config.yml file for each individual token by adding configs under TOKENS_PATH (default: config/tokens/)

This way you can permission tokens by further restricting or adding Endpoints, Placeholders, etc.

Here is an example:

tokens: [LOOOONG_STRING]

overrides:
  variables: # Disable Placeholder
  blockedEndpoints: # Disable Sending
    - /v2/send
  messageAliases: # Disable Aliases

Environment

Suppose you want to set a new Placeholder NUMBER in your Environment...

environment:
  SETTINGS__VARIABLES__NUMBER: "+123400001"

This would internally be converted into settings.variables.number matching the config formatting.

[!IMPORTANT] Underscores _ are removed during Conversion, Double Underscores __ on the other hand convert the Variable into a nested Object (__ replaced by .)

String To Type

[!TIP] This formatting applies to almost every situation where the only (allowed) Input Type is a string and other Output Types are needed.

If you are using Environment Variables as an example you won't be able to specify an Array or a Dictionary of items, in that case you can provide a specifically formatted string which will be translated into the correct type...

type example
string abc
string +123
int 123
int -123
json {"a":"b","c":"d"}
array(int) [1,2,3]
array(str) [a,b,c]

[!NOTE] If you have a string that should not be turned into any other type, then you will need to escape all Type Denotations, [] or {} (also -) with a \ Backslash (or Double Backslash). An Odd number of Backslashes escape the character in front of them and an Even number leave the character as-is.

Templating

Secured Signal API uses Golang's Standard Templating Library. This means that any valid Go template string will also work in Secured Signal API.

Go's templating library is used in the following features:

This makes advanced Message Templates like this one possible:

settings:
    messageTemplate: |
    {{- $greeting := "Hello" -}}
    {{ $greeting }}, {{ @name }}!
    {{ if @age -}}
    You are {{ @age }} years old.
    {{- else -}}
    Age unknown.
    {{- end }}
    Your friends:
    {{- range @friends }}
    - {{ . }}
    {{- else }}
    You have no friends.
    {{- end }}
    Profile details:
    {{- range $key, $value := @profile }}
    - {{ $key }}: {{ $value }}
    {{- end }}
    {{ define "footer" -}}
    This is the footer for {{ @name }}.
    {{- end }}
    {{ template "footer" . -}}
    ------------------------------------
    Content-Type: {{ #Content_Type }}
    Redacted Auth Header: {{ #Authorization }}

API Token(s)

During Authentication Secured Signal API will try to match the given Token against the list of Tokens inside of these Variables.

api:
  tokens: [token1, token2, token3]

[!IMPORTANT] Using API Tokens is highly recommended, but not mandatory. Some important Security Features won't be available (like default Blocked Endpoints).

[!NOTE] Blocked Endpoints can be reactivated by manually configuring them

Endpoints

Since Secured Signal API is just a Proxy you can use all of the Signal REST API endpoints except for...

Endpoint
/v1/about /v1/unregister
/v1/configuration /v1/qrcodelink
/v1/devices /v1/contacts
/v1/register /v1/accounts

These Endpoints are blocked by default due to Security Risks.

[!NOTE] Matching works by checking if the requested Endpoints starts with a Blocked or an Allowed Endpoint

You can modify Blocked Endpoints by configuring blockedEndpoints in your config:

settings:
  blockedEndpoints: [/v1/register, /v1/unregister, /v1/qrcodelink, /v1/contacts]

You can also override Blocked Endpoints by adding Allowed Endpoints to allowedEndpoints.

settings:
  allowedEndpoints: [/v2/send]
Config (Allow) (Block) Result
allowedEndpoints: ["/v2/send"] unset all 🛑 /v2/send
unset blockedEndpoints: ["/v1/receive"] all /v1/receive 🛑
blockedEndpoints: ["/v2"] allowedEndpoints: ["/v2/send"] /v2* 🛑 /v2/send

Variables

Placeholders can be added under variables and can then be referenced in the Body, Query or URL. See Placeholders.

[!NOTE] Every Placeholder Key will be converted into an Uppercase String. Example: number becomes NUMBER in {{.NUMBER}}

settings:
  variables:
    number: "+123400001",
    recipients: ["+123400002", "group.id", "user.id"]

Message Templates

To customize the message attribute you can use Message Templates to build your message by using other Body Keys and Variables. Use messageTemplate to configure:

settings:
  messageTemplate: |
    Your Message:
    {{@message}}.
    Sent with Secured Signal API.

Message Templates support Standard Golang Templating. Use @data.key to reference Body Keys, #Content_Type for Headers and .KEY for Variables.

Data Aliases

To improve compatibility with other services Secured Signal API provides Data Aliases and a built-in message Alias.

Default `message` Aliases
Alias Score Alias Score
msg 100 data.content 9
content 99 data.description 8
description 98 data.text 7
text 20 data.summary 6
summary 15 data.details 5
details 14 body 2
data.message 10 data 1

Secured Signal API will pick the best scoring Data Alias (if available) to extract set the Key to the correct Value from the Request Body.

Data Aliases can be added by setting dataAliases in your config:

settings:
  dataAliases:
    "@message":
      [
        { alias: "msg", score: 80 },
        { alias: "data.message", score: 79 },
        { alias: "array[0].message", score: 78 },
      ]
    ".NUMBER": [{ alias: "phone_number", score: 100 }]

Use @ for aliasing Body Keys and . for aliasing Variables.

Port

To change the Port which Secured Signal API uses, you need to set service.port in your config. (default: 8880)

Log Level

To change the Log Level set logLevel to: (default: info)

Log Levels
Level
info
debug
warn
error
fatal
dev
Contributing

Found a bug? Want to change or add something? Feel free to open up an Issue or create a Pull Request!

Support

Has this Repo been helpful 👍️ to you? Then consider ⭐️'ing this Project.

:)

Help

Are you having Problems setting up Secured Signal API?
No worries check out the Discussions Tab and ask for help.

We are all Volunteers, so please be friendly and patient.

License

MIT

Logo designed by @CodeShellDev, All Rights Reserved.

This Project is not affiliated with the Signal Foundation.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internals
utils

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL