ShieldFlow - Real-Time PII Redaction

Auto-redacts PII (emails, credit cards, phone numbers, API keys) from browser content in real-time with visual overlays

Size

26.8 KB

Version

1.0.1

Created

Jan 8, 2026

Updated

13 days ago

1// ==UserScript==
2// @name		ShieldFlow - Real-Time PII Redaction
3// @description		Auto-redacts PII (emails, credit cards, phone numbers, API keys) from browser content in real-time with visual overlays
4// @version		1.0.1
5// @match		*://*/*
6// @icon		https://www.gstatic.com/lamda/images/gemini_sparkle_aurora_33f86dc0c0257da337c63.svg
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // ============================================
12    // SHIELDFLOW - REAL-TIME PII REDACTION ENGINE
13    // ============================================
14
15    console.log('ShieldFlow: Initializing PII Redaction Engine...');
16
17    // Configuration and State Management
18    const CONFIG = {
19        storageKeys: {
20            enabled: 'shieldflow_enabled',
21            emailsEnabled: 'shieldflow_emails',
22            creditCardsEnabled: 'shieldflow_creditcards',
23            phonesEnabled: 'shieldflow_phones',
24            apiKeysEnabled: 'shieldflow_apikeys',
25            excludedSites: 'shieldflow_excluded_sites'
26        },
27        overlayClass: 'shieldflow-redacted',
28        dataAttributes: {
29            type: 'data-shieldflow-type',
30            original: 'data-shieldflow-original'
31        }
32    };
33
34    // PII Detection Patterns (Optimized Regex)
35    const PATTERNS = {
36        email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
37        creditCard: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
38        phone: /\b(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})\b/g,
39        apiKeys: {
40            aws: /\b(AKIA[0-9A-Z]{16})\b/g,
41            openai: /\b(sk-[a-zA-Z0-9]{48})\b/g,
42            stripe: /\b(sk_live_[a-zA-Z0-9]{24,})\b/g,
43            generic: /\b([a-zA-Z0-9_-]{32,})\b/g
44        }
45    };
46
47    // State object to hold current settings
48    let state = {
49        enabled: true,
50        emailsEnabled: true,
51        creditCardsEnabled: true,
52        phonesEnabled: true,
53        apiKeysEnabled: true,
54        excludedSites: [],
55        processedNodes: new WeakSet()
56    };
57
58    // ============================================
59    // STORAGE MANAGEMENT
60    // ============================================
61
62    async function loadSettings() {
63        try {
64            const enabled = await GM.getValue(CONFIG.storageKeys.enabled, true);
65            const emailsEnabled = await GM.getValue(CONFIG.storageKeys.emailsEnabled, true);
66            const creditCardsEnabled = await GM.getValue(CONFIG.storageKeys.creditCardsEnabled, true);
67            const phonesEnabled = await GM.getValue(CONFIG.storageKeys.phonesEnabled, true);
68            const apiKeysEnabled = await GM.getValue(CONFIG.storageKeys.apiKeysEnabled, true);
69            const excludedSites = await GM.getValue(CONFIG.storageKeys.excludedSites, '[]');
70
71            state.enabled = enabled;
72            state.emailsEnabled = emailsEnabled;
73            state.creditCardsEnabled = creditCardsEnabled;
74            state.phonesEnabled = phonesEnabled;
75            state.apiKeysEnabled = apiKeysEnabled;
76            state.excludedSites = JSON.parse(excludedSites);
77
78            console.log('ShieldFlow: Settings loaded', state);
79        } catch (error) {
80            console.error('ShieldFlow: Error loading settings', error);
81        }
82    }
83
84    async function saveSettings() {
85        try {
86            await GM.setValue(CONFIG.storageKeys.enabled, state.enabled);
87            await GM.setValue(CONFIG.storageKeys.emailsEnabled, state.emailsEnabled);
88            await GM.setValue(CONFIG.storageKeys.creditCardsEnabled, state.creditCardsEnabled);
89            await GM.setValue(CONFIG.storageKeys.phonesEnabled, state.phonesEnabled);
90            await GM.setValue(CONFIG.storageKeys.apiKeysEnabled, state.apiKeysEnabled);
91            await GM.setValue(CONFIG.storageKeys.excludedSites, JSON.stringify(state.excludedSites));
92            console.log('ShieldFlow: Settings saved');
93        } catch (error) {
94            console.error('ShieldFlow: Error saving settings', error);
95        }
96    }
97
98    function isCurrentSiteExcluded() {
99        const currentHost = window.location.hostname;
100        return state.excludedSites.some(site => currentHost.includes(site));
101    }
102
103    // ============================================
104    // CSS INJECTION
105    // ============================================
106
107    function injectStyles() {
108        const styles = `
109            .shieldflow-redacted {
110                position: relative;
111                display: inline-block;
112            }
113
114            .shieldflow-redacted::before {
115                content: '';
116                position: absolute;
117                top: -2px;
118                left: -2px;
119                right: -2px;
120                bottom: -2px;
121                background: rgba(0, 0, 0, 0.85);
122                backdrop-filter: blur(8px);
123                -webkit-backdrop-filter: blur(8px);
124                border-radius: 3px;
125                z-index: 999999;
126                pointer-events: none;
127            }
128
129            .shieldflow-redacted::after {
130                content: '🔒';
131                position: absolute;
132                top: 50%;
133                left: 50%;
134                transform: translate(-50%, -50%);
135                font-size: 12px;
136                z-index: 1000000;
137                pointer-events: none;
138            }
139
140            /* Popup UI Styles */
141            .shieldflow-popup {
142                position: fixed;
143                top: 20px;
144                right: 20px;
145                width: 320px;
146                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
147                border-radius: 12px;
148                box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
149                z-index: 10000000;
150                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
151                color: white;
152                padding: 20px;
153                display: none;
154            }
155
156            .shieldflow-popup.visible {
157                display: block;
158                animation: slideIn 0.3s ease-out;
159            }
160
161            @keyframes slideIn {
162                from {
163                    transform: translateX(400px);
164                    opacity: 0;
165                }
166                to {
167                    transform: translateX(0);
168                    opacity: 1;
169                }
170            }
171
172            .shieldflow-header {
173                display: flex;
174                justify-content: space-between;
175                align-items: center;
176                margin-bottom: 20px;
177                padding-bottom: 15px;
178                border-bottom: 1px solid rgba(255, 255, 255, 0.2);
179            }
180
181            .shieldflow-title {
182                font-size: 18px;
183                font-weight: 600;
184                display: flex;
185                align-items: center;
186                gap: 8px;
187            }
188
189            .shieldflow-close {
190                background: rgba(255, 255, 255, 0.2);
191                border: none;
192                color: white;
193                width: 28px;
194                height: 28px;
195                border-radius: 50%;
196                cursor: pointer;
197                font-size: 18px;
198                display: flex;
199                align-items: center;
200                justify-content: center;
201                transition: background 0.2s;
202            }
203
204            .shieldflow-close:hover {
205                background: rgba(255, 255, 255, 0.3);
206            }
207
208            .shieldflow-toggle-group {
209                margin-bottom: 15px;
210            }
211
212            .shieldflow-toggle-item {
213                display: flex;
214                justify-content: space-between;
215                align-items: center;
216                padding: 12px;
217                background: rgba(255, 255, 255, 0.1);
218                border-radius: 8px;
219                margin-bottom: 8px;
220                transition: background 0.2s;
221            }
222
223            .shieldflow-toggle-item:hover {
224                background: rgba(255, 255, 255, 0.15);
225            }
226
227            .shieldflow-toggle-label {
228                font-size: 14px;
229                font-weight: 500;
230            }
231
232            .shieldflow-master-toggle {
233                font-size: 16px;
234                font-weight: 600;
235            }
236
237            .shieldflow-switch {
238                position: relative;
239                width: 48px;
240                height: 24px;
241                background: rgba(255, 255, 255, 0.3);
242                border-radius: 12px;
243                cursor: pointer;
244                transition: background 0.3s;
245            }
246
247            .shieldflow-switch.active {
248                background: #4ade80;
249            }
250
251            .shieldflow-switch::after {
252                content: '';
253                position: absolute;
254                top: 2px;
255                left: 2px;
256                width: 20px;
257                height: 20px;
258                background: white;
259                border-radius: 50%;
260                transition: transform 0.3s;
261            }
262
263            .shieldflow-switch.active::after {
264                transform: translateX(24px);
265            }
266
267            .shieldflow-fab {
268                position: fixed;
269                bottom: 30px;
270                right: 30px;
271                width: 56px;
272                height: 56px;
273                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
274                border-radius: 50%;
275                box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
276                cursor: pointer;
277                display: flex;
278                align-items: center;
279                justify-content: center;
280                font-size: 24px;
281                z-index: 9999999;
282                transition: transform 0.2s, box-shadow 0.2s;
283            }
284
285            .shieldflow-fab:hover {
286                transform: scale(1.1);
287                box-shadow: 0 6px 30px rgba(102, 126, 234, 0.6);
288            }
289
290            .shieldflow-exclusion {
291                margin-top: 15px;
292                padding-top: 15px;
293                border-top: 1px solid rgba(255, 255, 255, 0.2);
294            }
295
296            .shieldflow-exclusion-title {
297                font-size: 13px;
298                font-weight: 600;
299                margin-bottom: 10px;
300                opacity: 0.9;
301            }
302
303            .shieldflow-exclusion-btn {
304                width: 100%;
305                padding: 10px;
306                background: rgba(255, 255, 255, 0.2);
307                border: none;
308                border-radius: 6px;
309                color: white;
310                font-size: 13px;
311                font-weight: 500;
312                cursor: pointer;
313                transition: background 0.2s;
314            }
315
316            .shieldflow-exclusion-btn:hover {
317                background: rgba(255, 255, 255, 0.3);
318            }
319
320            .shieldflow-exclusion-btn.excluded {
321                background: #ef4444;
322            }
323
324            .shieldflow-stats {
325                margin-top: 15px;
326                padding: 12px;
327                background: rgba(0, 0, 0, 0.2);
328                border-radius: 8px;
329                font-size: 12px;
330            }
331
332            .shieldflow-stat-item {
333                display: flex;
334                justify-content: space-between;
335                margin-bottom: 5px;
336            }
337
338            .shieldflow-stat-item:last-child {
339                margin-bottom: 0;
340            }
341        `;
342
343        TM_addStyle(styles);
344        console.log('ShieldFlow: Styles injected');
345    }
346
347    // ============================================
348    // PII DETECTION AND REDACTION ENGINE
349    // ============================================
350
351    let redactionStats = {
352        emails: 0,
353        creditCards: 0,
354        phones: 0,
355        apiKeys: 0
356    };
357
358    function detectAndRedactPII(node) {
359        if (!state.enabled || isCurrentSiteExcluded()) {
360            return;
361        }
362
363        // Skip if already processed
364        if (state.processedNodes.has(node)) {
365            return;
366        }
367
368        // Skip script, style, and already redacted elements
369        if (node.nodeType !== Node.TEXT_NODE || 
370            !node.textContent.trim() ||
371            node.parentElement?.classList.contains(CONFIG.overlayClass)) {
372            return;
373        }
374
375        const parent = node.parentElement;
376        if (!parent || parent.tagName === 'SCRIPT' || parent.tagName === 'STYLE' || parent.tagName === 'NOSCRIPT') {
377            return;
378        }
379
380        let text = node.textContent;
381        let hasMatch = false;
382        const matches = [];
383
384        // Detect Emails
385        if (state.emailsEnabled) {
386            const emailMatches = [...text.matchAll(PATTERNS.email)];
387            emailMatches.forEach(match => {
388                matches.push({ type: 'email', text: match[0], index: match.index });
389                hasMatch = true;
390                redactionStats.emails++;
391            });
392        }
393
394        // Detect Credit Cards
395        if (state.creditCardsEnabled) {
396            const ccMatches = [...text.matchAll(PATTERNS.creditCard)];
397            ccMatches.forEach(match => {
398                matches.push({ type: 'creditcard', text: match[0], index: match.index });
399                hasMatch = true;
400                redactionStats.creditCards++;
401            });
402        }
403
404        // Detect Phone Numbers
405        if (state.phonesEnabled) {
406            const phoneMatches = [...text.matchAll(PATTERNS.phone)];
407            phoneMatches.forEach(match => {
408                matches.push({ type: 'phone', text: match[0], index: match.index });
409                hasMatch = true;
410                redactionStats.phones++;
411            });
412        }
413
414        // Detect API Keys
415        if (state.apiKeysEnabled) {
416            for (const [keyType, pattern] of Object.entries(PATTERNS.apiKeys)) {
417                const keyMatches = [...text.matchAll(pattern)];
418                keyMatches.forEach(match => {
419                    // Filter out common false positives for generic pattern
420                    if (keyType === 'generic' && match[0].length < 40) {
421                        return;
422                    }
423                    matches.push({ type: 'apikey', text: match[0], index: match.index });
424                    hasMatch = true;
425                    redactionStats.apiKeys++;
426                });
427            }
428        }
429
430        if (hasMatch && matches.length > 0) {
431            // Sort matches by index in reverse order to replace from end to start
432            matches.sort((a, b) => b.index - a.index);
433
434            // Create document fragment for efficient DOM manipulation
435            const fragment = document.createDocumentFragment();
436            let lastIndex = text.length;
437
438            // Build the new content with redacted spans
439            for (let i = matches.length - 1; i >= 0; i--) {
440                const match = matches[i];
441                const endIndex = match.index + match.text.length;
442
443                // Add text after this match
444                if (endIndex < lastIndex) {
445                    fragment.insertBefore(
446                        document.createTextNode(text.substring(endIndex, lastIndex)),
447                        fragment.firstChild
448                    );
449                }
450
451                // Create redacted span
452                const span = document.createElement('span');
453                span.className = CONFIG.overlayClass;
454                span.setAttribute(CONFIG.dataAttributes.type, match.type);
455                span.setAttribute(CONFIG.dataAttributes.original, match.text);
456                span.textContent = match.text;
457                span.title = `Protected ${match.type} - Click to reveal`;
458                
459                // Add click to reveal functionality
460                span.style.cursor = 'pointer';
461                span.addEventListener('click', function(e) {
462                    e.stopPropagation();
463                    if (confirm('Reveal protected information?')) {
464                        this.classList.remove(CONFIG.overlayClass);
465                        this.style.cursor = 'default';
466                    }
467                });
468
469                fragment.insertBefore(span, fragment.firstChild);
470                lastIndex = match.index;
471            }
472
473            // Add remaining text before first match
474            if (lastIndex > 0) {
475                fragment.insertBefore(
476                    document.createTextNode(text.substring(0, lastIndex)),
477                    fragment.firstChild
478                );
479            }
480
481            // Replace the text node with the fragment
482            parent.replaceChild(fragment, node);
483            state.processedNodes.add(parent);
484            
485            console.log(`ShieldFlow: Redacted ${matches.length} PII items in element`, parent);
486        }
487    }
488
489    function scanDOM(rootNode = document.body) {
490        if (!state.enabled || isCurrentSiteExcluded()) {
491            return;
492        }
493
494        const walker = document.createTreeWalker(
495            rootNode,
496            NodeFilter.SHOW_TEXT,
497            {
498                acceptNode: function(node) {
499                    // Skip empty text nodes and already processed parents
500                    if (!node.textContent.trim() || state.processedNodes.has(node.parentElement)) {
501                        return NodeFilter.FILTER_REJECT;
502                    }
503                    return NodeFilter.FILTER_ACCEPT;
504                }
505            }
506        );
507
508        const textNodes = [];
509        let currentNode;
510        while (currentNode = walker.nextNode()) {
511            textNodes.push(currentNode);
512        }
513
514        // Process all text nodes
515        textNodes.forEach(node => detectAndRedactPII(node));
516        
517        console.log(`ShieldFlow: Scanned ${textNodes.length} text nodes`);
518    }
519
520    // ============================================
521    // MUTATION OBSERVER (Performance Optimized)
522    // ============================================
523
524    let observerTimeout;
525    const DEBOUNCE_DELAY = 300;
526
527    function debounce(func, delay) {
528        return function(...args) {
529            clearTimeout(observerTimeout);
530            observerTimeout = setTimeout(() => func.apply(this, args), delay);
531        };
532    }
533
534    const debouncedScan = debounce((mutations) => {
535        if (!state.enabled || isCurrentSiteExcluded()) {
536            return;
537        }
538
539        const nodesToScan = new Set();
540
541        mutations.forEach(mutation => {
542            mutation.addedNodes.forEach(node => {
543                if (node.nodeType === Node.ELEMENT_NODE) {
544                    nodesToScan.add(node);
545                } else if (node.nodeType === Node.TEXT_NODE) {
546                    detectAndRedactPII(node);
547                }
548            });
549        });
550
551        // Scan all collected element nodes
552        nodesToScan.forEach(node => scanDOM(node));
553    }, DEBOUNCE_DELAY);
554
555    function initMutationObserver() {
556        const observer = new MutationObserver(debouncedScan);
557
558        observer.observe(document.body, {
559            childList: true,
560            subtree: true,
561            characterData: false,
562            attributes: false
563        });
564
565        console.log('ShieldFlow: MutationObserver initialized');
566        return observer;
567    }
568
569    // ============================================
570    // POPUP UI
571    // ============================================
572
573    function createPopupUI() {
574        // Create FAB button
575        const fab = document.createElement('div');
576        fab.className = 'shieldflow-fab';
577        fab.innerHTML = '🛡️';
578        fab.title = 'ShieldFlow Settings';
579        
580        // Create popup
581        const popup = document.createElement('div');
582        popup.className = 'shieldflow-popup';
583        popup.innerHTML = `
584            <div class="shieldflow-header">
585                <div class="shieldflow-title">
586                    🛡️ ShieldFlow
587                </div>
588                <button class="shieldflow-close">×</button>
589            </div>
590
591            <div class="shieldflow-toggle-group">
592                <div class="shieldflow-toggle-item">
593                    <span class="shieldflow-toggle-label shieldflow-master-toggle">Master Protection</span>
594                    <div class="shieldflow-switch ${state.enabled ? 'active' : ''}" data-toggle="master"></div>
595                </div>
596            </div>
597
598            <div class="shieldflow-toggle-group">
599                <div class="shieldflow-toggle-item">
600                    <span class="shieldflow-toggle-label">📧 Hide Emails</span>
601                    <div class="shieldflow-switch ${state.emailsEnabled ? 'active' : ''}" data-toggle="emails"></div>
602                </div>
603                <div class="shieldflow-toggle-item">
604                    <span class="shieldflow-toggle-label">💳 Hide Credit Cards</span>
605                    <div class="shieldflow-switch ${state.creditCardsEnabled ? 'active' : ''}" data-toggle="creditcards"></div>
606                </div>
607                <div class="shieldflow-toggle-item">
608                    <span class="shieldflow-toggle-label">📱 Hide Phone Numbers</span>
609                    <div class="shieldflow-switch ${state.phonesEnabled ? 'active' : ''}" data-toggle="phones"></div>
610                </div>
611                <div class="shieldflow-toggle-item">
612                    <span class="shieldflow-toggle-label">🔑 Hide API Keys</span>
613                    <div class="shieldflow-switch ${state.apiKeysEnabled ? 'active' : ''}" data-toggle="apikeys"></div>
614                </div>
615            </div>
616
617            <div class="shieldflow-stats">
618                <div class="shieldflow-stat-item">
619                    <span>Emails Protected:</span>
620                    <span id="stat-emails">0</span>
621                </div>
622                <div class="shieldflow-stat-item">
623                    <span>Credit Cards Protected:</span>
624                    <span id="stat-creditcards">0</span>
625                </div>
626                <div class="shieldflow-stat-item">
627                    <span>Phones Protected:</span>
628                    <span id="stat-phones">0</span>
629                </div>
630                <div class="shieldflow-stat-item">
631                    <span>API Keys Protected:</span>
632                    <span id="stat-apikeys">0</span>
633                </div>
634            </div>
635
636            <div class="shieldflow-exclusion">
637                <div class="shieldflow-exclusion-title">Site Exclusion</div>
638                <button class="shieldflow-exclusion-btn" data-action="exclude">
639                    ${isCurrentSiteExcluded() ? '✓ Site Excluded' : 'Exclude This Site'}
640                </button>
641            </div>
642        `;
643
644        document.body.appendChild(fab);
645        document.body.appendChild(popup);
646
647        // Event Listeners
648        fab.addEventListener('click', () => {
649            popup.classList.toggle('visible');
650            updateStatsDisplay();
651        });
652
653        popup.querySelector('.shieldflow-close').addEventListener('click', () => {
654            popup.classList.remove('visible');
655        });
656
657        // Toggle switches
658        popup.querySelectorAll('.shieldflow-switch').forEach(toggle => {
659            toggle.addEventListener('click', async function() {
660                const toggleType = this.getAttribute('data-toggle');
661                this.classList.toggle('active');
662
663                switch(toggleType) {
664                    case 'master':
665                        state.enabled = !state.enabled;
666                        if (state.enabled) {
667                            scanDOM();
668                        } else {
669                            removeAllRedactions();
670                        }
671                        break;
672                    case 'emails':
673                        state.emailsEnabled = !state.emailsEnabled;
674                        break;
675                    case 'creditcards':
676                        state.creditCardsEnabled = !state.creditCardsEnabled;
677                        break;
678                    case 'phones':
679                        state.phonesEnabled = !state.phonesEnabled;
680                        break;
681                    case 'apikeys':
682                        state.apiKeysEnabled = !state.apiKeysEnabled;
683                        break;
684                }
685
686                await saveSettings();
687                
688                // Re-scan if enabling a category
689                if (state.enabled) {
690                    removeAllRedactions();
691                    redactionStats = { emails: 0, creditCards: 0, phones: 0, apiKeys: 0 };
692                    scanDOM();
693                    updateStatsDisplay();
694                }
695            });
696        });
697
698        // Exclusion button
699        popup.querySelector('[data-action="exclude"]').addEventListener('click', async function() {
700            const currentHost = window.location.hostname;
701            const isExcluded = isCurrentSiteExcluded();
702
703            if (isExcluded) {
704                state.excludedSites = state.excludedSites.filter(site => !currentHost.includes(site));
705                this.textContent = 'Exclude This Site';
706                this.classList.remove('excluded');
707                scanDOM();
708            } else {
709                state.excludedSites.push(currentHost);
710                this.textContent = '✓ Site Excluded';
711                this.classList.add('excluded');
712                removeAllRedactions();
713            }
714
715            await saveSettings();
716        });
717
718        console.log('ShieldFlow: Popup UI created');
719    }
720
721    function updateStatsDisplay() {
722        document.getElementById('stat-emails').textContent = redactionStats.emails;
723        document.getElementById('stat-creditcards').textContent = redactionStats.creditCards;
724        document.getElementById('stat-phones').textContent = redactionStats.phones;
725        document.getElementById('stat-apikeys').textContent = redactionStats.apiKeys;
726    }
727
728    function removeAllRedactions() {
729        document.querySelectorAll(`.${CONFIG.overlayClass}`).forEach(span => {
730            const text = span.textContent;
731            const textNode = document.createTextNode(text);
732            span.parentNode.replaceChild(textNode, span);
733        });
734        state.processedNodes = new WeakSet();
735        console.log('ShieldFlow: All redactions removed');
736    }
737
738    // ============================================
739    // INITIALIZATION
740    // ============================================
741
742    async function init() {
743        console.log('ShieldFlow: Starting initialization...');
744
745        // Load settings first
746        await loadSettings();
747
748        // Check if site is excluded
749        if (isCurrentSiteExcluded()) {
750            console.log('ShieldFlow: Current site is excluded, skipping initialization');
751            return;
752        }
753
754        // Inject styles
755        injectStyles();
756
757        // Wait for body to be ready
758        TM_runBody(() => {
759            console.log('ShieldFlow: Body ready, starting PII detection...');
760
761            // Initial scan
762            if (state.enabled) {
763                scanDOM();
764                console.log('ShieldFlow: Initial scan complete', redactionStats);
765            }
766
767            // Initialize mutation observer
768            initMutationObserver();
769
770            // Create popup UI
771            createPopupUI();
772
773            console.log('ShieldFlow: Fully initialized and monitoring for PII');
774        });
775    }
776
777    // Start the extension
778    init();
779
780})();
ShieldFlow - Real-Time PII Redaction | Robomonkey