- Overview
- Reporting Security Vulnerabilities
- Security Best Practices
- Data Privacy
- Connection Security
- Snapshot Security
- Transformation Guidelines
- Access Control
- Compliance Considerations
- Security Checklist
Snaplet takes security seriously. This document outlines our security policies, best practices for using Snaplet securely, and how to report security vulnerabilities.
If you discover a security vulnerability in Snaplet, please report it responsibly:
- DO NOT create a public GitHub issue
- Email us at security@snaplet.dev with:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Any suggested fixes
We aim to respond within 48 hours and will keep you updated on our progress.
- 0-48 hours: Initial response and assessment
- 7 days: Fix development and testing
- 30 days: Patch release and disclosure
# Good: Use environment variables for sensitive data
export DATABASE_URL="postgresql://user:pass@host:5432/db"
export SNAPLET_PRIVATE_KEY="$(cat private.key)"
# Bad: Hardcode credentials
snaplet capture --db-url "postgresql://admin:password123@prod.example.com/db"// snaplet.config.ts
// Good: Reference environment variables
export default defineConfig({
privateKey: process.env.SNAPLET_PRIVATE_KEY,
// ...
})
// Bad: Hardcode sensitive data
export default defineConfig({
privateKey: 'actual-private-key-content', // Never do this!
// ...
})Add to .gitignore:
# Snaplet
.snaplet/
snapshots/
*.snapshot
private.key
public.key
.env
.env.*
Snaplet automatically detects and suggests transformations for common PII:
// Detected patterns:
- Email addresses
- Phone numbers
- Social Security Numbers
- Credit card numbers
- IP addresses
- Physical addresses// snaplet.config.ts
import { copycat } from '@snaplet/copycat'
export default defineConfig({
transform: {
// Good: Use deterministic transformations
public: {
users: ({ row }) => ({
email: copycat.email(row.email), // Deterministic
name: copycat.fullName(row.name),
ssn: copycat.ssn(row.ssn),
created_at: row.created_at, // Keep non-sensitive data
}),
// Transform all columns by default
$default: ({ row }) =>
Object.fromEntries(
Object.entries(row).map(([key, value]) => [
key,
typeof value === 'string' ? copycat.scramble(value) : value
])
)
}
}
})For highly sensitive data, exclude it entirely:
export default defineConfig({
select: {
public: {
// Exclude sensitive tables
audit_logs: false,
payment_methods: false,
// Or exclude specific columns
users: {
columns: {
password_hash: false,
two_factor_secret: false,
}
}
}
}
})Always use SSL for database connections:
# Good: SSL enabled
postgresql://user:pass@host:5432/db?sslmode=require
# Better: Full verification
postgresql://user:pass@host:5432/db?sslmode=verify-full&sslcert=client.crt&sslkey=client.key
# Bad: No SSL
postgresql://user:pass@host:5432/db?sslmode=disable// Good: Parse and validate connection strings
import { parseConnectionString } from '@snaplet/sdk'
const config = parseConnectionString(process.env.DATABASE_URL)
if (!config.ssl) {
throw new Error('SSL required for production databases')
}
// Bad: Direct usage without validation
const db = new Client(process.env.DATABASE_URL)For additional security, use SSH tunnels:
# Create SSH tunnel
ssh -L 5433:localhost:5432 user@database-host
# Connect through tunnel
snaplet capture --db-url postgresql://user:pass@localhost:5433/dbEnable snapshot encryption:
// snaplet.config.ts
export default defineConfig({
publicKey: process.env.SNAPLET_PUBLIC_KEY,
// Snapshots will be encrypted automatically
})# Set restrictive permissions
chmod 600 snapshots/*
chmod 700 snapshots/
# Good: Store in secure location
~/secure-snapshots/
# Bad: Store in public directories
/tmp/snapshots/
~/Downloads/When sharing snapshots:
# Good: Encrypted transfer
gpg --encrypt --recipient team@example.com snapshot.tar.gz
# Good: Secure transfer protocols
scp snapshot.tar.gz user@secure-host:/secure/location/
aws s3 cp snapshot.tar.gz s3://secure-bucket/ --sse
# Bad: Unencrypted sharing
email snapshot without encryption
upload to public file sharingUse seeded transformations for consistency:
import { copycat } from '@snaplet/copycat'
// Good: Deterministic (same input = same output)
copycat.email('john@example.com') // Always returns same fake email
// Bad: Random transformations
Math.random().toString() + '@example.com' // Different each timeMaintain data format for application compatibility:
transform: {
public: {
users: ({ row }) => ({
// Preserve format
phone: copycat.phoneNumber(row.phone, {
format: '+1 (###) ###-####'
}),
// Preserve domain for emails
email: row.email.replace(/^[^@]+/, copycat.username(row.email)),
// Preserve length
api_key: copycat.scramble(row.api_key).substring(0, row.api_key.length),
})
}
}Maintain relationships in transformed data:
transform: {
public: {
// Transform consistently across tables
users: ({ row }) => ({
id: row.id, // Keep IDs unchanged
email: copycat.email(row.email),
}),
posts: ({ row }) => ({
user_id: row.user_id, // Maintain foreign key
content: copycat.paragraph(row.content),
})
}
}Create read-only users for capture:
-- Create read-only role
CREATE ROLE snaplet_reader WITH LOGIN PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE production TO snaplet_reader;
GRANT USAGE ON SCHEMA public TO snaplet_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO snaplet_reader;
-- Revoke unnecessary permissions
REVOKE CREATE ON SCHEMA public FROM snaplet_reader;For Snaplet Cloud features:
# Good: Use environment variables
export SNAPLET_ACCESS_TOKEN="snap_token_xxx"
# Good: Use secure key storage
snaplet auth login # Stores securely in OS keychain
# Bad: Hardcode in scripts
TOKEN="snap_token_xxx" # Don't do thisImplement access controls:
// Good: Role-based access
const canCapture = user.role === 'admin' || user.role === 'developer'
const canRestore = user.role !== 'viewer'
// Good: Audit logging
logger.info('Snapshot captured', {
user: user.id,
timestamp: Date.now(),
snapshot: snapshot.id
})- Right to Erasure: Exclude deleted users from snapshots
- Data Minimization: Only capture necessary data
- Purpose Limitation: Use snapshots only for development
export default defineConfig({
subset: {
targets: [{
table: 'public.users',
// Exclude deleted users (GDPR compliance)
where: 'deleted_at IS NULL',
}]
}
})For healthcare data:
export default defineConfig({
transform: {
public: {
patients: ({ row }) => ({
// De-identify all PHI
name: copycat.fullName(row.name),
ssn: copycat.ssn(row.ssn),
dob: copycat.dateString(row.dob),
// Remove direct identifiers
medical_record_number: copycat.uuid(row.medical_record_number),
})
}
}
})For payment data:
export default defineConfig({
select: {
public: {
// Never include payment data
credit_cards: false,
payment_tokens: false,
transaction_logs: false,
}
}
})Before using Snaplet in production:
- Database connections use SSL/TLS
- Credentials stored in environment variables
- Configuration files don't contain secrets
-
.gitignoreincludes sensitive files
- PII transformation rules configured
- Sensitive tables excluded
- Transformation functions are deterministic
- Referential integrity maintained
- Database user has minimal permissions
- API keys stored securely
- Team access controls implemented
- Audit logging enabled
- Snapshots encrypted at rest
- Storage permissions restricted
- Transfer methods secure
- Retention policies defined
- GDPR requirements met
- Industry-specific compliance verified
- Data retention policies followed
- Right to erasure implemented
- Security alerts configured
- Access logs reviewed regularly
- Transformation effectiveness verified
- Compliance audits scheduled
For security-related questions:
- Email: security@snaplet.dev
- Discord: #security channel
- Documentation: https://docs.snaplet.dev/security