@vocoweb/residency
v1.1.0
Published
Schrems II Data Sovereignty Lock with Cloudflare Workers integration and DNS resolution hooks
Maintainers
Readme
@vocoweb/residency
Production-ready Schrems II data sovereignty lock with Cloudflare Workers integration
Features
- DNS Resolution Hook: Intercepts fetch and axios calls for automatic enforcement
- Region Allowlist: EU_STRICT, GDPR_ADEQUATE, and SCCS_ONLY modes
- Cloudflare Workers: Edge function template included for global enforcement
- DPIA Audit Trail: Data Protection Impact Assessment logging
- Client & Server Support: Works in both browser and Node.js environments
- IP-Based Detection: Automatic region detection from user IP
Installation
npm install @vocoweb/residency
# or
yarn add @vocoweb/residency
# or
pnpm add @vocoweb/residencyQuick Start
1. Server-Side Middleware
import { detectRegion, parseIpFromHeaders, isDestinationAllowed } from '@vocoweb/residency/server';
// API route middleware
export async function GET(request: Request) {
// Detect user region from IP
const headers = Object.fromEntries(request.headers.entries());
const ip = parseIpFromHeaders(headers);
const region = await detectRegion(ip, headers);
// Check if external API call is allowed
const result = await isDestinationAllowed('https://api.example.com', region);
if (!result.allowed) {
return new Response('Request blocked by data residency policy', {
status: 403,
});
}
// Proceed with request
}2. Client-Side Fetch Interception
'use client';
import { installFetchInterception } from '@vocoweb/residency/client';
export default function RootLayout() {
useEffect(() => {
// Install fetch interception
const cleanup = installFetchInterception({
blockNonEuEgress: true,
blockedDestinations: [
'analytics.google.com',
'tracking.example.com'
]
});
return cleanup;
}, []);
return <YourApp />;
}
// All fetch calls are now checked
await fetch('https://api.example.com/data'); // Allowed or blocked based on policy3. Cloudflare Workers Edge Function
// worker.ts
import { detectRegionFromHeaders, isDestinationAllowed } from '@vocoweb/residency/server';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const headers = Object.fromEntries(request.headers.entries());
const region = detectRegionFromHeaders(headers) || 'global';
// Check if request is external
if (isExternalRequest(request.url)) {
const result = await isDestinationAllowed(request.url, region);
if (!result.allowed) {
return new Response('Request blocked by data residency policy', {
status: 403,
headers: {
'X-Residency-Block-Reason': result.reason
}
});
}
}
// Proxy the request
return fetch(request);
}
};4. Region Detection
import { isEuCountry, hasGdprAdequacy, detectRegion } from '@vocoweb/residency/server';
// Check if country is in EU
const inEu = isEuCountry('DE'); // true
const inEu = isEuCountry('US'); // false
// Check GDPR adequacy
const hasAdequacy = hasGdprAdequacy('JP'); // true (Japan has adequacy)
// Detect region from IP
const region = await detectRegion('192.0.2.1', {});
// Returns: 'eu', 'us', 'asia', etc.5. DPIA Logging
import { logDpiaEvent, getDpiaLogs } from '@vocoweb/residency/server';
// Log data transfer
await logDpiaEvent({
type: 'data_transfer',
sourceRegion: 'eu',
destination: 'https://api.us-service.com',
destinationRegion: 'us',
allowed: false,
reason: 'Destination not in GDPR adequate country',
userId: 'user-123',
ipAddress: '192.0.2.1'
});
// Get DPIA logs
const logs = await getDpiaLogs({
userId: 'user-123',
startDate: new Date('2024-01-01'),
endDate: new Date('2024-01-31')
});API Reference
Server Functions
import {
detectRegion,
parseIpFromHeaders,
isDestinationAllowed,
detectRegionFromHeaders,
isEuCountry,
hasGdprAdequacy
} from '@vocoweb/residency/server';
// Detect region from IP and headers
await detectRegion(
ip: string,
headers: Record<string, string>
): Promise<Region>;
// Parse IP from headers
parseIpFromHeaders(headers: Record<string, string>): string | null;
// Check if destination is allowed
await isDestinationAllowed(
destination: string,
region: Region
): Promise<{
allowed: boolean;
reason?: string;
region?: string;
}>;
// Detect region from headers
detectRegionFromHeaders(headers: Record<string, string>): Region | null;
// Check if country is in EU
isEuCountry(countryCode: string): boolean;
// Check if country has GDPR adequacy
hasGdprAdequacy(countryCode: string): boolean;Client Functions
import {
installFetchInterception,
uninstallFetchInterception,
checkUrlAllowed
} from '@vocoweb/residency/client';
// Install fetch interception
const cleanup = installFetchInterception(config: {
blockNonEuEgress?: boolean;
blockedDestinations?: string[];
allowedRegions?: Region[];
onBlocked?: (url: string, reason: string) => void;
}): () => void;
// Uninstall fetch interception
uninstallFetchInterception(): void;
// Check if URL is allowed
await checkUrlAllowed(url: string): Promise<{
allowed: boolean;
reason?: string;
}>;DPIA Logging
import {
logDpiaEvent,
getDpiaLogs,
exportDpiaLogs
} from '@vocoweb/residency/server';
// Log DPIA event
await logDpiaEvent({
type: 'data_transfer' | 'data_access' | 'data_processing';
sourceRegion: string;
destination: string;
destinationRegion?: string;
allowed: boolean;
reason?: string;
userId?: string;
ipAddress?: string;
metadata?: Record<string, unknown>;
}): Promise<void>;
// Get DPIA logs
await getDpiaLogs(filters?: {
userId?: string;
type?: string;
startDate?: Date;
endDate?: Date;
limit?: number;
}): Promise<DpiaLog[]>;
// Export DPIA logs
await exportDpiaLogs(format: 'json' | 'csv'): Promise<Buffer>;Configuration
import type { DataResidencyConfig } from '@vocoweb/residency';
import { configureResidency } from '@vocoweb/residency/server';
const config: Partial<DataResidencyConfig> = {
// Default region
defaultRegion: 'eu',
// Allowed regions
allowedRegions: ['eu', 'eea', 'gdpr_adequate'],
// Block non-EU egress
blockNonEuEgress: true,
// Blocked destinations
blockedDestinations: [
'analytics.google.com',
'tracking.example.com',
'*.us-api.com'
],
// Destination rules
destinationRules: [
{
pattern: '*.supabase.co',
allowedRegions: ['eu'],
description: 'Supabase EU region'
},
{
pattern: '*.cloudflare.com',
allowedRegions: ['global'],
description: 'Cloudflare edge functions'
}
],
// DPIA logging
logDpiaEvents: true,
dpiaRetentionDays: 365,
// DNS enforcement
enforceDnsCheck: true
};
configureResidency(config);Region Modes
EU_STRICT
Only allows data to stay within the EU/EEA:
configureResidency({
mode: 'EU_STRICT',
allowedRegions: ['eu', 'eea']
});GDPR_ADEQUATE
Allows data transfer to countries with GDPR adequacy decisions:
configureResidency({
mode: 'GDPR_ADEQUATE',
allowedRegions: ['eu', 'eea', 'gdpr_adequate']
// Includes: Japan, UK, Switzerland, Canada (commercial), etc.
});SCCS_ONLY
Only allows transfers with Standard Contractual Clauses (SCCs):
configureResidency({
mode: 'SCCS_ONLY',
allowWithScc: true,
requireSccVerification: true
});GDPR Adequate Countries
The following countries are recognized as having GDPR adequacy:
- European Union: All EU member states
- European Economic Area: Norway, Iceland, Liechtenstein
- United Kingdom: Post-Brexit adequacy
- Switzerland: Federal Data Protection Act adequacy
- Japan: Mutual adequacy arrangement
- Canada: Commercial adequacy (PIPEDA)
- Andorra, Argentina, Faroe Islands, Guernsey, Israel, Isle of Man, Jersey, New Zealand, Uruguay
Cloudflare Workers Template
// workers-site/data-residency-worker.ts
import { detectRegionFromHeaders, isDestinationAllowed } from '@vocoweb/residency/server';
interface Env {
RESIDENCY_MODE: 'EU_STRICT' | 'GDPR_ADEQUATE' | 'SCCS_ONLY';
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const headers = Object.fromEntries(request.headers.entries());
// Detect region from CF-IPCountry header
const region = detectRegionFromHeaders(headers) || 'global';
// Check external requests
const url = new URL(request.url);
if (isExternalRequest(url.href)) {
const result = await isDestinationAllowed(url.href, region);
if (!result.allowed) {
return new Response('Request blocked by data residency policy', {
status: 403,
headers: {
'Content-Type': 'application/json',
'X-Residency-Block-Reason': result.reason || 'Unknown'
}
});
}
}
// Proxy request
return fetch(request);
}
};
function isExternalRequest(url: string): boolean {
const parsed = new URL(url);
return !parsed.hostname.endsWith('.yourdomain.com');
}Best Practices
- Enable DPIA Logging: Always log data transfers for audit purposes
- Use Edge Enforcement: Deploy Cloudflare Workers for global enforcement
- Regular Reviews: Review and update region allowlists quarterly
- Document SCCs: Keep records of Standard Contractual Clauses
- Monitor Blocked Requests: Alert on high block rates for potential issues
Legal Compliance
This package helps comply with:
- GDPR Article 44: General principle for transfers
- GDPR Article 45: Transfers on the basis of an adequacy decision
- GDPR Article 46: Transfers subject to appropriate safeguards
- Schrems II: Data Protection Framework for EU-US transfers
License
MIT
Made with ❤️ by VocoWeb
