Skip to content

Security Best Practices

This guide outlines security best practices for implementing and operating Grizzly in production environments.

Key Management

Encryption Key Rotation

Regular key rotation is essential for maintaining strong security posture. Grizzly automates this process while allowing manual control when needed.

Automatic Rotation Strategy Configure KeyRings to rotate encryption keys based on usage:
const ring = await client.ring.create('ProductionData', {
   config: {
      algos: {
         aes256: {
            maxEncryptCount: 10000  // Rotate after 10,000 encryptions
         }
      }
   }
})
**Recommended rotation frequencies:** | Data Volume | Recommended maxEncryptCount | |-------------|----------------------------| | Low (< 1GB/day) | 10,000 - 50,000 | | Medium (1-10GB/day) | 5,000 - 10,000 | | High (> 10GB/day) | 1,000 - 5,000 | **Guideline:** Rotate when approximately 64GB of data has been encrypted with a single key.
Manual Rotation Triggers

Rotate keys immediately when:

const ring = await client.ring.get('SensitiveData')

// Rotate on security events
if (securityIncident) {
   await ring.rotate()
   console.log('Keys rotated due to security incident')
}

// Rotate on personnel changes
if (employeeOffboarding) {
   await ring.rotate()
   console.log('Keys rotated after access changes')
}

// Rotate on compliance schedule
if (quarterlyRotationDue) {
   await ring.rotate()
   console.log('Quarterly key rotation completed')
}

When to manually rotate:

  • Security incident or suspected compromise
  • Employee with key access leaves organization
  • Compliance requirements (quarterly, annually)
  • Major system updates or migrations
  • Before/after third-party data processor engagement
Post-Rotation Verification

Verify rotation was successful:

async function verifyRotation(keyringName, previousKeyId) {
   const ring = await client.ring.get(keyringName)

   // Verify new key is active
   if (ring.activeKey.id === previousKeyId) {
      throw new Error('Rotation failed - key unchanged')
   }

   console.log('Previous key:', previousKeyId)
   console.log('New key:', ring.activeKey.id)

   // Verify old data still decrypts
   const testData = 'Verification test'
   const encrypted = await ring.encrypt(testData)
   const decrypted = await ring.decrypt(encrypted)

   if (decrypted !== testData) {
      throw new Error('Post-rotation encryption/decryption failed')
   }

   console.log('✓ Rotation verified successfully')
}

Customer-Managed Keys (CMK)

Managing your own RSA key pairs provides complete control over encryption keys.

Key Generation Best Practices

Generate keys securely:

# Generate 4096-bit RSA key pair
openssl genrsa -out private.pem 4096

# Extract public key
openssl rsa -in private.pem -pubout -out public.pem

# Verify key strength
openssl rsa -in private.pem -text -noout | grep "Private-Key"
# Should show: Private-Key: (4096 bit)

Security requirements:

  • ✅ Use 4096-bit RSA keys
  • ✅ Generate keys in secure environment (HSM, offline machine)
  • ✅ Never transmit private keys over network
  • ✅ Store private keys encrypted at rest
  • ❌ Don't use keys smaller than 4096 bits
  • ❌ Don't reuse keys across KeyRings
Private Key Storage

Secure storage options (in order of preference):

  1. Hardware Security Module (HSM)

  2. FIPS 140-2 Level 3 or higher

  3. Physical tamper protection
  4. Key extraction prevention

  5. Key Management Service (KMS)

  6. AWS KMS, Azure Key Vault, Google Cloud KMS

  7. Centralized key lifecycle management
  8. Access audit trails

  9. Encrypted File Storage

  10. AES-256 encryption

  11. Access-controlled storage
  12. Regular backup to secure location

Example: Encrypted file storage

# Encrypt private key with passphrase
openssl pkcs8 -in private.pem -topk8 -v2 aes256 -out private.encrypted.pem

# Decrypt when needed (requires passphrase)
openssl pkcs8 -in private.encrypted.pem -out private.pem
// Load encrypted key in application
import { readFileSync } from 'fs'
import { execSync } from 'child_process'

function loadPrivateKey(keyPath, passphrase) {
   // Decrypt key using passphrase from secure environment variable
   const decrypted = execSync(
      `openssl pkcs8 -in ${keyPath} -passin pass:${passphrase}`,
      { encoding: 'utf-8' }
   )
   return decrypted
}

// Load from environment-specific key store
const passphrase = process.env.KEY_PASSPHRASE
const privateKey = loadPrivateKey('./keys/private.encrypted.pem', passphrase)

API Key Management

API Key Lifecycle

Proper API key management is critical for maintaining secure access to the Grizzly platform.

Creation and Scoping

Principle of Least Privilege:

// Don't: Overly permissive (avoid)
{
   "accountId": "service-account",
   "flags": {
      "*.*.*.*": true  // Full access to everything
   }
}

// Do: Scoped to specific needs
{
   "accountId": "service-account",
   "flags": {
      "keyring.CustomerData.encrypt": true,
      "keyring.CustomerData.decrypt": true,
      "activity.read": true
   }
}

Scoping guidelines:

  • Grant only permissions required for specific function
  • Use KeyRing-specific flags (keyring.[name].encrypt) over wildcard
  • Separate read and write permissions
  • Create different API keys for different services/environments
Secure Storage

Never store API keys in:

  • Source code
  • Version control (git)
  • Client-side code
  • Unencrypted configuration files
  • Application logs

Recommended storage:

// Environment variables
const apiKey = process.env.GRIZZLY_API_KEY

// Secrets management service
import { SecretsManager } from '@aws-sdk/client-secrets-manager'

async function getApiKey() {
   const client = new SecretsManager({ region: 'us-west-2' })
   const secret = await client.getSecretValue({
      SecretId: 'grizzly/api-key'
   })
   return JSON.parse(secret.SecretString).apiKey
}

// Encrypted configuration
import { readFileSync } from 'fs'
import { decrypt } from './crypto'

const encryptedConfig = readFileSync('./config.enc')
const config = decrypt(encryptedConfig, process.env.CONFIG_KEY)
const apiKey = config.grizzlyApiKey
Rotation Strategy

Rotate API keys regularly and after security events:

async function rotateApiKey(accountId, currentKeyId) {
   // Create new API key with same permissions
   const currentKey = await getApiKeyDetails(currentKeyId)

   const newKey = await fetch('https://tenant.grizzlycbg.io/auth/apikey', {
      method: 'POST',
      headers: {
         'Authorization': `Bearer ${currentKey.key}`,
         'Content-Type': 'application/json'
      },
      body: JSON.stringify({
         accountId: accountId,
         flags: currentKey.flags
      })
   })

   const newKeyData = await newKey.json()

   // Update applications to use new key
   await updateApplicationConfig(newKeyData.key)

   // Monitor for successful switchover
   await monitorKeyUsage(newKeyData.keyid, '24h')

   // Disable old key after verification
   await disableApiKey(currentKeyId)

   console.log('API key rotation completed')
}

Rotation schedule:

  • Production keys: Every 90 days minimum
  • Service accounts: Every 30-60 days
  • After security incidents: Immediately
  • After personnel changes: Within 24 hours
Revocation and Deactivation

Disable API keys when no longer needed:

// Disable immediately on security incident
async function emergencyKeyRevocation(keyId, reason) {
   console.log(`SECURITY: Revoking key ${keyId} - ${reason}`)

   // Disable key
   await disableApiKey(keyId)

   // Audit recent activity
   const recentActivity = await queryActivity({
      keyId: keyId,
      start: Date.now() - (24 * 60 * 60 * 1000),  // Last 24h
      end: Date.now()
   })

   // Alert security team
   await sendSecurityAlert({
      event: 'api_key_revoked',
      keyId: keyId,
      reason: reason,
      recentActivity: recentActivity
   })

   console.log('Key revoked and security team notified')
}

// Scheduled cleanup of unused keys
async function cleanupUnusedKeys() {
   const allKeys = await listApiKeys()
   const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000)

   for (const key of allKeys) {
      const lastUsed = await getKeyLastUsed(key.keyid)

      if (lastUsed < thirtyDaysAgo) {
         console.log(`Disabling unused key: ${key.keyid}`)
         await disableApiKey(key.keyid)
      }
   }
}

Access Control

Role-Based Access Control (RBAC)

Implement least-privilege access using Accounts and API Keys.

Account Structure

Organize Accounts by role and responsibility:

// Example: Multi-tier access structure

// 1. Service Accounts (automated systems)
await createAccount({
   uid: 'service-data-processor',
   notes: 'Automated data processing service'
})

// 2. Team Accounts (shared team access)
await createAccount({
   uid: 'team-finance',
   notes: 'Finance team shared access'
})

// 3. Individual Accounts (named users)
await createAccount({
   uid: 'user-alice-admin',
   notes: 'Alice Smith - Platform Administrator'
})

// 4. External Accounts (third-party processors)
await createAccount({
   uid: 'external-vendor-acme',
   notes: 'ACME Corp - Data Processing Vendor'
})
Permission Matrices

Define clear permission tiers:

// Permission templates for common roles

const ROLE_TEMPLATES = {
   // Read-only auditor
   auditor: {
      'activity.read': true,
      'keyring.*.read': true,
      'account.read': true
   },

   // Application service (encrypt/decrypt only)
   application: {
      'keyring.AppData.encrypt': true,
      'keyring.AppData.decrypt': true
   },

   // Data administrator (full KeyRing management)
   dataAdmin: {
      'keyring.create': true,
      'keyring.*.read': true,
      'keyring.*.encrypt': true,
      'keyring.*.decrypt': true,
      'keyring.*.rotate': true,
      'keyring.*.config.read': true,
      'keyring.*.config.write': true
   },

   // Platform administrator (full access)
   platformAdmin: {
      '*.*.*.*': true
   }
}

// Apply template when creating API key
async function createApiKeyFromTemplate(accountId, roleName) {
   const flags = ROLE_TEMPLATES[roleName]

   if (!flags) {
      throw new Error(`Unknown role: ${roleName}`)
   }

   return await createApiKey({ accountId, flags })
}

Data Protection

Encryption in Transit

All communication with Grizzly must use TLS 1.2 or higher.

SDK Configuration
// Always use HTTPS
const client = await KeyClient.create({
   host: 'https://your-tenant.grizzlycbg.io',
   apikey: process.env.GRIZZLY_API_KEY
})

// Never use HTTP in production
// host: 'http://your-tenant.grizzlycbg.io'  // INSECURE
Certificate Validation

Ensure TLS certificates are validated:

// Node.js default validates certificates
// Never disable in production:

// NEVER DO THIS
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'

// For self-signed certs in development, use proper CA
import https from 'https'
import fs from 'fs'

const agent = new https.Agent({
   ca: fs.readFileSync('./ca-cert.pem')
})

// Use custom agent for requests

Data at Rest

Protect encrypted data storage.

Storage Security

Encrypted data protection:

  • Store encrypted data in access-controlled systems
  • Apply encryption at storage layer (database encryption, disk encryption)
  • Implement backup encryption
  • Use separate credentials for storage access
// Example: Secure database storage
async function storeEncryptedData(userId, data) {
   const ring = await client.ring.get('UserData')

   // Encrypt with Asset metadata
   const encrypted = await ring.encrypt(data, {
      asset: {
         id: `user-${userId}`,
         type: 'user-record',
         classification: 'confidential',
         encryptedAt: new Date().toISOString()
      }
   })

   // Store in encrypted database column
   await db.query(
      'INSERT INTO encrypted_records (user_id, encrypted_data, created_at) VALUES ($1, $2, $3)',
      [userId, encrypted, new Date()]
   )
}

Compliance and Audit

Activity Monitoring

Track all encryption operations for compliance and security monitoring.

Audit Trail Analysis
// Query activity for security review
async function securityAudit(startDate, endDate) {
   const activities = await queryActivity({
      start: startDate.getTime(),
      end: endDate.getTime(),
      limit: 10000
   })

   const report = {
      totalOperations: activities.length,
      byAction: {},
      byKeyRing: {},
      byAccount: {},
      failedOperations: [],
      suspiciousActivity: []
   }

   for (const activity of activities) {
      // Count by action
      report.byAction[activity.action] =
         (report.byAction[activity.action] || 0) + 1

      // Count by KeyRing
      report.byKeyRing[activity.ring] =
         (report.byKeyRing[activity.ring] || 0) + 1

      // Track failures
      if (!activity.success) {
         report.failedOperations.push(activity)
      }

      // Detect suspicious patterns
      if (activity.action === 'decrypt' && isAfterHours(activity.createdAt)) {
         report.suspiciousActivity.push(activity)
      }
   }

   return report
}

// Automated monitoring
async function setupSecurityAlerts() {
   setInterval(async () => {
      const last15Min = await queryActivity({
         start: Date.now() - (15 * 60 * 1000),
         end: Date.now()
      })

      const failureRate = last15Min.filter(a => !a.success).length / last15Min.length

      if (failureRate > 0.10) {  // More than 10% failures
         await sendSecurityAlert({
            type: 'high_failure_rate',
            rate: failureRate,
            period: '15min'
         })
      }
   }, 15 * 60 * 1000)  // Every 15 minutes
}
Compliance Reporting

Generate compliance reports for regulatory requirements:

async function generateComplianceReport(quarter) {
   const { startDate, endDate } = getQuarterDates(quarter)

   const report = {
      period: quarter,
      keyRotations: [],
      accessChanges: [],
      encryptionVolume: {},
      auditFindings: []
   }

   // Key rotation compliance
   const allKeyRings = await client.ring.getAll()
   for (const name of allKeyRings) {
      const ring = await client.ring.get(name)
      const rotationHistory = await getKeyRotationHistory(ring.id, startDate, endDate)

      report.keyRotations.push({
         keyring: name,
         rotations: rotationHistory.length,
         compliant: rotationHistory.length >= 1  // Quarterly requirement
      })
   }

   // Encryption volume by KeyRing
   const activities = await queryActivity({
      start: startDate.getTime(),
      end: endDate.getTime(),
      action: 'encrypt'
   })

   for (const activity of activities) {
      report.encryptionVolume[activity.ring] =
         (report.encryptionVolume[activity.ring] || 0) + 1
   }

   return report
}

Regulatory Compliance

HIPAA Compliance

HIPAA Requirements

For Protected Health Information (PHI):

// Tag PHI data for compliance tracking
async function encryptPHI(patientId, phi) {
   const ring = await client.ring.get('Healthcare')

   const encrypted = await ring.encrypt(phi, {
      asset: {
         id: `patient-${patientId}`,
         type: 'phi',
         classification: 'hipaa-protected',
         hipaa: {
            purpose: 'treatment',
            authorizedBy: 'provider-123',
            retentionYears: 7
         },
         encryptedAt: new Date().toISOString()
      }
   })

   return encrypted
}

// Audit trail for HIPAA compliance
async function hipaаAuditLog(patientId, dateRange) {
   const activities = await queryActivity({
      asset: `patient-${patientId}`,
      start: dateRange.start,
      end: dateRange.end
   })

   // HIPAA requires: Who, What, When, Where
   return activities.map(a => ({
      who: a.accountId,
      what: a.action,
      when: new Date(a.createdAt),
      where: a.metadata?.ipAddress || 'unknown',
      phi: `patient-${patientId}`
   }))
}

Key HIPAA controls:

  • Encrypt all PHI at rest and in transit
  • Maintain audit trails for minimum 6 years
  • Implement role-based access controls
  • Regular key rotation (quarterly minimum)
  • Breach notification procedures

GDPR Compliance

GDPR Requirements

For Personal Data:

// Right to erasure (right to be forgotten)
async function deleteUserData(userId) {
   // 1. Find all encrypted data for user
   const activities = await queryActivity({
      asset: `user-${userId}`
   })

   // 2. Delete encrypted data from storage
   await db.query('DELETE FROM encrypted_records WHERE user_id = $1', [userId])

   // 3. Rotate KeyRing if data isolation required
   const ring = await client.ring.get('UserData')
   await ring.rotate()

   // 4. Log deletion for compliance
   await logComplianceEvent({
      type: 'gdpr_erasure',
      userId: userId,
      timestamp: new Date(),
      recordCount: activities.length
   })

   console.log(`User ${userId} data erased per GDPR request`)
}

// Data portability
async function exportUserData(userId) {
   const activities = await queryActivity({
      asset: `user-${userId}`
   })

   // Decrypt and export user's data
   const exportData = {
      userId: userId,
      exportDate: new Date().toISOString(),
      records: []
   }

   for (const activity of activities) {
      if (activity.action === 'encrypt' && activity.encryptedData) {
         const decrypted = await client.decrypt(activity.encryptedData)
         exportData.records.push({
            date: activity.createdAt,
            data: decrypted
         })
      }
   }

   return exportData
}

Key GDPR controls:

  • Data minimization (only encrypt necessary data)
  • Purpose limitation (Asset metadata tracks purpose)
  • Right to access (query Activity feed by Asset)
  • Right to erasure (delete encrypted data + rotate keys)
  • Data portability (decrypt and export)

SOC 2 Compliance

SOC 2 Trust Principles

Security, Availability, Confidentiality:

// Implement security controls
const securityControls = {
   // Access control
   async enforceAccessControl() {
      // Principle of least privilege
      await reviewApiKeyPermissions()
      await disableUnusedKeys()
      await enforceStrongAuthentication()
   },

   // Change management
   async trackConfigChanges() {
      // Log all KeyRing configuration changes
      const ring = await client.ring.get('Production')

      ring.on('config:updated', (change) => {
         logAuditEvent({
            type: 'config_change',
            keyring: ring.name,
            changes: change,
            timestamp: new Date()
         })
      })
   },

   // Monitoring and alerting
   async setupMonitoring() {
      // Real-time security monitoring
      await monitorUnauthorizedAccess()
      await monitorEncryptionFailures()
      await monitorKeyRotation()
   },

   // Incident response
   async incidentResponse(incident) {
      // 1. Detect and log
      await logSecurityIncident(incident)

      // 2. Contain
      await revokeCompromisedKeys()
      await rotateAffectedKeyRings()

      // 3. Investigate
      const forensics = await queryActivity({
         start: incident.detectedAt - (7 * 24 * 60 * 60 * 1000),
         end: incident.detectedAt
      })

      // 4. Remediate
      await implementSecurityFixes()

      // 5. Report
      await generateIncidentReport(incident, forensics)
   }
}

Additional Resources