Pattern provides hierarchical memory storage for AI agents using NATS JetStream as the backend. While Pattern includes several security features (scope isolation, access controls, content scanning), users are responsible for protecting sensitive data.
This document explains:
- What Pattern protects against (and what it doesn't)
- How to handle sensitive data safely
- Security best practices for different use cases
- Incident response guidance
✅ Cross-Agent Memory Access
- Private memories are isolated by agent ID
- Personal memories follow only the creating agent
- Team memories are isolated by project
- Sub-agents cannot access parent's personal (core) memories
✅ Accidental Secret Storage (v0.3.1+)
- Content scanner detects common secret patterns
- Warnings issued before storing credentials, API keys, etc.
- Opt-out available via
PATTERN_DISABLE_CONTENT_SCAN
✅ Unauthorized Project Access
- Project isolation via project ID (derived from working directory)
- Team memories only visible within the same project
- Cross-project access requires explicit
publicscope
❌ Data at Rest Encryption
- NATS KV stores content in plaintext by default
- Anyone with NATS server access can read all memories
- Mitigation: Use client-side encryption (see below)
❌ Network Eavesdropping (without TLS)
- Plain
nats://connections send data unencrypted - Mitigation: Use
wss://(WebSocket with TLS) ortls://protocol
❌ Malicious Agent Behavior
- Pattern trusts agents to respect scope boundaries
- Compromised agent could share private memories to team scope
- Mitigation: Run untrusted agents in isolated projects
❌ NATS Server Security
- Pattern relies on NATS authentication and authorization
- No additional encryption layer on top of NATS
- Mitigation: Secure your NATS deployment (see below)
| Scope | NATS Bucket | Accessible By | Encryption |
|---|---|---|---|
private |
loom-pattern-{projectId} |
Same agent, same project | None (plaintext) |
personal |
loom-pattern-user-{userId} |
Same agent, all projects | None (plaintext) |
team |
loom-pattern-{projectId} |
All agents in project | None (plaintext) |
public |
loom-pattern-global |
All agents everywhere | None (plaintext) |
Recommendations:
-
Enable Authentication
# Use credentials in URL export NATS_URL="nats://user:pass@localhost:4222" # Or environment variables export NATS_USER="pattern-client" export NATS_PASS="secure-password"
-
Enable TLS/SSL
# For WebSocket with TLS export NATS_URL="wss://user:pass@nats.example.com" # For TCP with TLS export NATS_URL="tls://user:pass@nats.example.com:4222"
-
Configure NATS Authorization
- Restrict which clients can access which buckets
- Limit permissions to specific subject patterns
- See NATS Authorization Guide
-
Network Isolation
- Run NATS on private network
- Use firewall rules to restrict access
- Consider VPN for remote agents
🚫 Credentials and Secrets
- Passwords, API keys, tokens
- Private SSH keys, certificates
- Database connection strings with credentials
- OAuth secrets, webhook signing keys
🚫 Personally Identifiable Information (PII)
- Social Security Numbers, passport numbers
- Credit card numbers, bank account details
- Medical records, health information
- Biometric data
🚫 Proprietary Code or Data
- Source code with embedded secrets
- Customer data, financial records
- Trade secrets, unreleased product info
- File paths, URLs (may reveal system structure)
- Usernames, email addresses (consider privacy)
- Configuration details (may aid attackers)
Best Practice: Store references to secrets, not the secrets themselves.
Good:
{
"content": "API credentials stored in 1Password vault 'DevOps'"
}Bad:
{
"content": "API key: sk_live_abc123xyz..."
}Pattern includes an opt-in content scanner that detects common secret patterns:
- API keys and tokens (AWS, GitHub, Stripe, etc.)
- Passwords in common formats
- Private keys (RSA, SSH, PGP)
- Database connection strings
- JWT tokens, OAuth secrets
- Credit card numbers
- Email addresses (in certain contexts)
// When you call remember()
remember({
content: "My GitHub token is ghp_abc123xyz",
scope: "private",
category: "recent"
})
// Pattern warns but does not block:
// ⚠️ WARNING: Possible secret detected in content:
// - GitHub Personal Access Token (line 1)
// Consider storing this in a secure vault instead.If you encounter false positives or prefer to manage security yourself:
export PATTERN_DISABLE_CONTENT_SCAN=trueNote: Content scanning is a safety net, not a guarantee. Always review what you're storing.
For sensitive content that must be stored, use client-side encryption:
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
// Encryption function
function encryptContent(content: string, key: Buffer): string {
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(content, 'utf8', 'base64');
encrypted += cipher.final('base64');
// Prepend IV for decryption
return iv.toString('base64') + ':' + encrypted;
}
// Decryption function
function decryptContent(encrypted: string, key: Buffer): string {
const [ivBase64, ciphertext] = encrypted.split(':');
const iv = Buffer.from(ivBase64, 'base64');
const decipher = createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Usage with Pattern
const encryptionKey = Buffer.from(process.env.PATTERN_ENCRYPTION_KEY!, 'hex');
// Store encrypted
const sensitiveData = "Database password: secret123";
const encrypted = encryptContent(sensitiveData, encryptionKey);
await remember({
content: encrypted,
scope: "private",
category: "longterm",
metadata: {
tags: ["encrypted"],
source: "encrypted-secret"
}
});
// Retrieve and decrypt
const memories = await recallContext({ categories: ["longterm"] });
const encryptedMemory = memories.private.find(m =>
m.metadata?.tags?.includes("encrypted")
);
if (encryptedMemory) {
const decrypted = decryptContent(encryptedMemory.content, encryptionKey);
console.log(decrypted); // "Database password: secret123"
}Store encryption keys securely:
- ✅ Environment variables (for local development)
- ✅ Secret management services (AWS Secrets Manager, HashiCorp Vault)
- ✅ Hardware security modules (HSM) for production
- ❌ Never commit keys to version control
- ❌ Never store keys in Pattern memories
| Use Case | Recommended Scope | Reasoning |
|---|---|---|
| Debug notes, temp observations | private |
No need to share |
| Personal preferences, identity | personal |
Follows you across projects |
| Project decisions, architecture | team |
All project agents benefit |
| Public templates, knowledge | public |
Useful to broader community |
Sub-agents (spawned for specialized tasks) have restricted access:
| Parent Scope | Sub-Agent Access | Use Case |
|---|---|---|
private (recent, tasks, longterm) |
Read-only | Sub-agents can see context |
personal (core) |
No access | Identity protection |
team |
Read-write | Collaboration |
public |
Read-write | Shared knowledge |
Security Implication: Never store secrets in private scope if sub-agents are untrusted.
When sharing a machine with other users:
- Each user gets a separate
personalbucket (isolated by user ID) teamscope memories are shared across all agents in the same project directory- Use different project directories for different trust levels
When running agents on different machines:
- Set
LOOMINAL_AGENT_IDconsistently to share the same agent identity - Use
personalscope for memories that should follow the agent - Use
teamscope for project-specific knowledge
-
Delete the memory immediately
await forget({ memoryId: "the-memory-id", force: true });
-
Rotate the compromised secret
- Change the password/token/key
- Update any systems using the old secret
- Revoke API keys if possible
-
Check for sharing
# Search NATS KV for the secret (requires NATS CLI) nats kv get loom-pattern-{projectId} --all -
Consider NATS data purge (if widely shared)
# Delete entire bucket (loses all memories!) nats kv del loom-pattern-{projectId} -
Review access logs
- Check who had access to the NATS server
- Review network logs for unusual activity
- Assume the secret was compromised
Before storing any memory, ask:
- Does this contain credentials, secrets, or PII?
- Would I be comfortable sharing this with my project team?
- Is this something I'd commit to a public GitHub repo?
- Can I use a reference instead of the actual value?
- Should I encrypt this before storing?
-
NATS Server Logs
- Monitor for unusual access patterns
- Alert on failed authentication attempts
- Track bucket size growth
-
Memory Audit Trail
// Periodically review what's stored const allMemories = await recallContext({ scopes: ["private", "personal", "team"], limit: 1000 }); // Check for suspicious patterns allMemories.private.forEach(m => { if (m.content.includes("password") || m.content.includes("secret")) { console.warn("⚠️ Possible secret in memory:", m.id); } });
-
Cleanup Automation
// Regular cleanup of temporary memories await cleanup({ expireOnly: false });
- NATS running on localhost only
- Basic authentication enabled (
NATS_USER/NATS_PASS) - Content scanning enabled (default)
- Regular cleanup scheduled
- NATS running with TLS (
wss://ortls://) - Strong authentication and authorization configured
- Network isolation (private network/VPN)
- Content scanning enabled
- Encryption keys stored in secret manager
- Monitoring and alerting configured
- Backup/recovery plan documented
- Incident response plan in place
No, if they're in different projects. Pattern uses project isolation (based on working directory hash). Same project = same team scope, but private scope is still agent-specific.
All memories are readable. NATS KV stores data in plaintext. Use client-side encryption for sensitive content, or don't store sensitive data at all.
No. Use dedicated secret management tools:
- AWS Secrets Manager, Azure Key Vault, GCP Secret Manager
- HashiCorp Vault
- 1Password, LastPass (for team secrets)
- Kubernetes Secrets (with encryption at rest)
Pattern is for context and knowledge, not credentials.
Use wss:// (WebSocket Secure) instead of ws://:
export NATS_URL="wss://user:pass@nats.example.com"Ensure your NATS server has TLS certificates configured.
Pattern does not provide compliance guarantees. For regulated data:
- Use client-side encryption
- Implement access logging and auditing
- Document data retention policies
- Ensure NATS infrastructure meets compliance standards
- Consult legal/compliance team before storing regulated data
If you discover a security vulnerability in Pattern:
- Do not open a public GitHub issue
- Email security details to: mike@lopresti.org
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We will respond within 48 hours and work with you to address the issue.
Last Updated: 2025-12-23 Version: 0.3.1