n8n-nodes-memory-postgres-chat-encrypted
v0.1.2
Published
n8n community node — Postgres chat memory with configurable AES-256-GCM (application-level) or pgcrypto (server-side) encryption at rest
Downloads
427
Readme
n8n-nodes-memory-postgres-chat-encrypted
n8n community node — Postgres Chat Memory with configurable AES-256-GCM (application-level) or pgcrypto (server-side) encryption at rest.
Overview
This node extends the official n8n Postgres Chat Memory node with transparent, configurable encryption of AI agent chat history stored in PostgreSQL. The LangChain agent always receives plaintext messages; encryption and decryption are completely transparent.
| Mode | Encryption location | Algorithm | PG extension required |
|------|---------------------|-----------|----------------------|
| None (default) | — plaintext | — | No |
| Application-level | Node.js process | AES-256-GCM | No |
| pgcrypto | PostgreSQL server | OpenPGP / AES-256 | Yes (pgcrypto) |
When to use each mode
| Requirement | Recommended mode |
|-------------|-----------------|
| Backward compatible with existing Postgres Chat Memory data | none |
| Encryption without server-side PG changes (e.g., managed cloud PG) | app |
| Encryption delegated to the DB server (pgcrypto already installed) | pgcrypto |
Installation
In n8n
- Open Settings → Community Nodes
- Click Install
- Enter:
n8n-nodes-memory-postgres-chat-encrypted - Click Install and restart n8n
Via npm (self-hosted)
npm install n8n-nodes-memory-postgres-chat-encryptedRequirements
- n8n ≥ 1.x
- Node.js ≥ 18
- PostgreSQL ≥ 13
- pgcrypto extension — only if using
pgcryptomode (CREATE EXTENSION pgcrypto;)
Usage
1. Add the node to your workflow
Connect Postgres Chat Memory (Encrypted) to your AI Agent node's Memory input, just like the standard Postgres Chat Memory node.
2. Configure the Postgres credential
Use an existing Postgres credential or create a new one. Click Test to verify the connection.
3. Configure the node parameters
| Parameter | Description |
|-----------|-------------|
| Session ID | Unique identifier per conversation. Use {{ $json.sessionId }} for dynamic sessions. |
| Table Name | Table to store messages in (auto-created on first use). Default: n8n_chat_histories. |
| Context Window Length | Number of past exchanges to include (0 = unlimited). |
| Encryption Mode | None, Application-level (AES-256-GCM), or PostgreSQL pgcrypto. |
| Encryption Key | Required when mode is not None. See Key Management. |
| Key Input Type | Passphrase (PBKDF2-derived) or Raw 32-byte hex (app-level only). |
Key Management
⚠️ Security warning: The encryption key protects all stored chat history. Loss of the key means permanent data loss. Changing the key does not re-encrypt existing rows.
Application-level mode
Two key input types are supported:
Passphrase (recommended for simplicity)
- Any UTF-8 string
- A 32-byte AES key is derived via PBKDF2-SHA-256 (100 000 iterations, fixed salt)
- Example:
my-very-long-and-random-passphrase
Raw 32-byte hex (recommended for maximum security)
- Exactly 64 hexadecimal characters representing a raw 32-byte key
- No PBKDF2 derivation is applied
- Generate with:
openssl rand -hex 32
pgcrypto mode
- The key is passed directly to
pgp_sym_encrypt/pgp_sym_decryptas a passphrase - pgcrypto uses OpenPGP symmetric encryption with AES-256
- Key rotation requires re-encrypting all rows manually
Storing the key safely
Never store the key directly in the workflow JSON. Instead:
- n8n Generic Credential — create a
Generic Authcredential with the key as a field, then reference it via an expression:{{ $credentials.myEncKey.key }} - Environment variable — set
MY_ENCRYPT_KEYand reference it via{{ $env.MY_ENCRYPT_KEY }} - Vault integration — use an HTTP Request node to fetch the key from HashiCorp Vault or AWS Secrets Manager before the AI Agent node
Database Schema
The node auto-creates (and migrates) the table on first use:
CREATE TABLE IF NOT EXISTS n8n_chat_histories (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
message TEXT NOT NULL, -- JSON, "PGC:<base64>", or "APP:<base64>"
encryption_mode VARCHAR(16), -- 'none' | 'pgcrypto' | 'app' | NULL (legacy)
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_n8n_chat_histories_session_id
ON n8n_chat_histories (session_id);Sentinel prefixes
Each stored row is self-describing via a sentinel prefix:
| Prefix | Meaning |
|--------|---------|
| (none) | Plaintext JSON (mode: none or legacy) |
| PGC: | pgcrypto-encrypted base64 |
| APP: | AES-256-GCM encrypted base64 (iv \|\| tag \|\| ciphertext) |
Migrating from the original Postgres Chat Memory node
The none mode is 100% backward-compatible — it reads and writes the same row format as the original MemoryPostgresChat node. You can switch to this node without migrating any data.
Upgrading to encrypted mode
⚠️ Existing plaintext rows are not automatically re-encrypted when you switch modes. You must migrate data manually (see below) or accept mixed-mode rows (not supported — the strategy will throw on mismatched sentinel prefixes).
Migration script (application-level mode):
-- Step 1: Add the new columns (the node does this automatically, but you can do it manually)
ALTER TABLE n8n_chat_histories
ADD COLUMN IF NOT EXISTS encryption_mode VARCHAR(16),
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW();
-- Step 2: Re-encrypt existing rows using your preferred method
-- (Run a small n8n workflow that reads, re-encrypts, and updates each row,
-- or use a custom Node.js script that uses AppLevelStrategy directly.)Security Considerations
- AES-256-GCM provides authenticated encryption — any tampering with a stored ciphertext causes decryption to fail with an error.
- Random 12-byte IV per message ensures that identical messages produce different ciphertexts.
- pgcrypto mode requires the database server to handle the key material. Ensure the connection to Postgres uses TLS (
ssl: truein the credential) to prevent key exposure in transit. - None mode with a table that has encrypted rows will throw an error — there is no silent downgrade.
- The PBKDF2 salt is fixed (not per-row) in this implementation. For maximum security, prefer raw 32-byte hex keys (no PBKDF2 needed).
Development
# Install dependencies
npm install
# Build (TypeScript → dist/)
npm run build
# Unit tests (50 tests, no Docker required)
npm test
# Integration tests (18 tests, requires Docker)
npm run test:integration
# Local Postgres for manual testing
docker compose up -d
# psql postgresql://n8n:n8npass@localhost:5432/n8n_memory
# Lint
npm run lintPerformance
AES-256-GCM encrypt+decrypt round-trip: ~0.02 ms per message (well under the 5 ms NFR).
License
MIT — see LICENSE.
