salamailer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +43 -0
- package/README.md +159 -0
- package/cli/salamailer.mjs +795 -0
- package/package.json +45 -0
- package/public/js/crypto.js +319 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Salamailer — licensing
|
|
2
|
+
|
|
3
|
+
This repository contains two differently-licensed bodies of work.
|
|
4
|
+
|
|
5
|
+
1. CLIENT CODE — MIT licensed
|
|
6
|
+
The following files, and only these files, are released under the MIT
|
|
7
|
+
License set out below. They are the code that runs on the user's own
|
|
8
|
+
machine (the CLI/MCP client and the browser crypto module), and they are
|
|
9
|
+
published so that anyone can read, audit and verify what handles their
|
|
10
|
+
plaintext and keys:
|
|
11
|
+
|
|
12
|
+
cli/salamailer.mjs
|
|
13
|
+
public/js/crypto.js
|
|
14
|
+
|
|
15
|
+
2. EVERYTHING ELSE — proprietary, all rights reserved
|
|
16
|
+
All other files in this repository, including without limitation the
|
|
17
|
+
server implementation (src/), tests, scripts and documentation, are the
|
|
18
|
+
confidential and proprietary property of Maffen Oy. No licence to use,
|
|
19
|
+
copy, modify or distribute them is granted by this file or by the
|
|
20
|
+
distribution of the MIT-licensed files above.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
MIT License (applies to the files listed in section 1 only)
|
|
24
|
+
|
|
25
|
+
Copyright (c) 2026 Maffen Oy
|
|
26
|
+
|
|
27
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
in the Software without restriction, including without limitation the rights
|
|
30
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
furnished to do so, subject to the following conditions:
|
|
33
|
+
|
|
34
|
+
The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
copies or substantial portions of the Software.
|
|
36
|
+
|
|
37
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Salamailer
|
|
2
|
+
|
|
3
|
+
Zero-knowledge secure messaging for small regulated firms. Send a confidential
|
|
4
|
+
message + files to anyone (no account for the recipient) with true end-to-end
|
|
5
|
+
encryption — the server only ever holds ciphertext and keys it cannot unwrap.
|
|
6
|
+
|
|
7
|
+
**Installing from npm?** This package is the *client* — a zero-dependency CLI
|
|
8
|
+
and MCP server for the hosted service at [salamailer.fi](https://salamailer.fi).
|
|
9
|
+
Encryption happens on your machine; you need an account to send.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g salamailer
|
|
13
|
+
salamailer init --origin https://salamailer.fi --email you@firm.fi
|
|
14
|
+
claude mcp add salamailer -- salamailer mcp # 12 tools for agents
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Jump to [CLI & MCP](#cli--mcp--the-ai-agent-channel) for the full tool surface
|
|
18
|
+
and the agent guardrails. The rest of this file documents the server, which is
|
|
19
|
+
proprietary and not part of the npm package (see `LICENSE`).
|
|
20
|
+
|
|
21
|
+
**Zero npm dependencies.** All integrations (Stripe, Twilio, Brevo/Postmark,
|
|
22
|
+
S3-compatible storage) are implemented with `fetch` + `node:crypto` — there is no
|
|
23
|
+
supply chain to audit beyond Node itself. See `DECISIONS.md` for how the build
|
|
24
|
+
maps to `PLAN.md`, and `DEPLOY.md` for production setup.
|
|
25
|
+
|
|
26
|
+
## Run
|
|
27
|
+
|
|
28
|
+
Requires **Node ≥ 22.5** (built-in `node:sqlite`).
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm start # → http://localhost:3000
|
|
32
|
+
npm test # 146-check e2e suite: crypto, replies, gates, billing, SSO, hardening, CLI/MCP
|
|
33
|
+
npm run hashes # SHA-256 of every served asset (publish for auditors)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
With no configuration, email/SMS print to the console + `data/outbox.log`,
|
|
37
|
+
billing is off (everyone's on trial), and blobs live on local disk. Copy
|
|
38
|
+
`.env.example` → fill in keys → each integration goes live independently.
|
|
39
|
+
|
|
40
|
+
## The security model in one paragraph
|
|
41
|
+
|
|
42
|
+
Your browser generates a fresh AES-256-GCM content key per message and encrypts
|
|
43
|
+
everything (filenames included) before upload. The key travels **only in the URL
|
|
44
|
+
fragment** of the recipient's link — browsers never send fragments to servers. A
|
|
45
|
+
separate, rate-limited second channel (SMS code or passphrase) gates the
|
|
46
|
+
ciphertext download; it is *not* part of the key, so short codes can't be
|
|
47
|
+
brute-forced offline. The sender's copy of each key is wrapped under a vault key
|
|
48
|
+
wrapped under their password — so senders can re-send links, but a full server
|
|
49
|
+
breach yields nothing readable. Full threat model (including its honest limits):
|
|
50
|
+
`/security` in the running app.
|
|
51
|
+
|
|
52
|
+
## Try the full flow
|
|
53
|
+
|
|
54
|
+
1. **Register** (`/register`) — password is stretched locally; it never leaves the browser.
|
|
55
|
+
2. **Compose** (`/compose`) — pick **Passphrase** for a fully-offline demo. Send.
|
|
56
|
+
3. Open the link from the console/`data/outbox.log` in an incognito window,
|
|
57
|
+
enter the passphrase → decrypts in the browser.
|
|
58
|
+
4. **Reply securely** — right on the read page, no account: the reply (text +
|
|
59
|
+
files) is encrypted in the recipient's browser under the sender's reply
|
|
60
|
+
public key, which travelled *inside* the encrypted message. Read it on `/app`.
|
|
61
|
+
5. **Sent** (`/app`): resend link/code, revoke (real delete), export receipt,
|
|
62
|
+
inbox of encrypted replies.
|
|
63
|
+
6. **Account** (`/account`): plan, quotas, Stripe upgrade (when configured);
|
|
64
|
+
team owners also get the admin console — per-member usage, an org
|
|
65
|
+
recipient-domain policy enforced server-side on every channel, and **SSO
|
|
66
|
+
(OIDC)** setup. SSO authenticates the session only; the vault still unlocks
|
|
67
|
+
with the user's password, client-side (`/unlock`) — IdPs never see keys.
|
|
68
|
+
|
|
69
|
+
Verify the zero-knowledge claim yourself: DevTools → Network during a send — all
|
|
70
|
+
request bodies are ciphertext and the `#key` fragment appears in no request. Then
|
|
71
|
+
inspect `data/salamailer.db` and `data/blobs/`.
|
|
72
|
+
|
|
73
|
+
## CLI & MCP — the AI-agent channel
|
|
74
|
+
|
|
75
|
+
`cli/salamailer.mjs` is a zero-dependency CLI that reuses the *same* crypto module
|
|
76
|
+
the web app serves, so plaintext and keys exist only on the sending machine:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
salamailer init --origin https://app… --email you@firm.fi # password prompted, never stored
|
|
80
|
+
salamailer send --to client@x.fi --message "…" --file contract.pdf --burn [--self-relay] [--lang fi|en|sv]
|
|
81
|
+
salamailer list · replies · reply <id> [--save-dir d] · status · contacts · receipt <id> · resend <id> · resend-code <id> · revoke <id>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`--self-relay` finalizes without handing the link to the server, so the content
|
|
85
|
+
key never reaches it even transiently — you deliver the printed link yourself
|
|
86
|
+
(parity with the web compose checkbox).
|
|
87
|
+
|
|
88
|
+
**Saved recipients** give a regular correspondent one *standing* gate passphrase
|
|
89
|
+
instead of a fresh one per message: `salamailer contacts add --to partner@co.fi
|
|
90
|
+
--passphrase "…"`, and every later send to them reuses it automatically (they
|
|
91
|
+
learn it once). The passphrase is stored encrypted under your vault key, synced
|
|
92
|
+
across web/CLI/MCP; the server never reads it. Manage the same list on `/account`.
|
|
93
|
+
|
|
94
|
+
`salamailer mcp` runs it as an **MCP server** (stdio), exposing twelve tools —
|
|
95
|
+
`send_secure_message` (with `self_relay` / `save_passphrase`),
|
|
96
|
+
`list_sent_messages`, `get_delivery_receipt`, `get_account_status`,
|
|
97
|
+
`resend_notification`, `resend_sms_code`, `revoke_message`, `save_contact`,
|
|
98
|
+
`list_contacts`, `delete_contact`, plus `list_replies` / `read_reply` (inbound
|
|
99
|
+
secure replies, decrypted locally) — full parity with the web dashboard's
|
|
100
|
+
sender surface, so any MCP-capable agent can run a complete round trip
|
|
101
|
+
(send → client replies → agent reads the reply) in zero-knowledge mail:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
claude mcp add salamailer -- salamailer mcp
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Transport auth is an **API key** (created on the `/account` page or by `init`;
|
|
108
|
+
stored SHA-256-hashed server-side; can't manage other keys). Agent sends are
|
|
109
|
+
metered against the same plan quotas — agents are senders, senders are who pays.
|
|
110
|
+
The credentials file (`~/.config/salamailer/config.json`, 0600) holds the API key
|
|
111
|
+
and the raw Vault Key: treat it like an SSH private key.
|
|
112
|
+
|
|
113
|
+
**Agent guardrails** (config file / `init` flags) make the MCP a policy
|
|
114
|
+
enforcement point for a prompt-injected agent:
|
|
115
|
+
|
|
116
|
+
- `"hideRecipients": true` (`--hide-recipients`) — privacy mode, strictest:
|
|
117
|
+
the model never sees recipient emails or phone numbers at all. Sends are
|
|
118
|
+
addressed by saved-contact **label** (`contact` arg; resolved locally inside
|
|
119
|
+
the CLI process), tool outputs mask every address, the standing passphrase is
|
|
120
|
+
not echoed, and contact management stays human-only. Implies contactsOnly.
|
|
121
|
+
For teams where even the recipient list is confidential to the AI vendor.
|
|
122
|
+
- `"contactsOnly": true` (`--contacts-only`) — the agent may only
|
|
123
|
+
send to recipients a **human** has saved as contacts; new recipients are
|
|
124
|
+
refused with instructions for out-of-band approval, and the agent channel
|
|
125
|
+
cannot add or remove contacts itself (no self-approval).
|
|
126
|
+
- `"allowedRecipientDomains": […]` (`--allow-domain`) — looser: restrict to
|
|
127
|
+
recipient domains.
|
|
128
|
+
- `"attachmentRoot": "/path"` — attachments may only come from inside this
|
|
129
|
+
directory (default: the cwd where `salamailer mcp` starts).
|
|
130
|
+
|
|
131
|
+
Every send is gated (SMS code / passphrase) and audit-logged server-side, so
|
|
132
|
+
the operator's answer to a mis-send attempt is: "the guardrails blocked it —
|
|
133
|
+
here is the log."
|
|
134
|
+
|
|
135
|
+
## Layout
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
src/
|
|
139
|
+
server.js routing, strict CSP, CSRF origin checks, static serving
|
|
140
|
+
handlers.js API endpoints (auth, messages, gate, billing, webhook)
|
|
141
|
+
config.js all env-driven configuration (see .env.example)
|
|
142
|
+
billing.js entitlements, Stripe REST + webhook signature verification
|
|
143
|
+
storage.js ciphertext blobs: S3 SigV4 adapter or local fs
|
|
144
|
+
notify.js email/SMS providers (console | brevo | postmark | twilio)
|
|
145
|
+
db.js node:sqlite schema (PLAN.md §7.2 + billing)
|
|
146
|
+
security.js scrypt hashing, tokens, HMAC (gates only — never key material)
|
|
147
|
+
ratelimit.js per-IP/user sliding windows
|
|
148
|
+
retention.js expiry sweep + real hard-delete
|
|
149
|
+
audit.js metadata-only audit log
|
|
150
|
+
public/
|
|
151
|
+
js/crypto.js the crypto heart — Web Crypto only, runs on the sender/recipient device
|
|
152
|
+
js/*.js page logic (register, login, compose, app, account, read)
|
|
153
|
+
cli/
|
|
154
|
+
salamailer.mjs CLI + MCP server (stdio JSON-RPC), reuses public/js/crypto.js
|
|
155
|
+
scripts/
|
|
156
|
+
hashes.mjs published build hashes ("verify us, not trust us")
|
|
157
|
+
test/
|
|
158
|
+
e2e.mjs boots the server, drives the real client crypto end-to-end
|
|
159
|
+
```
|