diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d83e9b..8340065 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ All notable changes to GitHub Devwatch will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [Unreleased]
+
+### Security
+- GitHub sign-in sessions now stay in Chrome session storage only instead of persisting to local extension storage
+- Added validation for the GitHub device-flow verification URL before opening a browser tab
+- Tightened remote image handling for activity avatars and extension page CSP rules
+
## [1.0.2] - 2025-11-19
### Fixed
diff --git a/PRIVACY.md b/PRIVACY.md
index 021aedb..52a741e 100644
--- a/PRIVACY.md
+++ b/PRIVACY.md
@@ -14,8 +14,8 @@ GitHub Devwatch collects and stores the following data **locally on your device
1. **GitHub OAuth Session**
- Created when you connect GitHub through the built-in device-flow sign-in
- - Stored by the extension in Chrome storage
- - Current builds encrypt the auth session before writing it to local storage and keep a decrypted session copy while the extension is running
+ - Stored by the extension in Chrome session storage for the current browser session only
+ - Cleared when the browser session ends or when you disconnect GitHub in DevWatch
- Used only to authenticate with GitHub's API
- Not sent to third-party services operated by this project
- Never shared with anyone
@@ -56,7 +56,7 @@ All data collected is used exclusively to provide the extension's functionality:
- The extension uses Chrome storage APIs for settings, cached activity, and GitHub sign-in handling
- Settings and repository lists can optionally sync across your Chrome browsers if you use Chrome Sync
-- GitHub sign-in data uses local and session storage rather than Chrome sync
+- GitHub sign-in data uses session storage rather than Chrome sync and is not persisted across browser restarts
- You can clear all data at any time by uninstalling the extension or using Chrome's "Clear extension data" feature
## Third-Party Services
@@ -112,7 +112,7 @@ You have complete control over your data:
Current builds include several concrete safeguards:
- All API requests use HTTPS
-- The GitHub auth session is encrypted before it is persisted locally
+- The GitHub auth session is kept in session storage for the current browser session only
- The codebase includes input sanitization and GitHub URL validation checks
- Extension pages use a Content Security Policy
diff --git a/SECURITY.md b/SECURITY.md
index a8193f9..c8ff207 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -33,8 +33,8 @@ These are better suited for regular issues:
The extension includes several concrete protections, but this project has not been through a formal external security audit.
### GitHub Sign-In Storage
-- GitHub auth sessions are encrypted before they are written to local extension storage
-- A decrypted copy may be cached in session storage while the extension is running
+- GitHub auth sessions are kept in `chrome.storage.session` for the current browser session only
+- Legacy on-disk auth storage is cleared by current builds during sign-in handling
- Never transmitted to third-party servers
### Content Security Policy
diff --git a/manifest.json b/manifest.json
index 1c7478f..a85d343 100644
--- a/manifest.json
+++ b/manifest.json
@@ -34,6 +34,6 @@
"128": "icons/icon128.png"
},
"content_security_policy": {
- "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://api.github.com https://github.com https://registry.npmjs.org; img-src 'self' https: data:; default-src 'self'; style-src 'self'"
+ "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://api.github.com https://github.com https://registry.npmjs.org; img-src 'self' data: https://github.com https://*.github.com https://githubusercontent.com https://*.githubusercontent.com; default-src 'self'; style-src 'self'"
}
}
diff --git a/options/options.html b/options/options.html
index e6471d8..570ba9d 100644
--- a/options/options.html
+++ b/options/options.html
@@ -94,7 +94,7 @@
Connect GitHub
- Security: Your GitHub sign-in session is encrypted with AES-GCM and stored locally on your device.
+ Security: Your GitHub sign-in stays in Chrome session storage and is cleared when the browser session ends.
diff --git a/options/options.js b/options/options.js
index 808da7d..5ce7340 100644
--- a/options/options.js
+++ b/options/options.js
@@ -1,5 +1,5 @@
import { applyTheme, formatDateVerbose } from '../shared/utils.js';
-import { getAuthSession, getAccessToken, getLocalItems, getWatchedRepos, setLocalItem, setWatchedRepos } from '../shared/storage-helpers.js';
+import { clearAuthSession, getAuthSession, getAccessToken, getLocalItems, getWatchedRepos, setLocalItem, setWatchedRepos } from '../shared/storage-helpers.js';
import { createHeaders } from '../shared/github-api.js';
import { STORAGE_CONFIG, VALIDATION_PATTERNS } from '../shared/config.js';
import { validateRepository } from '../shared/repository-validator.js';
@@ -1052,6 +1052,7 @@ async function resetSettings() {
try {
// Clear all storage (both sync and local)
+ await clearAuthSession();
await chrome.storage.sync.clear();
await chrome.storage.local.clear();
diff --git a/popup/views/onboarding-view.js b/popup/views/onboarding-view.js
index 11316e7..19a747a 100644
--- a/popup/views/onboarding-view.js
+++ b/popup/views/onboarding-view.js
@@ -379,7 +379,7 @@ async function renderTokenStep() {
- Your GitHub sign-in session is encrypted with AES-GCM encryption and stored securely on your device. It's only used for GitHub API access and never shared.
+ Your GitHub sign-in stays in Chrome session storage for the current browser session only. It's used only for GitHub API access and is cleared when the browser session ends.
`;
}
diff --git a/shared/auth.js b/shared/auth.js
index 7cf070d..379ec2f 100644
--- a/shared/auth.js
+++ b/shared/auth.js
@@ -1,4 +1,5 @@
import { API_CONFIG, OAUTH_CONFIG } from './config.js';
+import { isValidGitHubAuthUrl } from './security.js';
function getStorageValue(area, key) {
return new Promise((resolve) => {
@@ -181,9 +182,18 @@ export async function requestGitHubDeviceCode() {
export function openGitHubDevicePage(deviceCodeData) {
const targetUrl = deviceCodeData.verificationUriComplete || deviceCodeData.verificationUri || OAUTH_CONFIG.DEVICE_VERIFY_URL;
+ if (!isValidGitHubAuthUrl(targetUrl)) {
+ throw createOAuthError(
+ 'GitHub sign-in returned an unexpected verification URL.',
+ 'invalid_verification_url'
+ );
+ }
+
if (chrome?.tabs?.create) {
chrome.tabs.create({ url: targetUrl });
}
+
+ return targetUrl;
}
export async function pollForGitHubAccessToken(deviceCodeData, options = {}) {
diff --git a/shared/crypto-utils.js b/shared/crypto-utils.js
deleted file mode 100644
index 5665577..0000000
--- a/shared/crypto-utils.js
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * Cryptographic utilities for secure auth session storage
- * Uses Web Crypto API (AES-GCM)
- */
-
-const ALGORITHM = 'AES-GCM';
-const KEY_USAGE = ['encrypt', 'decrypt'];
-const KEY_STORAGE_KEY = 'encryptionKey';
-
-/**
- * Get or create the encryption key
- * Persists the key in chrome.storage.local so it survives restarts
- * @returns {Promise} The encryption key
- */
-async function getEncryptionKey() {
- // Try to get existing key from storage
- const stored = await new Promise((resolve) => {
- chrome.storage.local.get([KEY_STORAGE_KEY], (result) => {
- resolve(result[KEY_STORAGE_KEY]);
- });
- });
-
- if (stored) {
- // Import the stored key
- const keyData = new Uint8Array(stored);
- return await crypto.subtle.importKey(
- 'raw',
- keyData,
- ALGORITHM,
- true,
- KEY_USAGE
- );
- }
-
- // Generate a new key
- const key = await crypto.subtle.generateKey(
- { name: ALGORITHM, length: 256 },
- true,
- KEY_USAGE
- );
-
- // Export and store the new key
- const exported = await crypto.subtle.exportKey('raw', key);
- const keyArray = Array.from(new Uint8Array(exported));
-
- await new Promise((resolve) => {
- chrome.storage.local.set({ [KEY_STORAGE_KEY]: keyArray }, resolve);
- });
-
- return key;
-}
-
-/**
- * Encrypt string data
- * @param {string} data - Data to encrypt
- * @returns {Promise