Screenshot Security: Handling Sensitive Content and Privacy

Implement secure screenshot capture with comprehensive privacy protection and sensitive content handling strategies.

Screenshot Security: Handling Sensitive Content and Privacy
September 15, 2025
12 min read
Website Screenshots

Screenshot Security: Handling Sensitive Content and Privacy


Screenshot security requires careful handling of sensitive content and privacy protection measures. Implementing comprehensive security strategies ensures compliance while maintaining functionality.


Screenshot Security Overview

Screenshot Security Overview


Overview {#overview}


Screenshot capture can expose sensitive data including PII, financial information, authentication tokens, and proprietary content. Security requires redaction, access controls, secure storage, and compliance with privacy regulations.


Security Priorities:

  • Data Protection: Redact/blur sensitive content before capture
  • Access Control: Authenticate and authorize screenshot requests
  • Secure Storage: Encrypt screenshots at rest and in transit
  • Compliance: GDPR, CCPA, PCI-DSS, HIPAA requirements
  • Audit Trail: Log all screenshot operations

Sensitive Data Protection {#sensitive-data}


Identifying and protecting sensitive content before screenshot capture.


Content Detection


interface SensitivePattern {
  type: 'pii' | 'financial' | 'auth' | 'proprietary'
  regex: RegExp
  redactionStrategy: 'blur' | 'remove' | 'replace'
  severity: 'low' | 'medium' | 'high' | 'critical'
}

const SENSITIVE_PATTERNS: SensitivePattern[] = [
  {
    type: 'financial',
    regex: /d{4}[- ]?d{4}[- ]?d{4}[- ]?d{4}/, // Credit card
    redactionStrategy: 'blur',
    severity: 'critical'
  },
  {
    type: 'financial',
    regex: /d{3}-d{2}-d{4}/, // SSN
    redactionStrategy: 'blur',
    severity: 'critical'
  },
  {
    type: 'pii',
    regex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Z|a-z]{2,}/, // Email
    redactionStrategy: 'blur',
    severity: 'high'
  },
  {
    type: 'auth',
    regex: /bearers+[a-zA-Z0-9._-]+/i, // Bearer token
    redactionStrategy: 'remove',
    severity: 'critical'
  },
  {
    type: 'auth',
    regex: /api[_-]?key[:s]+[a-zA-Z0-9]{16,}/i, // API key
    redactionStrategy: 'remove',
    severity: 'critical'
  }
]

class SensitiveContentDetector {
  detectSensitiveContent(html: string): {
    detected: boolean
    patterns: Array<{
      type: string
      position: number
      severity: string
    }>
  } {
    const detected: Array<{ type: string; position: number; severity: string }> = []
    
    for (const pattern of SENSITIVE_PATTERNS) {
      const matches = html.matchAll(new RegExp(pattern.regex, 'g'))
      
      for (const match of matches) {
        detected.push({
          type: pattern.type,
          position: match.index || 0,
          severity: pattern.severity
        })
      }
    }
    
    return {
      detected: detected.length > 0,
      patterns: detected
    }
  }
}

Privacy and Compliance {#privacy-compliance}


GDPR, CCPA, and industry regulations govern screenshot capture.


GDPR Compliance


interface GDPRScreenshotPolicy {
  legalBasis: 'consent' | 'legitimate_interest' | 'contract' | 'legal_obligation'
  dataMinimization: boolean
  purposeLimitation: string
  storageLimit: number // days
  dataSubjectRights: {
    access: boolean
    erasure: boolean
    portability: boolean
  }
}

class GDPRCompliance {
  validateScreenshotRequest(request: {
    url: string
    userId?: string
    purpose: string
    consent?: boolean
  }): {
    allowed: boolean
    legalBasis: string
    requirements: string[]
    warnings: string[]
  } {
    const warnings: string[] = []
    const requirements: string[] = []
    
    // Check for sensitive domains
    if (this.isSensitiveDomain(request.url)) {
      requirements.push('Explicit consent required')
      requirements.push('Redaction of PII mandatory')
      
      if (!request.consent) {
        return {
          allowed: false,
          legalBasis: 'none',
          requirements,
          warnings: ['Consent required for sensitive domain']
        }
      }
    }
    
    // Determine legal basis
    let legalBasis = 'legitimate_interest'
    if (request.consent) {
      legalBasis = 'consent'
    }
    
    // Add standard requirements
    requirements.push('Implement 30-day retention')
    requirements.push('Provide data subject access')
    requirements.push('Enable screenshot deletion on request')
    
    return {
      allowed: true,
      legalBasis,
      requirements,
      warnings
    }
  }
  
  private isSensitiveDomain(url: string): boolean {
    const sensitiveDomains = ['bank', 'healthcare', 'medical', 'government']
    return sensitiveDomains.some(keyword => url.toLowerCase().includes(keyword))
  }
}

Access Controls {#access-controls}


Securing screenshot capture and access.


Authentication and Authorization


interface ScreenshotPermissions {
  userId: string
  allowedDomains: string[]
  maxResolution: { width: number; height: number }
  rateLimit: { requests: number; period: number }
  features: {
    fullPage: boolean
    javascript: boolean
    customCSS: boolean
  }
}

class AccessControl {
  private permissions: Map<string, ScreenshotPermissions> = new Map()
  
  async authorize(userId: string, request: {
    url: string
    fullPage?: boolean
    width?: number
    height?: number
  }): Promise<{
    authorized: boolean
    reason?: string
    allowedFeatures?: string[]
  }> {
    const userPerms = this.permissions.get(userId)
    
    if (!userPerms) {
      return {
        authorized: false,
        reason: 'User not found'
      }
    }
    
    // Check domain whitelist
    const domain = new URL(request.url).hostname
    const domainAllowed = userPerms.allowedDomains.some(allowed => 
      allowed === '*' || domain.endsWith(allowed)
    )
    
    if (!domainAllowed) {
      return {
        authorized: false,
        reason: 'Domain not in whitelist'
      }
    }
    
    // Check resolution limits
    const width = request.width || 1920
    const height = request.height || 1080
    
    if (width > userPerms.maxResolution.width || height > userPerms.maxResolution.height) {
      return {
        authorized: false,
        reason: 'Resolution exceeds limit'
      }
    }
    
    // Check rate limit
    const withinLimit = await this.checkRateLimit(userId, userPerms.rateLimit)
    if (!withinLimit) {
      return {
        authorized: false,
        reason: 'Rate limit exceeded'
      }
    }
    
    return {
      authorized: true,
      allowedFeatures: Object.entries(userPerms.features)
        .filter(([_, allowed]) => allowed)
        .map(([feature]) => feature)
    }
  }
  
  private async checkRateLimit(userId: string, limit: { requests: number; period: number }): Promise<boolean> {
    // Implementation would check Redis/database
    return true
  }
}

Content Redaction {#redaction}


Automatically redacting sensitive content from screenshots.


Redaction Engine


interface RedactionRule {
  selector: string
  method: 'blur' | 'black' | 'remove' | 'replace'
  replacement?: string
}

class ContentRedactor {
  private rules: RedactionRule[] = [
    { selector: 'input[type="password"]', method: 'black' },
    { selector: 'input[type="hidden"]', method: 'remove' },
    { selector: '[data-sensitive]', method: 'blur' },
    { selector: '.credit-card', method: 'blur' },
    { selector: '.ssn', method: 'blur' },
    { selector: '.api-key', method: 'remove' }
  ]
  
  async redactPage(page: any): Promise<{
    redacted: number
    elements: string[]
  }> {
    const redactedElements: string[] = []
    
    for (const rule of this.rules) {
      const count = await page.evaluate((selector: string, method: string) => {
        const elements = document.querySelectorAll(selector)
        
        elements.forEach(el => {
          switch (method) {
            case 'blur':
              (el as HTMLElement).style.filter = 'blur(10px)'
              break
            case 'black':
              (el as HTMLElement).style.backgroundColor = 'black'
              (el as HTMLElement).style.color = 'black'
              break
            case 'remove':
              el.remove()
              break
          }
        })
        
        return elements.length
      }, rule.selector, rule.method)
      
      if (count > 0) {
        redactedElements.push(`${rule.selector} (${count} elements)`)
      }
    }
    
    return {
      redacted: redactedElements.length,
      elements: redactedElements
    }
  }
  
  async redactText(page: any, patterns: RegExp[]): Promise<number> {
    return await page.evaluate((regexStrings: string[]) => {
      let redactionCount = 0
      const walker = document.createTreeWalker(
        document.body,
        NodeFilter.SHOW_TEXT
      )
      
      const textNodes: Text[] = []
      let node
      while (node = walker.nextNode()) {
        textNodes.push(node as Text)
      }
      
      const patterns = regexStrings.map(r => new RegExp(r, 'g'))
      
      textNodes.forEach(textNode => {
        let text = textNode.textContent || ''
        let modified = false
        
        patterns.forEach(pattern => {
          if (pattern.test(text)) {
            text = text.replace(pattern, '████████')
            modified = true
            redactionCount++
          }
        })
        
        if (modified) {
          textNode.textContent = text
        }
      })
      
      return redactionCount
    }, patterns.map(p => p.source))
  }
}

Secure Storage {#storage-security}


Protecting screenshots during storage and transmission.


Encryption


import crypto from 'crypto'

class ScreenshotEncryption {
  private algorithm = 'aes-256-gcm'
  
  encrypt(screenshot: Buffer, key: Buffer): {
    encrypted: Buffer
    iv: Buffer
    authTag: Buffer
  } {
    const iv = crypto.randomBytes(16)
    const cipher = crypto.createCipheriv(this.algorithm, key, iv)
    
    const encrypted = Buffer.concat([
      cipher.update(screenshot),
      cipher.final()
    ])
    
    const authTag = cipher.getAuthTag()
    
    return { encrypted, iv, authTag }
  }
  
  decrypt(
    encrypted: Buffer,
    key: Buffer,
    iv: Buffer,
    authTag: Buffer
  ): Buffer {
    const decipher = crypto.createDecipheriv(this.algorithm, key, iv)
    decipher.setAuthTag(authTag)
    
    return Buffer.concat([
      decipher.update(encrypted),
      decipher.final()
    ])
  }
  
  generateKey(): Buffer {
    return crypto.randomBytes(32) // 256-bit key
  }
}

Secure Metadata


interface ScreenshotMetadata {
  id: string
  url: string
  capturedAt: Date
  requestedBy: string
  purpose: string
  retentionUntil: Date
  encryptionKeyId: string
  redactionsApplied: string[]
  accessLog: Array<{
    userId: string
    timestamp: Date
    action: string
  }>
}

class MetadataManager {
  createMetadata(request: {
    url: string
    userId: string
    purpose: string
    retentionDays: number
  }): ScreenshotMetadata {
    const now = new Date()
    const retentionUntil = new Date(now.getTime() + request.retentionDays * 24 * 60 * 60 * 1000)
    
    return {
      id: crypto.randomUUID(),
      url: request.url,
      capturedAt: now,
      requestedBy: request.userId,
      purpose: request.purpose,
      retentionUntil,
      encryptionKeyId: this.generateKeyId(),
      redactionsApplied: [],
      accessLog: [{
        userId: request.userId,
        timestamp: now,
        action: 'created'
      }]
    }
  }
  
  private generateKeyId(): string {
    return `key_${Date.now()}_${crypto.randomBytes(8).toString('hex')}`
  }
  
  logAccess(metadata: ScreenshotMetadata, userId: string, action: string): void {
    metadata.accessLog.push({
      userId,
      timestamp: new Date(),
      action
    })
  }
}

Implementation {#implementation}


Complete secure screenshot implementation.


class SecureScreenshotService {
  private accessControl: AccessControl
  private redactor: ContentRedactor
  private encryption: ScreenshotEncryption
  private metadataManager: MetadataManager
  
  constructor() {
    this.accessControl = new AccessControl()
    this.redactor = new ContentRedactor()
    this.encryption = new ScreenshotEncryption()
    this.metadataManager = new MetadataManager()
  }
  
  async captureSecure(request: {
    url: string
    userId: string
    purpose: string
    redact?: boolean
  }): Promise<{
    screenshotId: string
    success: boolean
    metadata: ScreenshotMetadata
  }> {
    // 1. Authorize request
    const authResult = await this.accessControl.authorize(request.userId, { url: request.url })
    
    if (!authResult.authorized) {
      throw new Error(`Unauthorized: ${authResult.reason}`)
    }
    
    // 2. Capture screenshot
    const browser = await chromium.launch()
    const page = await browser.newPage()
    
    try {
      await page.goto(request.url)
      
      // 3. Apply redaction if requested
      if (request.redact) {
        await this.redactor.redactPage(page)
        await this.redactor.redactText(page, SENSITIVE_PATTERNS.map(p => p.regex))
      }
      
      const screenshot = await page.screenshot({ type: 'png' })
      
      // 4. Create metadata
      const metadata = this.metadataManager.createMetadata({
        url: request.url,
        userId: request.userId,
        purpose: request.purpose,
        retentionDays: 30
      })
      
      // 5. Encrypt screenshot
      const encryptionKey = this.encryption.generateKey()
      const { encrypted, iv, authTag } = this.encryption.encrypt(screenshot, encryptionKey)
      
      // 6. Store securely
      await this.storeSecure(metadata.id, encrypted, iv, authTag, encryptionKey, metadata)
      
      return {
        screenshotId: metadata.id,
        success: true,
        metadata
      }
    } finally {
      await browser.close()
    }
  }
  
  private async storeSecure(
    id: string,
    encrypted: Buffer,
    iv: Buffer,
    authTag: Buffer,
    key: Buffer,
    metadata: ScreenshotMetadata
  ): Promise<void> {
    // Store encrypted screenshot
    // Store encryption key in separate key management service
    // Store metadata in database
    console.log(`Storing secure screenshot: ${id}`)
  }
}

Conclusion {#conclusion}


Screenshot security requires comprehensive protection including sensitive content detection, automatic redaction, access controls, encryption, and compliance with privacy regulations. Success depends on implementing layered security, maintaining audit trails, and following data minimization principles.


Key success factors include detecting and redacting sensitive patterns before capture, implementing role-based access control, encrypting screenshots at rest, maintaining compliance with GDPR/CCPA, and enforcing retention policies.


Capture screenshots securely with our privacy-first APIs, designed to protect sensitive data while maintaining compliance with global privacy regulations.

Tags:securityprivacy-protectionsensitive-contentcompliance