diff --git a/AUDIT.md b/AUDIT.md new file mode 100644 index 0000000..922d1a2 --- /dev/null +++ b/AUDIT.md @@ -0,0 +1,381 @@ +# Threads DraftCraft - Detaljan Audit Aplikacije + +**Datum:** 2026-03-21 +**Verzija:** 1.0.0 +**Tip aplikacije:** Chrome Extension (Manifest V3) +**Ukupno linija koda:** ~3,830 + +--- + +## 1. PREGLED ARHITEKTURE + +### Struktura +``` +background/background.js (433 LOC) - Service worker, lifecycle, messaging +content/content.js (1,351 LOC) - DOM manipulacija na threads.com +content/content.css (446 LOC) - Stilovi za content script +popup/popup.html (159 LOC) - Popup UI +popup/popup.js (611 LOC) - Popup kontroler +popup/popup.css (783 LOC) - Popup stilovi +manifest.json (47 LOC) - Extension konfiguracija +``` + +### Ocjena arhitekture: 7/10 +- Dobra separacija (background/content/popup) +- Class-based pristup je konzistentan +- Nema build sistema, lintinga, ni testova + +--- + +## 2. SIGURNOSNI PROBLEMI + +### 2.1 KRITIČNO: innerHTML koriscenje bez sanitizacije + +**Fajlovi:** `content/content.js:890`, `content/content.js:1258-1268`, `content/content.js:1309-1324`, `popup/popup.js:548-561` + +```javascript +// content.js:890 - Sort indicator +statusIndicator.innerHTML = `...${this.sortOrder}...`; + +// content.js:1258 - Time indicator fallback +timeIndicator.innerHTML = `
πŸ“… ${draft.scheduledTimeStr}
`; + +// content.js:1309 - Count badge +countBadge.innerHTML = `πŸ“Š ${this.drafts.length}`; + +// popup.js:548 - Info message +infoDiv.innerHTML = `
πŸ“Œ Navigate to Threads.com...
`; +``` + +**Rizik:** Dok su `this.sortOrder` i `this.drafts.length` kontrolisane vrijednosti, `draft.scheduledTimeStr` dolazi iz parsiranog DOM sadrzaja Threads.com stranice. Ako bi napadac kontrolisao sadrzaj koji se renderuje na Threads.com (npr. kroz draft tekst koji sadrzi ` diff --git a/popup/popup.js b/popup/popup.js index 2a00af2..1632ae3 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -21,7 +21,7 @@ class ThreadsDraftCraftPopup { * Initialize the popup */ async init() { - console.log('[Threads DraftCraft] Popup initialized'); + // Popup initialized // Load current settings await this.loadSettings(); @@ -55,7 +55,7 @@ class ThreadsDraftCraftPopup { this.settings = { ...this.settings, ...result }; } catch (error) { - console.error('[Threads DraftCraft] Failed to load settings:', error); + // Settings load failed this.showError('Failed to load settings'); } } @@ -66,9 +66,9 @@ class ThreadsDraftCraftPopup { async saveSettings() { try { await chrome.storage.sync.set(this.settings); - console.log('[Threads DraftCraft] Settings saved:', this.settings); + // Settings saved } catch (error) { - console.error('[Threads DraftCraft] Failed to save settings:', error); + // Settings save failed this.showError('Failed to save settings'); } } @@ -241,7 +241,7 @@ class ThreadsDraftCraftPopup { this.showNoStatsMessage(); } } catch (error) { - console.error('[Threads DraftCraft] Failed to load draft stats:', error); + // Draft stats load failed this.showNoStatsMessage(); } } @@ -261,7 +261,7 @@ class ThreadsDraftCraftPopup { } if (scheduledDrafts) { - scheduledDrafts.textContent = stats.totalDrafts || '0'; + scheduledDrafts.textContent = stats.scheduledDrafts || stats.totalDrafts || '0'; } if (stats.nextScheduled && nextScheduledContainer && nextDraftText && nextDraftTime) { @@ -275,140 +275,58 @@ class ThreadsDraftCraftPopup { /** - * Handle sort order change + * Generic helper: send a message to the active threads.com tab */ - async handleSortOrderChange(order) { - this.settings.sortOrder = order; - await this.saveSettings(); - - // Send message to content script + async _sendToActiveTab(message) { try { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (tab && tab.url.includes('threads.com')) { - await chrome.tabs.sendMessage(tab.id, { - action: 'changeSortOrder', - sortOrder: order - }); + await chrome.tabs.sendMessage(tab.id, message); } } catch (error) { - console.error('[Threads DraftCraft] Failed to change sort order:', error); + // Tab might not have content script } - - this.showSuccess(`Sort order changed to ${order} first`); } /** - * Handle auto sort toggle + * Generic toggle handler to reduce duplication */ - async handleAutoSortToggle(enabled) { - this.settings.autoSort = enabled; + async _toggleSetting(settingKey, action, value, label) { + this.settings[settingKey] = value; await this.saveSettings(); - - // Send message to content script - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (tab && tab.url.includes('threads.com')) { - await chrome.tabs.sendMessage(tab.id, { - action: 'toggleAutoSort', - enabled: enabled - }); - } - } catch (error) { - console.error('[Threads DraftCraft] Failed to toggle auto sort:', error); + await this._sendToActiveTab({ action, enabled: value }); + if (typeof value === 'boolean') { + this.showSuccess(value ? `${label} enabled` : `${label} disabled`); + } else { + this.showSuccess(`${label} changed to ${value} first`); } - - this.showSuccess(enabled ? 'Auto sort enabled' : 'Auto sort disabled'); } - /** - * Handle time indicators toggle - */ - async handleTimeIndicatorsToggle(enabled) { - this.settings.showTimeIndicators = enabled; + async handleSortOrderChange(order) { + this.settings.sortOrder = order; await this.saveSettings(); + await this._sendToActiveTab({ action: 'changeSortOrder', sortOrder: order }); + this.showSuccess(`Sort order changed to ${order} first`); + } - // Send message to content script - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (tab && tab.url.includes('threads.com')) { - await chrome.tabs.sendMessage(tab.id, { - action: 'toggleTimeIndicators', - enabled: enabled - }); - } - } catch (error) { - console.error('[Threads DraftCraft] Failed to toggle time indicators:', error); - } + async handleAutoSortToggle(enabled) { + await this._toggleSetting('autoSort', 'toggleAutoSort', enabled, 'Auto sort'); + } - this.showSuccess(enabled ? 'Time indicators enabled' : 'Time indicators disabled'); + async handleTimeIndicatorsToggle(enabled) { + await this._toggleSetting('showTimeIndicators', 'toggleTimeIndicators', enabled, 'Time indicators'); } - /** - * Handle draft count toggle - */ async handleDraftCountToggle(enabled) { - this.settings.showDraftCount = enabled; - await this.saveSettings(); - - // Send message to content script - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (tab && tab.url.includes('threads.com')) { - await chrome.tabs.sendMessage(tab.id, { - action: 'toggleDraftCount', - enabled: enabled - }); - } - } catch (error) { - console.error('[Threads DraftCraft] Failed to toggle draft count:', error); - } - - this.showSuccess(enabled ? 'Draft count enabled' : 'Draft count disabled'); + await this._toggleSetting('showDraftCount', 'toggleDraftCount', enabled, 'Draft count'); } - /** - * Handle sort indicator toggle - */ async handleSortIndicatorToggle(enabled) { - this.settings.showSortIndicator = enabled; - await this.saveSettings(); - - // Send message to content script - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (tab && tab.url.includes('threads.com')) { - await chrome.tabs.sendMessage(tab.id, { - action: 'toggleSortIndicator', - enabled: enabled - }); - } - } catch (error) { - console.error('[Threads DraftCraft] Failed to toggle sort indicator:', error); - } - - this.showSuccess(enabled ? 'Sort indicator enabled' : 'Sort indicator disabled'); + await this._toggleSetting('showSortIndicator', 'toggleSortIndicator', enabled, 'Sort indicator'); } - /** - * Handle date divider toggle - */ async handleDateDividerToggle(enabled) { - this.settings.showDateDivider = enabled; - await this.saveSettings(); - - try { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (tab && tab.url.includes('threads.com')) { - await chrome.tabs.sendMessage(tab.id, { - action: 'toggleDateDivider', - enabled: enabled - }); - } - } catch (error) { - console.error('[Threads DraftCraft] Failed to toggle date divider:', error); - } - - this.showSuccess(enabled ? 'Date divider enabled' : 'Date divider disabled'); + await this._toggleSetting('showDateDivider', 'toggleDateDivider', enabled, 'Date divider'); } /** @@ -429,7 +347,7 @@ class ThreadsDraftCraftPopup { await this.loadDraftStats(); this.showSuccess('Drafts refreshed'); } catch (error) { - console.error('[Threads DraftCraft] Failed to refresh drafts:', error); + // Refresh failed this.showError('Failed to refresh drafts'); } finally { this.showLoading(false); @@ -446,7 +364,7 @@ class ThreadsDraftCraftPopup { }); window.close(); } catch (error) { - console.error('[Threads DraftCraft] Failed to open Threads:', error); + // Open Threads failed this.showError('Failed to open Threads.com'); } } @@ -462,7 +380,7 @@ class ThreadsDraftCraftPopup { this.showThreadsNotActiveMessage(); } } catch (error) { - console.error('[Threads DraftCraft] Failed to check tab:', error); + // Tab check failed } } @@ -482,11 +400,12 @@ class ThreadsDraftCraftPopup { showError(message) { const errorMessage = document.getElementById('errorMessage'); const errorText = document.getElementById('errorText'); - + if (errorMessage && errorText) { errorText.textContent = message; errorMessage.style.display = 'flex'; - + errorMessage.focus(); + // Auto hide after 5 seconds setTimeout(() => { this.hideError(); @@ -510,11 +429,11 @@ class ThreadsDraftCraftPopup { showSuccess(message) { const successMessage = document.getElementById('successMessage'); const successText = document.getElementById('successText'); - + if (successMessage && successText) { successText.textContent = message; successMessage.style.display = 'flex'; - + // Auto hide after 3 seconds setTimeout(() => { this.hideSuccess(); @@ -545,20 +464,10 @@ class ThreadsDraftCraftPopup { // Show info message const infoDiv = document.createElement('div'); infoDiv.className = 'info-message'; - infoDiv.innerHTML = ` -
- πŸ“Œ Navigate to Threads.com to see draft statistics -
- `; + const innerInfo = document.createElement('div'); + innerInfo.className = 'info-message-content'; + innerInfo.textContent = 'Navigate to Threads.com to see draft statistics'; + infoDiv.appendChild(innerInfo); const statsSection = document.querySelector('.stats-section'); if (statsSection && !document.querySelector('.info-message')) {