Issue: Several instances of innerHTML assignment with unsanitized user input
Risk: Malicious users could inject JavaScript code through ignored words, site lists, or error types
Impact: Medium severity - could lead to code execution in extension context
-
popup.js - Error Types List (Line 118-130)
- Before:
innerHTML = sorted.map(([type, count]) => ...) - After: Using
createElement+textContentfor safe DOM manipulation - Input source: Error statistics from background script
- Before:
-
popup.js - Ignored Words List (Line 148-160)
- Before:
innerHTML = words.map(word => ...) - After: Using
createElement+textContent - Input source: User-added dictionary words
- Before:
-
popup.js - Site Lists (Line 228-252)
- Before:
innerHTML = enabled.map(site => ...)&innerHTML = disabled.map(site => ...) - After: Using
createElement+textContent - Input source: User-configured site whitelist/blacklist
- Before:
Test Case:
// Before fix: This would execute JavaScript
ignoredWords: ['<img src=x onerror=alert(1)>']
// After fix: Displayed as plain text (safe)
textContent = '<img src=x onerror=alert(1)>' // Renders literallyReviewed Locations:
-
content.js - Suggestion Popup (Line 601)
- Usage:
popup.innerHTML = ... - Status: β
SAFE - Uses
escapeHtml()function on all user data - Protection:
escapeHtml(error.matchedText)escapes all HTML entities
- Usage:
-
content.js - Highlight Backdrop (Line 1310)
- Usage:
backdrop.innerHTML = html - Status: β
SAFE - All text escaped via
escapeHtml() - Protection:
html += escapeHtml(text.substring(lastEnd))
- Usage:
-
content.js - ContentEditable Highlighting (Line 1400)
- Usage:
field.innerHTML = html - Status: β SAFE - Text sanitized before insertion
- Protection:
html += escapeHtml(text.substring(lastEnd))
- Usage:
-
content.js - Keyboard Shortcuts Hint (Line 1687)
- Usage:
shortcutsHint.innerHTML = ... - Status: β SAFE - Static HTML only, no user input
- Usage:
-
content.js - Error Panel (Line 2217)
- Usage:
panel.innerHTML = ... - Status: β SAFE - Static HTML template
- Usage:
escapeHtml() Function (Line 833):
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}- β
Properly escapes:
<,>,&,",' - β Browser-native escaping (most reliable)
Checked for:
- β
eval()- Not found - β
Function()constructor - Not found - β
setTimeout(string)/setInterval(string)- Not found (only function callbacks used) - β
document.write()- Not found - β Unsafe
postMessage- Only internal worker communication (validated)
manifest.json CSP:
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}Analysis:
- β
script-src 'self'- Only extension's own scripts can run - β
No
unsafe-inline- Blocks inline scripts - β
No
unsafe-eval- Blocks eval() (except WASM for performance) - β
object-src 'self'- Restricts plugin sources
Note: 'wasm-unsafe-eval' is acceptable for WebAssembly performance, doesn't affect JavaScript security.
LanguageTool API (background.js Line 2701):
const response = await fetch('https://api.languagetoolplus.com/v2/check', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
text: text,
language: ltLang,
apiKey: apiKey
})
});Security Measures:
- β HTTPS only (encrypted transmission)
- β POST request (doesn't leak data in URL)
- β User opt-in required (disabled by default)
- β User's own API key (not shared credentials)
- β
Response validation:
if (!data || !Array.isArray(data.matches))
Checked for:
- β
Uses
chrome.storage.sync(official Chrome API) - β No localStorage (could leak to web pages)
- β No sessionStorage (could leak to web pages)
- β No cookies (not accessible from extension)
- β No IndexedDB for sensitive data
Storage Contents:
{
enabled: true,
correctionMode: "inline",
ignoredWords: [...], // User dictionary
disabledSites: [...], // Site preferences
languageToolApiKey: "..." // User's own key (local only)
}- β No passwords, credit cards, or PII
- β API key stored locally (user-provided, optional)
- β Cleared on extension uninstall
Required Permissions:
storage- Settings persistence βactiveTab- Current tab grammar checking βoffscreen- Service worker operations βclipboardWrite- Copy corrections βcontextMenus- Right-click menu βhost_permissions: <all_urls>- Work on any site β
Not Requested (Good):
- β
webRequest- Not monitoring network - β
cookies- Not accessing cookies - β
history- Not tracking browsing - β
tabs(full) - OnlyactiveTab(less invasive) - β
geolocation- Not tracking location
Add length limits to prevent memory exhaustion:
// In popup.js - addWord()
if (word.length > 100) {
alert('Word too long (max 100 characters)');
return;
}For LanguageTool API to prevent abuse:
const API_RATE_LIMIT = 10; // Max 10 requests per minute
const apiCallTimestamps = [];
// Check timestamps before calling APICurrently not using external resources β (all local)
| Category | Status | Details |
|---|---|---|
| XSS Vulnerabilities | β FIXED | All innerHTML with user data replaced with textContent |
| Code Injection | β SAFE | No eval(), Function(), or dynamic code execution |
| CSP | β SECURE | Strict policy, no unsafe-inline/unsafe-eval |
| External APIs | β SAFE | HTTPS only, opt-in, validated responses |
| Data Storage | β SAFE | Chrome storage API, no sensitive data leakage |
| Permissions | β MINIMAL | Only required permissions, no excessive access |
Strengths:
- β No external data transmission (except optional LanguageTool)
- β Proper HTML escaping throughout
- β Strict CSP policy
- β Minimal permissions
- β No tracking or analytics
- β Local-first architecture
Minor Notes:
- Input validation could be stricter (but not critical)
- API rate limiting could prevent abuse (optional)
Overall: Extension follows security best practices and is safe for user installation.