home / skills / jeremylongshore / claude-code-plugins-plus-skills / juicebox-data-handling

This skill helps you enforce GDPR-compliant data handling for Juicebox, enabling data classification, PII protection, retention, and subject rights.

npx playbooks add skill jeremylongshore/claude-code-plugins-plus-skills --skill juicebox-data-handling

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
9.2 KB
---
name: juicebox-data-handling
description: |
  Implement Juicebox data privacy and handling.
  Use when managing personal data, implementing GDPR compliance,
  or handling sensitive candidate information.
  Trigger with phrases like "juicebox data privacy", "juicebox GDPR",
  "juicebox PII handling", "juicebox data compliance".
allowed-tools: Read, Write, Edit, Bash(kubectl:*), Bash(curl:*)
version: 1.0.0
license: MIT
author: Jeremy Longshore <[email protected]>
---

# Juicebox Data Handling

## Overview
Implement compliant data handling practices for personal and candidate data from Juicebox.

## Prerequisites
- Understanding of applicable privacy regulations (GDPR, CCPA)
- Data classification framework
- Legal/compliance team sign-off

## Data Classification

| Category | Examples | Retention | Access |
|----------|----------|-----------|--------|
| Public | Name, title, company | 1 year | All users |
| Contact | Email, phone | 90 days | Recruiters only |
| Sensitive | SSN, salary | 30 days | Admins only |
| Derived | Scores, notes | Permanent | Internal only |

## Instructions

### Step 1: Data Classification System
```typescript
// lib/data-classifier.ts
export enum DataCategory {
  PUBLIC = 'public',
  CONTACT = 'contact',
  SENSITIVE = 'sensitive',
  DERIVED = 'derived'
}

export const fieldClassification: Record<string, DataCategory> = {
  // Public data
  name: DataCategory.PUBLIC,
  title: DataCategory.PUBLIC,
  company: DataCategory.PUBLIC,
  location: DataCategory.PUBLIC,
  linkedin_url: DataCategory.PUBLIC,

  // Contact data
  email: DataCategory.CONTACT,
  phone: DataCategory.CONTACT,
  personal_email: DataCategory.CONTACT,

  // Sensitive data
  salary: DataCategory.SENSITIVE,
  compensation: DataCategory.SENSITIVE,

  // Derived data
  fit_score: DataCategory.DERIVED,
  recruiter_notes: DataCategory.DERIVED
};

export function classifyData(data: Record<string, any>): Record<DataCategory, string[]> {
  const classified: Record<DataCategory, string[]> = {
    [DataCategory.PUBLIC]: [],
    [DataCategory.CONTACT]: [],
    [DataCategory.SENSITIVE]: [],
    [DataCategory.DERIVED]: []
  };

  for (const field of Object.keys(data)) {
    const category = fieldClassification[field] || DataCategory.DERIVED;
    classified[category].push(field);
  }

  return classified;
}
```

### Step 2: PII Handling
```typescript
// lib/pii-handler.ts
import crypto from 'crypto';

export class PIIHandler {
  // Mask sensitive fields for logging
  maskForLogging(profile: Profile): Record<string, any> {
    return {
      ...profile,
      email: this.maskEmail(profile.email),
      phone: this.maskPhone(profile.phone),
      personal_email: undefined // Remove entirely
    };
  }

  private maskEmail(email?: string): string | undefined {
    if (!email) return undefined;
    const [local, domain] = email.split('@');
    return `${local[0]}***@${domain}`;
  }

  private maskPhone(phone?: string): string | undefined {
    if (!phone) return undefined;
    return `***-***-${phone.slice(-4)}`;
  }

  // Encrypt sensitive data at rest
  encrypt(data: string, key: Buffer): string {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    let encrypted = cipher.update(data, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    const tag = cipher.getAuthTag();
    return `${iv.toString('base64')}:${tag.toString('base64')}:${encrypted}`;
  }

  // Decrypt sensitive data
  decrypt(encrypted: string, key: Buffer): string {
    const [ivB64, tagB64, data] = encrypted.split(':');
    const iv = Buffer.from(ivB64, 'base64');
    const tag = Buffer.from(tagB64, 'base64');
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
    decipher.setAuthTag(tag);
    let decrypted = decipher.update(data, 'base64', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  }
}
```

### Step 3: Retention Policies
```typescript
// lib/retention-policy.ts
export class RetentionPolicy {
  private policies: Record<DataCategory, number> = {
    [DataCategory.PUBLIC]: 365,      // 1 year
    [DataCategory.CONTACT]: 90,      // 90 days
    [DataCategory.SENSITIVE]: 30,    // 30 days
    [DataCategory.DERIVED]: -1       // No auto-delete
  };

  getRetentionDays(category: DataCategory): number {
    return this.policies[category];
  }

  async enforceRetention(): Promise<RetentionReport> {
    const report: RetentionReport = {
      processed: 0,
      deleted: 0,
      errors: []
    };

    for (const [category, days] of Object.entries(this.policies)) {
      if (days < 0) continue; // Skip no-delete categories

      const cutoff = new Date();
      cutoff.setDate(cutoff.getDate() - days);

      const result = await this.deleteExpiredData(category as DataCategory, cutoff);
      report.processed += result.processed;
      report.deleted += result.deleted;
    }

    return report;
  }

  private async deleteExpiredData(
    category: DataCategory,
    cutoff: Date
  ): Promise<{ processed: number; deleted: number }> {
    // Implementation depends on data storage
    return db.$transaction(async (tx) => {
      const expired = await tx.profileData.findMany({
        where: {
          category,
          createdAt: { lt: cutoff }
        }
      });

      await tx.profileData.deleteMany({
        where: {
          id: { in: expired.map(e => e.id) }
        }
      });

      return {
        processed: expired.length,
        deleted: expired.length
      };
    });
  }
}

// Run retention daily
cron.schedule('0 2 * * *', async () => {
  const policy = new RetentionPolicy();
  const report = await policy.enforceRetention();
  logger.info('Retention policy enforced', report);
});
```

### Step 4: Data Subject Rights
```typescript
// services/data-rights.ts
export class DataRightsService {
  // Right to access
  async handleAccessRequest(subjectEmail: string): Promise<DataExport> {
    // Find all data for subject
    const profiles = await db.profiles.findMany({
      where: { email: subjectEmail }
    });

    // Include access logs
    const accessLogs = await db.accessLogs.findMany({
      where: { profileEmail: subjectEmail }
    });

    return {
      profiles,
      accessLogs,
      exportedAt: new Date(),
      format: 'json'
    };
  }

  // Right to erasure (GDPR Article 17)
  async handleDeletionRequest(
    subjectEmail: string,
    requestId: string
  ): Promise<DeletionReport> {
    const report: DeletionReport = {
      requestId,
      subjectEmail,
      deletedRecords: 0,
      status: 'completed',
      completedAt: new Date()
    };

    await db.$transaction(async (tx) => {
      // Delete profile data
      const deleted = await tx.profiles.deleteMany({
        where: { email: subjectEmail }
      });
      report.deletedRecords += deleted.count;

      // Delete from cache
      await cache.deleteByPattern(`*${subjectEmail}*`);

      // Log deletion for compliance
      await tx.deletionLogs.create({
        data: {
          requestId,
          subjectEmail,
          deletedCount: report.deletedRecords,
          completedAt: new Date()
        }
      });
    });

    // Notify downstream systems
    await this.notifyDeletion(subjectEmail, requestId);

    return report;
  }

  // Right to rectification
  async handleRectificationRequest(
    subjectEmail: string,
    corrections: Record<string, any>
  ): Promise<void> {
    await db.profiles.updateMany({
      where: { email: subjectEmail },
      data: corrections
    });

    // Log for audit
    await db.auditLogs.create({
      data: {
        type: 'rectification',
        subjectEmail,
        changes: corrections
      }
    });
  }
}
```

### Step 5: Access Logging
```typescript
// middleware/access-logging.ts
export function logDataAccess(req: Request, res: Response, next: NextFunction) {
  const originalJson = res.json.bind(res);

  res.json = (data: any) => {
    // Log access to profile data
    if (data?.profiles || data?.profile) {
      const profiles = data.profiles || [data.profile];
      const profileIds = profiles.map((p: any) => p.id);

      db.accessLogs.create({
        data: {
          userId: req.user?.id,
          profileIds,
          operation: req.method,
          path: req.path,
          timestamp: new Date(),
          ip: req.ip,
          userAgent: req.get('user-agent')
        }
      }).catch(console.error);
    }

    return originalJson(data);
  };

  next();
}
```

## Compliance Checklist

```markdown
## Data Handling Compliance

### GDPR Requirements
- [ ] Lawful basis for processing documented
- [ ] Privacy policy updated
- [ ] Data subject rights implemented
- [ ] Data breach notification process
- [ ] DPA with Juicebox executed

### CCPA Requirements
- [ ] "Do Not Sell" option implemented
- [ ] Consumer rights portal
- [ ] Opt-out mechanisms
- [ ] Annual training completed

### Security
- [ ] Encryption at rest
- [ ] Encryption in transit
- [ ] Access logging
- [ ] Regular audits
```

## Output
- Data classification system
- PII handling utilities
- Retention policy enforcement
- Data subject rights handlers

## Resources
- [Juicebox Privacy Policy](https://juicebox.ai/privacy)
- [GDPR Guidelines](https://gdpr.eu)

## Next Steps
After data handling, see `juicebox-enterprise-rbac` for access controls.

Overview

This skill implements Juicebox data privacy and handling patterns for managing personal and candidate information. It packages classification, PII masking/encryption, retention enforcement, subject-rights workflows, and access logging into a practical set of utilities. Use it to align data flows with GDPR/CCPA expectations and simplify compliance tasks.

How this skill works

The skill classifies incoming profile fields into public, contact, sensitive, and derived categories, then applies handling rules per category. It provides PII utilities to mask data for logs and encrypt/decrypt sensitive fields at rest. A retention policy enforcer deletes expired records on a schedule, while a Data Rights service supports access, erasure, and rectification requests. Access logging middleware captures who accessed profile data and when for auditing.

When to use it

  • Building or integrating candidate/profile pipelines that store personal data
  • Implementing GDPR/CCPA compliance for Juicebox-sourced information
  • Adding automated retention and deletion of expired personal data
  • Responding to subject access, erasure, or rectification requests
  • Instrumenting audit trails and access logs for sensitive records

Best practices

  • Start with a legal review to confirm lawful bases and retention windows
  • Classify fields up front and treat unknown fields as derived until reviewed
  • Mask PII in logs and only decrypt sensitive fields when strictly needed
  • Store encryption keys in a secure key management system, not in code
  • Run retention enforcement as a scheduled job and capture a deletion audit

Example use cases

  • Masking emails and phone numbers before writing request logs or telemetry
  • Encrypting SSNs and salary data at rest and decrypting for authorized admins
  • Daily job that deletes contact records older than 90 days and sensitive data older than 30 days
  • Serving a data export bundle (profiles + access logs) for a data subject access request
  • Processing a GDPR erasure request that deletes profiles, clears caches, and logs the operation

FAQ

How are unknown fields handled by classification?

Unknown or unmapped fields default to the derived category until reviewed and explicitly classified.

Where should encryption keys be stored?

Use a dedicated key management service or hardware security module; never hard-code keys in source or repos.