PRIV ProtocolPRIV Docs
Plugin

Privacy & Security

How the PRIV extension protects your data and maintains security.

Privacy & Security

The PRIV extension implements multiple layers of privacy protection and security measures to ensure your data is handled safely.


Privacy Overview

flowchart TD
    A[Raw Browsing Data] --> B[Content Script]
    B --> C{Privacy Filter}
    C -->|Sensitive| D[Discard]
    C -->|Safe| E[Anonymization]
    E --> F[URL Stripping]
    F --> G[Title Sanitization]
    G --> H[Local Storage]
    H --> I[Batch Processing]
    I --> J[Encrypted Transmission]
    J --> K[PRIV API]

URL Anonymization

All URLs are anonymized before collection to remove identifying information.

Anonymization Process

/**
 * Anonymize a URL by removing sensitive components
 */
function anonymizeUrl(url: string): string {
  try {
    const parsed = new URL(url);
    // Only keep protocol, hostname, and pathname
    // Remove query params and hash which may contain PII
    return `${parsed.protocol}//${parsed.hostname}${parsed.pathname}`;
  } catch {
    return 'unknown';
  }
}

What Gets Removed

ComponentExampleRemoved
Query params?user=john&id=123Yes
Hash/fragment#section-2Yes
Auth tokens?token=abc123Yes
UTM params?utm_source=...Yes

Before and After

Before: https://example.com/page?user=john&token=secret#section
After:  https://example.com/page

Title Sanitization

Page titles are sanitized to remove potential personally identifiable information (PII).

Sanitization Function

/**
 * Anonymize page title by removing potential PII
 */
function anonymizeTitle(title: string): string {
  // Remove email addresses
  let cleaned = title.replace(
    /[\w.-]+@[\w.-]+\.\w+/g,
    '[email]'
  );

  // Remove phone numbers
  cleaned = cleaned.replace(
    /(\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,
    '[phone]'
  );

  // Remove potential names (capitalized word pairs)
  cleaned = cleaned.replace(
    /\b[A-Z][a-z]+ [A-Z][a-z]+\b/g,
    '[name]'
  );

  // Truncate to reasonable length
  return cleaned.substring(0, 100);
}

Examples

OriginalSanitized
John Smith - Profile[name] - Profile
Contact: john@email.comContact: [email]
Call 555-123-4567Call [phone]

Message Validation

All messages between extension components are validated for security.

Sender Validation

/**
 * Validate the sender of a Chrome runtime message
 */
function validateMessageSender(
  sender: chrome.runtime.MessageSender
): SenderValidationResult {
  const extensionId = chrome.runtime.id;

  // Check if message is from our own extension
  if (sender.id === extensionId) {
    return {
      isValid: true,
      isInternal: true,
      isExternalAllowed: false,
    };
  }

  // Check for external messages (from web pages)
  if (sender.origin) {
    if (isAllowedOrigin(sender.origin)) {
      return {
        isValid: true,
        isInternal: false,
        isExternalAllowed: true,
      };
    }

    return {
      isValid: false,
      error: `Untrusted origin: ${sender.origin}`,
    };
  }

  return {
    isValid: false,
    error: 'Unknown message sender',
  };
}

Message Structure Validation

function validateMessage(
  message: unknown,
  sender: chrome.runtime.MessageSender
): { isValid: boolean; error?: string } {
  // Validate sender first
  const senderResult = validateMessageSender(sender);
  if (!senderResult.isValid) {
    return { isValid: false, error: senderResult.error };
  }

  // Validate message structure
  if (!message || typeof message !== 'object') {
    return { isValid: false, error: 'Invalid message format' };
  }

  // Validate message type
  const msg = message as Record<string, unknown>;
  if (!msg.type || typeof msg.type !== 'string') {
    return { isValid: false, error: 'Invalid message type' };
  }

  // External senders can only send certain message types
  if (senderResult.isExternalAllowed && !senderResult.isInternal) {
    if (!isValidExternalMessageType(msg.type)) {
      return {
        isValid: false,
        error: `Type "${msg.type}" not allowed externally`,
      };
    }
  }

  return { isValid: true };
}

Blocked URL Types

Messages from these URL types are blocked:

const blockedPrefixes = [
  'chrome://',
  'chrome-extension://',
  'about:',
  'javascript:',
  'data:',
];

Rate Limiting

External messages are rate limited to prevent abuse.

Rate Limiter Implementation

class RateLimiter {
  private timestamps: Map<string, number[]> = new Map();

  constructor(
    private maxRequests: number,
    private windowMs: number
  ) {}

  isAllowed(key: string): boolean {
    const now = Date.now();
    const timestamps = this.timestamps.get(key) ?? [];

    // Remove old timestamps
    const validTimestamps = timestamps.filter(
      (t) => now - t < this.windowMs
    );

    if (validTimestamps.length >= this.maxRequests) {
      return false;
    }

    validTimestamps.push(now);
    this.timestamps.set(key, validTimestamps);
    return true;
  }
}

Rate Limits

SourceLimitWindow
External messages101 minute
API calls1001 minute
Data submissions205 minutes

Offline Queue Security

Data is securely queued when offline.

Queue Entry Structure

interface QueueEntry {
  id: string;              // Unique identifier
  data: CollectedDataEntry;// The collected data
  attempts: number;        // Retry count
  createdAt: number;       // Timestamp
  lastAttempt: number | null;
}

Queue Management

  • Maximum 10 retry attempts
  • Exponential backoff between retries
  • Data expired after 24 hours
  • Queue cleared on wallet disconnect

Content Security Policy

The extension enforces strict CSP rules.

Manifest CSP

{
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  }
}

What This Prevents

AttackPrevention
Inline scriptsNo eval() or inline JS
Remote scriptsOnly local scripts run
Object injectionNo external plugins
XSS attacksNo dynamic script execution

Input Sanitization

All user inputs and external data are sanitized.

HTML Escaping

/**
 * Escape HTML special characters to prevent XSS
 */
function escapeHtml(unsafe: string | null | undefined): string {
  if (!unsafe) return '';
  return unsafe
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

URL Sanitization

/**
 * Sanitize URL to prevent javascript: attacks
 */
function sanitizeUrl(url: string): string {
  if (!url) return '';

  const trimmed = url.trim().toLowerCase();

  // Block dangerous protocols
  if (
    trimmed.startsWith('javascript:') ||
    trimmed.startsWith('data:') ||
    trimmed.startsWith('vbscript:')
  ) {
    return '#';
  }

  return escapeHtml(url);
}

Wallet Address Validation

/**
 * Validate and sanitize wallet address
 */
function sanitizeWalletAddress(
  address: string | null | undefined
): string | null {
  if (!address) return null;

  const trimmed = address.trim();

  // Validate Ethereum address format
  if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) {
    return null;
  }

  return trimmed.toLowerCase();
}

Sensitive Data Redaction

Logs are sanitized to prevent accidental exposure.

Redaction Function

/**
 * Redact sensitive data from log output
 */
function redactSensitive(data: unknown): unknown {
  if (!data) return data;

  if (typeof data === 'string') {
    // Redact potential tokens/keys
    return data.replace(
      /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
      '[REDACTED_TOKEN]'
    );
  }

  if (typeof data !== 'object') return data;

  const obj = data as Record<string, unknown>;
  const redacted: Record<string, unknown> = {};

  const sensitiveKeys = [
    'token',
    'password',
    'secret',
    'key',
    'authorization',
    'signature'
  ];

  for (const [key, value] of Object.entries(obj)) {
    if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
      redacted[key] = '[REDACTED]';
    } else if (typeof value === 'object') {
      redacted[key] = redactSensitive(value);
    } else {
      redacted[key] = value;
    }
  }

  return redacted;
}

Excluded URLs

Sensitive URLs are automatically excluded from collection.

Exclusion Patterns

function shouldExcludeUrl(url: string): boolean {
  // Internal browser pages
  if (url.startsWith('chrome://') ||
      url.startsWith('chrome-extension://') ||
      url.startsWith('about:') ||
      url.startsWith('edge://') ||
      url.startsWith('file://')) {
    return true;
  }

  // Sensitive domains
  const sensitivePatterns = [
    /healthcare|medical|health\./i,
    /bank|banking/i,
    /login|signin|auth/i,
    /password|credential/i,
    /mail\.google\.com/i,
    /outlook\.(live|office)\.com/i,
    /inbox\./i,
  ];

  return sensitivePatterns.some(pattern => pattern.test(url));
}

Categories Excluded

CategoryExamples
HealthcareHospital portals, health apps
BankingOnline banking, financial apps
AuthenticationLogin pages, OAuth flows
EmailGmail, Outlook, inbox pages
PasswordsPassword managers, reset pages

JWT Token Handling

JWT tokens are validated but never logged.

Token Validation

/**
 * Check if a JWT is expired
 */
function isJwtExpired(token: string, bufferSeconds = 60): boolean {
  const payload = decodeJwtPayload(token);

  if (!payload || typeof payload.exp !== 'number') {
    return true; // Treat as expired if invalid
  }

  const now = Math.floor(Date.now() / 1000);
  return now >= payload.exp - bufferSeconds;
}

Token Security Rules

  • Tokens are never logged
  • Tokens are validated before use
  • Expired tokens are rejected
  • Tokens are cleared on disconnect

Data Transmission Security

TLS Encryption

All API communication uses TLS 1.3:

// API requests always use HTTPS
const API_BASE_URL = 'https://api.privprotocol.xyz';

async function submitData(data: CollectedDataEntry[]) {
  const response = await fetch(`${API_BASE_URL}/v1/data`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify(data),
  });

  return response.json();
}

Host Permissions

Only approved hosts can receive data:

{
  "host_permissions": [
    "https://api.privprotocol.xyz/*",
    "https://*.privprotocol.xyz/*",
    "https://*.supabase.co/*"
  ]
}

Privacy Guarantees

What We Collect

  • Anonymized URLs (no query params)
  • Sanitized page titles
  • Time spent on pages
  • Scroll depth percentage
  • Ad network detection
  • Page categories

What We Never Collect

  • Login credentials
  • Form data
  • Personal messages
  • Financial information
  • Health records
  • Email content
  • Exact timestamps
  • IP addresses

Data Minimization

We follow the principle of data minimization:

  1. Collect only what's needed
  2. Anonymize immediately
  3. Aggregate before sharing
  4. Delete after processing

Security Checklist

For Users

  • Keep extension updated
  • Review enabled data types
  • Check wallet connection
  • Monitor earnings for anomalies

For Developers

  • Validate all inputs
  • Sanitize all outputs
  • Log no sensitive data
  • Use strict CSP
  • Rate limit externals
  • Exclude sensitive URLs

Reporting Security Issues

If you discover a security vulnerability:

  1. Do not disclose publicly
  2. Email security@privprotocol.xyz
  3. Include reproduction steps
  4. Allow 90 days for fix

We offer a bug bounty program for valid security reports.


Next Steps