As developers, we've all been there. You're filling out a form on a webpage, maybe testing an API integration or setting up a new service, and you accidentally paste your production API key. By the time you realize what you've done, it's too late—the form has been submitted, and your secret is now potentially exposed in server logs, analytics tools, or worse.
Recently, I came across this exact problem in conversation—what if there was a browser extension that could detect secrets in form fields before submission? The idea was compelling, but the practical challenges were unclear. Could it work at scale? What about token usage and API costs? Would performance be acceptable on complex forms?
The best way to answer these questions was to build a prototype. That's how Chromegg was born—a Chrome extension that uses GitGuardian's API to scan form fields in real-time and alert you when you're about to leak a secret.
Secret leakage happens in many ways. We often think about secrets being committed to Git repositories, but web forms represent another significant attack surface. Consider these scenarios:
By the time you submit the form, your secret has left your machine. It might be stored in server logs, sent to analytics platforms, or even cached by the browser itself. Prevention is far better than remediation.
Chromegg takes a proactive approach. Instead of scanning after submission, it monitors form fields and scans their contents before you submit. When field values change, the extension:
This gives you an immediate visual warning that you're about to leak sensitive information.
The extension is built using Chrome's Manifest V3, which provides several security benefits over the older V2 format. Most notably, it uses a service worker instead of a persistent background page, reducing resource usage and improving security isolation.
The permissions model is intentionally minimal:
"permissions": ["storage", "activeTab"],
"host_permissions": ["https://api.gitguardian.com/*"]
We only request storage (for API credentials) and host permissions for the GitGuardian API. No broad network access, no sensitive browser APIs—just what's needed to do the job.
The extension includes a strict Content Security Policy (CSP) that prevents inline scripts and restricts code execution:
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
This protects against XSS attacks and ensures all code comes from trusted sources.
One of the more interesting architectural decisions was how to handle API communication. Chrome extensions running in content scripts face CORS restrictions when making API calls. The solution? Route all GitGuardian API requests through a background service worker.
The content script sends a message to the background worker:
chrome.runtime.sendMessage(
{
action: 'scanContent',
data: {
apiUrl,
apiKey,
documents,
useMultiscan
}
},
(response) => {
// Handle scan results
}
);
The background worker handles the actual API call, bypassing CORS restrictions entirely.
One of the core viability questions was token usage and cost. Scanning every form field individually would rack up API calls quickly—imagine a form with 50 fields triggering 50 separate scans. The solution was aggregation: collect all form field values and send them in a single scan request.
This approach worked beautifully for typical forms. But what about edge cases? To stress-test the limits, I built test-massive-page.html
, a deliberately extreme scenario that generates 2,000 text fields, each containing ~1KB of random data. This creates approximately 2MB of content that needs to be scanned.
The problem? GitGuardian's API has payload size limits. Sending 2MB in a single request would fail. This is where chunking became necessary:
export function chunkYaml(yamlContent, baseFilename = 'form_data') {
// If already under the limit, return as single chunk
if (getByteSize(yamlContent) <= MAX_CHUNK_SIZE) {
const fieldIds = extractFieldIds(yamlContent);
return [{ document: yamlContent, filename: `${baseFilename}.yaml`, fieldIds }];
}
// Split YAML into individual field entries
const fieldEntries = splitYamlFields(yamlContent);
const chunks = [];
let currentChunk = [];
let currentSize = 0;
for (const entry of fieldEntries) {
const entrySize = getByteSize(entry.yaml);
if (currentSize + entrySize > MAX_CHUNK_SIZE && currentChunk.length > 0) {
chunks.push(createChunkObject(currentChunk, baseFilename, chunks.length));
currentChunk = [];
currentSize = 0;
}
currentChunk.push(entry);
currentSize += entrySize;
}
if (currentChunk.length > 0) {
chunks.push(createChunkObject(currentChunk, baseFilename, chunks.length));
}
return chunks;
}
When content exceeds 1MB, it's automatically split into chunks. Each chunk is sent to GitGuardian's /v1/multiscan
endpoint, which is specifically designed for batch scanning operations.
The massive form test revealed another issue: performance. Updating borders on 2,000 DOM elements simultaneously caused noticeable UI lag. The fix was batching DOM updates using requestAnimationFrame
:
const processBatch = () => {
const start = currentBatch * BATCH_SIZE;
const end = Math.min(start + BATCH_SIZE, fieldEntries.length);
for (let i = start; i < end; i++) {
const [field, fieldId] = fieldEntries[i];
// Update field borders in batches
}
currentBatch++;
if (end < fieldEntries.length) {
requestAnimationFrame(processBatch);
}
};
This spreads the work across multiple animation frames, keeping the UI responsive even when updating thousands of fields.
The extension supports two operating modes:
Continuous Mode (auto-scan): Every time a field value changes, it triggers a scan. This is great for security-conscious users who want constant protection.
Manual Mode (default): Scanning only happens when you click the extension icon. This reduces API calls and gives users more control over when scanning occurs.
The settings page also includes:
https://api.gitguardian.com
)Initially, I experimented with both red borders (secrets found) and green borders (clean fields). Testing revealed this was too noisy—green borders everywhere just created visual clutter.
The final design uses only red borders for fields containing secrets. Clean fields remain unchanged. This follows the principle of "silence is golden"—only alert users when there's a problem.
The project maintains comprehensive unit tests with 90%+ statement and function coverage. Two test pages proved essential:
test-page.html: A standard form with various input types (text, email, password, textarea, contenteditable). This tests basic functionality—can we detect an AWS key in a text field? Does the border appear correctly?
test-massive-page.html: The stress test with 2,000 fields and ~2MB of data. This revealed the chunking requirement, the performance issues with DOM updates, and even uncovered a bug in how we were batching API requests.
It's a good job we tested this on large forms—it really helped flush out some easy wins which makes this tool a lot more compatible with the range of forms that may exist in the wild.
The prototype successfully answered the core questions around viability:
Token Usage: Aggregation keeps costs reasonable. A typical form with 10-20 fields becomes a single API call instead of 20 separate calls. Even the massive 2,000-field stress test only requires 2-3 chunked requests.
Performance: With batched DOM updates and intelligent chunking, the extension handles both simple and complex forms smoothly. The massive form test—an extreme edge case—still completes scanning and border updates in under a second.
Chromegg is open source and available on GitHub. The extension demonstrates several important concepts:
Whether you're building Chrome extensions, integrating with security APIs, or just interested in preventing secret leakage, I hope you find the project useful.
*** This is a Security Bloggers Network syndicated blog from GitGuardian Blog - Take Control of Your Secrets Security authored by Andy Rea. Read the original post at: https://blog.gitguardian.com/building-chromegg-a-chrome-extension-for-real-time-secret-detection/