Screenshot Security: Handling Sensitive Content and Privacy
Implement secure screenshot capture with comprehensive privacy protection and sensitive content handling strategies.
Table of Contents
Table of Contents
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
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.