Claude-skill-registry browser-extension-developer
Chrome Manifest v3 extension development for Vigil Guard v2.0.0. Use for plugin development, content scripts, service workers, webhook integration with 3-branch detection, browser fingerprinting, and extension debugging.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/browser-extension-developer" ~/.claude/skills/majiayu000-claude-skill-registry-browser-extension-developer && rm -rf "$T"
manifest:
skills/data/browser-extension-developer/SKILL.mdsource content
Browser Extension Developer (v2.0.0)
Overview
Chrome Manifest v3 browser extension for Vigil Guard providing client-side prompt injection protection through webhook proxy integration with 3-branch parallel detection architecture and browser fingerprinting.
When to Use This Skill
- Developing Chrome extension (plugin/)
- Implementing Manifest v3 features
- Working with content scripts and background workers
- Managing webhook integration with 3-branch detection
- Implementing browser fingerprinting
- Debugging extension issues
Tech Stack
- Manifest v3 (Chrome Extensions API)
- JavaScript ES6+ (no build step)
- Service Worker (background.js)
- Content Scripts (content.js)
- Popup UI (HTML/CSS)
Project Structure
plugin/ ├── manifest.json # Extension manifest (Manifest v3) ├── background.js # Service Worker (webhook proxy) ├── content.js # Content script (ChatGPT integration) ├── popup.html # Extension popup UI ├── popup.js # Popup logic ├── config.js # Shared configuration ├── icons/ # Extension icons │ ├── icon16.png │ ├── icon48.png │ └── icon128.png └── README.md # Installation guide
v2.0.0 Response Structure
Webhook Response Format (3-Branch Detection)
{ "status": "BLOCKED", "arbiter_decision": "BLOCK", "threat_score": 85, "branch_a_score": 72, "branch_b_score": 88, "branch_c_score": 91, "branch_a_timing_ms": 45, "branch_b_timing_ms": 120, "branch_c_timing_ms": 250, "detected_categories": ["PROMPT_INJECTION", "JAILBREAK"], "sanitized_input": null, "pii_detected": false, "pipeline_version": "2.0.0" }
Decision Mapping
// v2.0.0 arbiter decisions const DECISION_MAP = { 'BLOCK': { allow: false, sanitize: false }, 'SANITIZE': { allow: true, sanitize: true }, 'ALLOW': { allow: true, sanitize: false } };
Manifest v3 Structure
{ "manifest_version": 3, "name": "Vigil Guard Browser Extension", "version": "2.0.0", "description": "Client-side prompt injection protection with 3-branch detection", "permissions": [ "storage", "activeTab" ], "host_permissions": [ "https://chat.openai.com/*", "http://localhost:5678/*" ], "background": { "service_worker": "background.js", "type": "module" }, "content_scripts": [{ "matches": ["https://chat.openai.com/*"], "js": ["content.js"], "run_at": "document_idle" }], "action": { "default_popup": "popup.html", "default_icon": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" } } }
Common Tasks
Task 1: Content Script (v2.0.0 Integration)
// content.js - Updated for v2.0.0 response format (function() { 'use strict'; // Generate unique client ID let clientId = localStorage.getItem('vigil_client_id'); if (!clientId) { clientId = 'client_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('vigil_client_id', clientId); } // Intercept form submission function interceptChatSubmit() { const form = document.querySelector('form[class*="composer"]'); if (!form) return; form.addEventListener('submit', async function(e) { e.preventDefault(); e.stopPropagation(); const textarea = form.querySelector('textarea'); const userInput = textarea.value.trim(); if (!userInput) return; // Get browser metadata const browserMetadata = { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, screenResolution: `${screen.width}x${screen.height}`, timestamp: new Date().toISOString() }; // Send to Vigil Guard via background script chrome.runtime.sendMessage({ type: 'ANALYZE_PROMPT', data: { chatInput: userInput, clientId: clientId, browser_metadata: browserMetadata } }, response => { // v2.0.0: Check arbiter_decision instead of status const decision = response.arbiter_decision || response.status; if (decision === 'ALLOW') { form.submit(); } else if (decision === 'SANITIZE') { // Use sanitized input if available if (response.sanitized_input) { textarea.value = response.sanitized_input; } form.submit(); } else if (decision === 'BLOCK') { // v2.0.0: Show detailed branch scores const branchInfo = response.branch_a_score !== undefined ? `\n\nBranch Scores:\n• Heuristics: ${response.branch_a_score}\n• Semantic: ${response.branch_b_score}\n• LLM Guard: ${response.branch_c_score}` : ''; alert(`⚠️ Vigil Guard: Potential prompt injection detected!\n\nThreat Score: ${response.threat_score}\nCategories: ${(response.detected_categories || []).join(', ')}${branchInfo}`); } }); }, true); } // Initialize when DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', interceptChatSubmit); } else { interceptChatSubmit(); } })();
Task 2: Background Service Worker (v2.0.0)
// background.js - Updated for v2.0.0 chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === 'ANALYZE_PROMPT') { chrome.storage.sync.get(['webhookUrl'], async (items) => { const webhookUrl = items.webhookUrl || 'http://localhost:5678/webhook/default'; try { const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...request.data, sessionId: `ext_${Date.now()}` }) }); const result = await response.json(); // v2.0.0: Normalize response for backward compatibility const normalizedResult = { ...result, // Map arbiter_decision to status if not present status: result.status || result.arbiter_decision, // Include branch scores if available branch_a_score: result.branch_a_score, branch_b_score: result.branch_b_score, branch_c_score: result.branch_c_score, // Map detected categories detected_categories: result.detected_categories || result.detectedCategories || [] }; sendResponse(normalizedResult); } catch (error) { console.error('Vigil Guard error:', error); sendResponse({ status: 'ALLOW', arbiter_decision: 'ALLOW', // v2.0.0 field error: error.message }); } }); return true; } });
Task 3: Popup Configuration UI (v2.0.0)
<!-- popup.html - Updated for v2.0.0 --> <!DOCTYPE html> <html> <head> <title>Vigil Guard v2.0.0 Settings</title> <style> body { width: 320px; padding: 12px; font-family: Arial, sans-serif; } h3 { margin: 0 0 10px 0; } input { width: 100%; padding: 8px; margin: 5px 0; box-sizing: border-box; } button { width: 100%; padding: 10px; margin: 5px 0; cursor: pointer; } .primary { background: #4CAF50; color: white; border: none; } .secondary { background: #2196F3; color: white; border: none; } .status { margin-top: 10px; padding: 8px; border-radius: 4px; font-size: 12px; } .success { background: #d4edda; color: #155724; } .error { background: #f8d7da; color: #721c24; } .info { background: #cce5ff; color: #004085; } .branch-status { font-size: 11px; margin-top: 8px; } .branch-status div { margin: 2px 0; } .healthy { color: #155724; } .unhealthy { color: #721c24; } </style> </head> <body> <h3>Vigil Guard v2.0.0</h3> <label>Webhook URL:</label> <input type="text" id="webhookUrl" placeholder="http://localhost:5678/webhook/xxx"> <button id="save" class="primary">Save Configuration</button> <button id="test" class="secondary">Test Connection</button> <button id="healthCheck" class="secondary">Check Branch Health</button> <div id="status"></div> <div id="branchStatus" class="branch-status"></div> <script src="popup.js"></script> </body> </html>
// popup.js - Updated for v2.0.0 document.getElementById('save').addEventListener('click', () => { const webhookUrl = document.getElementById('webhookUrl').value; chrome.storage.sync.set({ webhookUrl }, () => { showStatus('Configuration saved!', 'success'); }); }); document.getElementById('test').addEventListener('click', async () => { const webhookUrl = document.getElementById('webhookUrl').value; try { const response = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ chatInput: 'test connection', sessionId: 'extension_test' }) }); if (response.ok) { const result = await response.json(); // v2.0.0: Check for arbiter_decision if (result.arbiter_decision) { showStatus(`✅ Connected (v2.0.0) - Arbiter: ${result.arbiter_decision}`, 'success'); } else { showStatus('✅ Connected (legacy mode)', 'success'); } } else { showStatus(`❌ Error: ${response.status}`, 'error'); } } catch (error) { showStatus(`❌ Connection failed: ${error.message}`, 'error'); } }); // v2.0.0: Branch health check document.getElementById('healthCheck').addEventListener('click', async () => { const webhookUrl = document.getElementById('webhookUrl').value; const baseUrl = new URL(webhookUrl).origin; try { // Note: This requires backend proxy endpoint const response = await fetch(`${baseUrl}/api/health/branches`); if (response.ok) { const branches = await response.json(); showBranchStatus(branches); } else { showStatus('❌ Health check unavailable', 'error'); } } catch (error) { showStatus('❌ Cannot reach health endpoint', 'error'); } }); function showStatus(message, type) { const status = document.getElementById('status'); status.textContent = message; status.className = `status ${type}`; } function showBranchStatus(branches) { const container = document.getElementById('branchStatus'); container.innerHTML = ` <div class="${branches.branch_a?.healthy ? 'healthy' : 'unhealthy'}"> Branch A (Heuristics): ${branches.branch_a?.healthy ? '✅' : '❌'} </div> <div class="${branches.branch_b?.healthy ? 'healthy' : 'unhealthy'}"> Branch B (Semantic): ${branches.branch_b?.healthy ? '✅' : '❌'} </div> <div class="${branches.branch_c?.healthy ? 'healthy' : 'unhealthy'}"> Branch C (LLM Guard): ${branches.branch_c?.healthy ? '✅' : '❌'} </div> `; showStatus('Branch status updated', 'info'); } // Load saved configuration chrome.storage.sync.get(['webhookUrl'], (items) => { if (items.webhookUrl) { document.getElementById('webhookUrl').value = items.webhookUrl; } });
Task 4: Browser Fingerprinting
// config.js function getBrowserFingerprint() { return { userAgent: navigator.userAgent, language: navigator.language, languages: navigator.languages.join(','), platform: navigator.platform, hardwareConcurrency: navigator.hardwareConcurrency, deviceMemory: navigator.deviceMemory, screenResolution: `${screen.width}x${screen.height}`, colorDepth: screen.colorDepth, pixelRatio: window.devicePixelRatio, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, plugins: Array.from(navigator.plugins).map(p => p.name).join(','), touchSupport: 'ontouchstart' in window, webGL: getWebGLInfo(), canvas: getCanvasFingerprint() }; } function getWebGLInfo() { const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (!gl) return 'not supported'; const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); return debugInfo ? { vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL), renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) } : 'no debug info'; } function getCanvasFingerprint() { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.textBaseline = 'top'; ctx.font = '14px Arial'; ctx.fillText('Vigil Guard Fingerprint', 2, 2); return canvas.toDataURL().substring(0, 50); }
Integration Points
With n8n-vigil-workflow (v2.0.0):
when: Extension sends request action: 1. Workflow receives browser_metadata + clientId 2. 3-branch parallel detection executes 3. Arbiter v2 makes decision 4. Response includes branch scores 5. Log to ClickHouse with branch columns
With clickhouse-grafana-monitoring (v2.0.0):
-- Query extension usage with branch data SELECT client_id, count() as requests, avg(branch_a_score) as avg_heuristics, avg(branch_b_score) as avg_semantic, avg(branch_c_score) as avg_llm_guard, countIf(arbiter_decision = 'BLOCK') as blocked FROM n8n_logs.events_processed WHERE client_id LIKE 'client_%' GROUP BY client_id ORDER BY requests DESC
Testing & Development
Local Testing
# 1. Load extension in Chrome chrome://extensions/ → Enable Developer Mode → Load unpacked → Select plugin/ # 2. Test on ChatGPT open https://chat.openai.com/ # 3. Check console for v2.0.0 response F12 → Console → Look for "Vigil Guard" messages with branch scores # 4. Verify webhook (check for arbiter_decision) docker logs vigil-n8n | grep "arbiter_decision"
Debugging
// background.js - Add v2.0.0 logging console.log('[Vigil Guard v2.0.0] Analyzing prompt:', request.data); console.log('[Vigil Guard v2.0.0] Response:', { arbiter_decision: response.arbiter_decision, branch_a: response.branch_a_score, branch_b: response.branch_b_score, branch_c: response.branch_c_score });
Troubleshooting
v2.0.0 response not recognized:
// Check for both old and new response formats const decision = response.arbiter_decision || response.status; const categories = response.detected_categories || response.detectedCategories || [];
Branch scores undefined:
// Branch C may timeout - handle gracefully const branchInfo = response.branch_a_score !== undefined ? `Scores: A=${response.branch_a_score}, B=${response.branch_b_score}, C=${response.branch_c_score || 'N/A'}` : 'Branch scores unavailable';
Quick Reference
# Package extension cd plugin && zip -r vigil-guard-extension-v2.0.0.zip * # Install in Chrome chrome://extensions/ → Load unpacked → Select plugin/ # View console F12 → Console # Check v2.0.0 backend curl http://localhost:5678/webhook/default \ -H "Content-Type: application/json" \ -d '{"chatInput":"test","sessionId":"ext_test"}' | jq '.arbiter_decision'
Current Version: v2.0.0 Manifest: v3 Supported Sites: ChatGPT (chat.openai.com) Backend: 3-Branch Parallel Detection (24 nodes) Response Format: arbiter_decision + branch scores