Security & Encryption
Nexus Broker is designed around a zero-trust encryption model. The broker never possesses your private key and cannot read the contents of your webhook payloads. This page details the cryptographic algorithms, data flows, and security controls that protect your data.
Threat Model
Nexus Broker is designed to protect against the following threat actors and attack vectors:
- Man-in-the-middle (MITM) — All communication is encrypted with TLS 1.3. HSTS is enforced with a minimum one-year max-age. Payload encryption provides a second layer independent of transport security.
- Server compromise — An attacker with full access to the Nexus Broker infrastructure cannot decrypt stored payloads. Private keys are never transmitted to or stored on Nexus Broker servers.
- Insider threats — Employees and operators of Nexus Broker cannot read webhook contents. Encryption happens at ingestion time with user-supplied public keys.
- Database breach — Stored payloads contain only ciphertext, wrapped keys, and metadata. Without the user's private key, the data is cryptographically unrecoverable.
- Replay attacks — Each encrypted message includes a unique IV and authentication tag. Messages are identified by ID and sequence number, preventing replay.
What Nexus Broker does not protect against: a compromised private key on the consumer side, a compromised webhook slug leaked to an attacker (allowing them to inject messages), or denial-of-service attacks at the network layer.
Zero-Trust Architecture
The core security property of Nexus Broker is that the service operates as an opaque relay. The broker's role is limited to:
- Receiving raw payloads at the ingestion endpoint
- Encrypting them with the user's public key
- Storing the encrypted payload in the queue
- Serving the encrypted payload to authenticated consumers
At no point does the broker possess the private key required to decrypt the payload. The private key never leaves your infrastructure. This means that even a complete compromise of Nexus Broker's servers, database, and backups yields no access to plaintext webhook data.
Encryption Algorithms
RSA-2048 Hybrid Encryption
RSA-2048 hybrid encryption combines the convenience of RSA with the performance of symmetric encryption. The process uses a Key Encapsulation Mechanism (KEM) and a Data Encapsulation Mechanism (DEM):
- KEM — Generate a random 256-bit AES key. Encrypt this key with RSA-OAEP (SHA-256) using the webhook's registered RSA-2048 public key.
- DEM — Encrypt the payload with AES-256-GCM using the random AES key and a random 96-bit IV. The GCM mode produces a 128-bit authentication tag.
- The stored message contains the ciphertext, IV, authentication tag, and the RSA-encrypted AES key.
This scheme is widely compatible and supported by every major crypto library. The trade-off is no forward secrecy — if the RSA private key is compromised, all historically encrypted messages can be decrypted.
X25519 ECIES
X25519 ECIES (Elliptic Curve Integrated Encryption Scheme) provides forward secrecy through ephemeral key exchange:
- Generate an ephemeral X25519 keypair for this message
- Perform ECDH between the ephemeral private key and the recipient's X25519 public key to produce a shared secret
- Derive a 256-bit symmetric key from the shared secret using HKDF-SHA256 with the ephemeral public key as info
- Encrypt the payload with AES-256-GCM using the derived key and a random 96-bit IV
- Store the ciphertext, IV, authentication tag, and the ephemeral public key
Each message uses a unique ephemeral key, so compromising the long-term private key does not allow decryption of past messages. This is forward secrecy.
Algorithm Comparison
| Property | RSA-2048 | X25519 |
|---|---|---|
| Encryption overhead | ~256 bytes (encrypted AES key) | ~32 bytes (ephemeral public key) |
| Forward secrecy | No | Yes |
| Key size | 2048-bit (public: ~392 bytes PEM) | 256-bit (public: ~92 bytes PEM) |
| Max payload (direct) | N/A (hybrid — unlimited) | N/A (ECIES — unlimited) |
| Library compatibility | Universal | Modern libraries (OpenSSL 1.1.0+, Node 10+) |
| Plan availability | All plans | Pro and Enterprise |
Encryption Flow (Ingestion)
When a webhook payload arrives at the ingestion endpoint:
- The raw HTTP body is read into memory (subject to plan size limits)
- The webhook's registered public key and algorithm are loaded from the database
- A random 256-bit AES key and 96-bit IV are generated using crypto-secure random
- The payload is encrypted with AES-256-GCM using the AES key, producing ciphertext and a 128-bit authentication tag
- For RSA-2048: the AES key is encrypted with RSA-OAEP (SHA-256). For X25519: an ephemeral keypair is generated, ECDH is performed, and the AES key is derived via HKDF-SHA256
- The ciphertext, IV, tag, and key material (encrypted AES key or ephemeral public key) are written to Firestore
- The ephemeral keys and plaintext AES key are zeroed from memory
Decryption Flow (Consumption)
When your consumer dequeues a message, it must decrypt the payload locally:
RSA-2048 Decryption (Node.js)
import { privateDecrypt, createDecipheriv } from "node:crypto";
function decryptRSA(encrypted, privateKeyPem: string) {
const privateKey = Buffer.from(privateKeyPem);
const aesKey = privateDecrypt(
{ key: privateKey, padding: 1, oaepHash: "sha256" },
Buffer.from(encrypted.encryptedKey, "base64"),
);
const decipher = createDecipheriv(
"aes-256-gcm",
aesKey,
Buffer.from(encrypted.iv, "base64"),
);
decipher.setAuthTag(Buffer.from(encrypted.tag, "base64"));
return Buffer.concat([
decipher.update(Buffer.from(encrypted.ciphertext, "base64")),
decipher.final(),
]).toString("utf-8");
}RSA-2048 Decryption (Python)
import base64
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives import hashes
def decrypt_rsa(encrypted, private_key_path):
with open(private_key_path, "rb") as f:
private_key = load_pem_private_key(f.read(), password=None)
aes_key = private_key.decrypt(
base64.b64decode(encrypted["encryptedKey"]),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
aesgcm = AESGCM(aes_key)
plaintext = aesgcm.decrypt(
nonce=base64.b64decode(encrypted["iv"]),
data=base64.b64decode(encrypted["ciphertext"])
+ base64.b64decode(encrypted["tag"]),
)
return plaintext.decode("utf-8")X25519 Decryption (Node.js)
import { diffieHellman, createDecipheriv, createHash } from "node:crypto";
import { generateKeyPairSync, createECDH } from "node:crypto";
function decryptX25519(encrypted, privateKeyPem: string) {
// Derive shared secret using your private key + ephemeral public key
const ecdh = createECDH("x25519");
ecdh.setPrivateKey(
extractRawPrivateKey(privateKeyPem),
);
const sharedSecret = ecdh.computeSecret(
Buffer.from(encrypted.ephemeralPublicKey, "base64"),
);
// Derive AES key via HKDF-SHA256
const aesKey = createHash("sha256")
.update(sharedSecret)
.update(Buffer.from(encrypted.ephemeralPublicKey, "base64"))
.digest();
// Decrypt with AES-256-GCM
const decipher = createDecipheriv(
"aes-256-gcm",
aesKey,
Buffer.from(encrypted.iv, "base64"),
);
decipher.setAuthTag(Buffer.from(encrypted.tag, "base64"));
return Buffer.concat([
decipher.update(Buffer.from(encrypted.ciphertext, "base64")),
decipher.final(),
]).toString("utf-8");
}X25519 Decryption (Python)
import base64, hashlib
from cryptography.hazmat.primitives.asymmetric.x25519 import (
X25519PrivateKey, X25519PublicKey,
)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def decrypt_x25519(encrypted, private_key_path):
with open(private_key_path, "rb") as f:
private_key = load_pem_private_key(f.read(), password=None)
ephemeral_pub = X25519PublicKey.from_public_bytes(
base64.b64decode(encrypted["ephemeralPublicKey"])
)
shared_secret = private_key.exchange(ephemeral_pub)
aes_key = hashlib.sha256(
shared_secret
+ base64.b64decode(encrypted["ephemeralPublicKey"])
).digest()
aesgcm = AESGCM(aes_key)
plaintext = aesgcm.decrypt(
nonce=base64.b64decode(encrypted["iv"]),
data=base64.b64decode(encrypted["ciphertext"])
+ base64.b64decode(encrypted["tag"]),
)
return plaintext.decode("utf-8")Key Management
Proper key management is critical to the security of your webhook data. Follow these guidelines:
- Generation — Always generate keypairs locally using OpenSSL or your language's crypto library. Never use online key generators.
- Storage — Store private keys in a secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager). Never commit private keys to source control.
- Rotation — Rotate keys periodically by generating a new keypair, updating the webhook with the new public key, and decrypting any remaining queued messages with the old key before decommissioning it.
- Access control — Limit access to private keys to the minimum set of services and people that need it. Use IAM policies and audit access logs.
- Destruction — When a private key is no longer needed, securely wipe it. Most secrets managers support scheduled deletion.
Transport Security
- TLS 1.3 — All API endpoints require TLS 1.3 with strong cipher suites. TLS 1.0 and 1.1 are not supported.
- HSTS — Strict Transport Security is enabled with a minimum max-age of one year and includes subdomains.
- Certificate pinning — Not enforced at the application level, but recommended for consumers in high-security environments.
- API key transmission — API keys are sent via the
x-api-keyheader over TLS. They are never transmitted in URLs or query parameters.
Audit Logging
Nexus Broker maintains audit logs for security-relevant events. Logs are immutable and retained according to your plan:
| Event | Details Logged |
|---|---|
| Key created | Key ID, user ID, timestamp |
| Key rotated | Old key ID, new key ID, user ID, timestamp |
| Key deleted | Key ID, user ID, timestamp |
| Webhook created | Webhook ID, algorithm, user ID, timestamp |
| Message ingested | Message ID, webhook ID, source IP, size, timestamp |
| Message dequeued | Message ID, API key ID (last 4 chars), timestamp |
| Message committed/errored | Message ID, action, API key ID (last 4 chars), timestamp |
| Auth failure | Source IP, attempted resource, reason, timestamp |
Audit log retention: Free (7 days), Pro (30 days), Enterprise (365 days). Enterprise customers can export audit logs via the API for long-term retention in their SIEM.
SOC 2 Readiness
Nexus Broker implements controls aligned with the AICPA Trust Services Criteria:
| Control | Implementation |
|---|---|
| CC6.1 | Encryption at rest (AES-256-GCM) and in transit (TLS 1.3). Logical access restricted by Firebase Auth and API key authentication. |
| CC6.7 | Data encryption with user-managed keys. Zero-knowledge architecture ensures broker cannot access plaintext. Key rotation supported. |
| CC7.1 | Comprehensive audit logging of all security-relevant events. Anomaly detection on authentication failures. Real-time monitoring of ingestion and error rates. |
| CC7.2 | Automated alerting on unusual patterns (spikes in failed auth, unusual ingestion volumes, DLQ overflow). Incident response procedures documented. |
Responsible Disclosure
If you discover a security vulnerability in Nexus Broker, please report it responsibly. We appreciate the efforts of security researchers and commit to:
- Acknowledging receipt within 24 hours
- Providing an initial assessment within 72 hours
- Keeping you informed of remediation progress
- Crediting researchers in our security advisories (with consent)
Report vulnerabilities to security@nexusbroker.com. Please include a detailed description of the vulnerability, steps to reproduce, and any proof-of-concept code. Encrypt your report with our PGP key for sensitive findings.
Please do not report vulnerabilities via public GitHub issues, social media, or community forums.