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
| Component | Example | Removed |
|---|---|---|
| Query params | ?user=john&id=123 | Yes |
| Hash/fragment | #section-2 | Yes |
| Auth tokens | ?token=abc123 | Yes |
| UTM params | ?utm_source=... | Yes |
Before and After
Before: https://example.com/page?user=john&token=secret#section
After: https://example.com/pageTitle 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
| Original | Sanitized |
|---|---|
John Smith - Profile | [name] - Profile |
Contact: john@email.com | Contact: [email] |
Call 555-123-4567 | Call [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
| Source | Limit | Window |
|---|---|---|
| External messages | 10 | 1 minute |
| API calls | 100 | 1 minute |
| Data submissions | 20 | 5 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
| Attack | Prevention |
|---|---|
| Inline scripts | No eval() or inline JS |
| Remote scripts | Only local scripts run |
| Object injection | No external plugins |
| XSS attacks | No 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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}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
| Category | Examples |
|---|---|
| Healthcare | Hospital portals, health apps |
| Banking | Online banking, financial apps |
| Authentication | Login pages, OAuth flows |
| Gmail, Outlook, inbox pages | |
| Passwords | Password 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:
- Collect only what's needed
- Anonymize immediately
- Aggregate before sharing
- 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:
- Do not disclose publicly
- Email security@privprotocol.xyz
- Include reproduction steps
- Allow 90 days for fix
We offer a bug bounty program for valid security reports.
Next Steps
- Development - Security in development
- Troubleshooting - Security-related issues
- Data Types - What data is collected