Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 65 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,79 @@ on:
branches: [ main, master ]

jobs:
lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run linter
run: npm run lint

typecheck:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run type checker
run: npm run typecheck

test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci
- name: Install dependencies
run: npm ci

- name: Run linter
run: npm run lint
- name: Run tests with coverage
run: npm test -- --coverage --coverageReporters=text --coverageReporters=lcov

- name: Run type checker
run: npm run typecheck
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Run tests with coverage
run: npm test -- --coverage --coverageReporters=text --coverageReporters=lcov
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
cache: 'npm'

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Install dependencies
run: npm ci

- name: Validate extension build
run: npm run build
- name: Validate extension build
run: npm run build
19 changes: 10 additions & 9 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ GitHub Devwatch is a Chrome extension for monitoring activity on GitHub reposito

GitHub Devwatch collects and stores the following data **locally on your device only**:

1. **GitHub Personal Access Token**
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 token before writing it to local storage and keep a decrypted session copy while the extension is running
- Current builds encrypt the auth session before writing it to local storage and keep a decrypted session copy while the extension is running
- Used only to authenticate with GitHub's API
- Not sent to third-party services operated by this project
- Never shared with anyone
Expand Down Expand Up @@ -46,16 +47,16 @@ GitHub Devwatch collects and stores the following data **locally on your device

All data collected is used exclusively to provide the extension's functionality:

- Your GitHub token authenticates API requests to GitHub
- Your GitHub connection authenticates API requests to GitHub
- Your repository list determines which repositories to monitor
- Your settings customize how the extension behaves
- Activity data is displayed in the extension popup for your review

## Data Storage

- The extension uses Chrome storage APIs for settings, cached activity, and token handling
- 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
- Token handling uses local and session storage rather than Chrome sync
- GitHub sign-in data uses local and session storage rather than Chrome sync
- You can clear all data at any time by uninstalling the extension or using Chrome's "Clear extension data" feature

## Third-Party Services
Expand All @@ -65,7 +66,7 @@ All data collected is used exclusively to provide the extension's functionality:
This extension communicates with GitHub's API (api.github.com) to fetch repository activity. When you use this extension:

- API requests are made directly from your browser to GitHub
- Requests include your GitHub Personal Access Token for authentication
- Requests include your GitHub OAuth access token for authentication
- GitHub's privacy policy and terms of service apply to these interactions
- See GitHub's privacy policy at: https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement

Expand All @@ -90,7 +91,7 @@ GitHub Devwatch does **NOT**:

The extension requests the following Chrome permissions:

- **storage**: To save your settings, token, and activity data locally
- **storage**: To save your settings, GitHub sign-in state, and activity data locally
- **alarms**: To periodically check for new repository activity
- **notifications**: To show you browser notifications for new activity
- **Host permission for api.github.com**: To fetch repository activity from GitHub's API
Expand All @@ -104,14 +105,14 @@ You have complete control over your data:
- **View Your Data**: All settings are visible in the extension's options page
- **Delete Your Data**: Uninstall the extension to remove all data, or use the "Clear All Data" option in settings
- **Export Your Data**: Use the backup/restore feature to export your settings
- **Revoke Access**: Remove or regenerate your GitHub Personal Access Token at any time via GitHub's settings
- **Revoke Access**: Disconnect locally in DevWatch, and revoke the OAuth app in GitHub's authorized applications settings at any time

## Security

Current builds include several concrete safeguards:

- All API requests use HTTPS
- The token is encrypted before it is persisted locally
- The GitHub auth session is encrypted before it is persisted locally
- The codebase includes input sanitization and GitHub URL validation checks
- Extension pages use a Content Security Policy

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Monitor pull requests, issues, and releases across multiple GitHub repositories

## Key Features

- **Guided Setup** - Built-in setup flow for token creation and repository selection
- **Guided Setup** - Built-in GitHub sign-in flow and repository selection
- **Browser Notifications** - Get notified about new PRs, issues, and releases
- **Multi-Repo Monitoring** - Watch up to 50 repositories from one interface
- **Configurable Updates** - Check every 5, 15, 30, or 60 minutes
Expand All @@ -30,7 +30,7 @@ Monitor pull requests, issues, and releases across multiple GitHub repositories
3. Grant permissions when prompted
4. Follow the guided setup wizard on first launch

**GitHub Token Permissions**: You'll need a [Personal Access Token](https://github.com/settings/tokens/new) with `repo` (for private repos) or `public_repo` (for public only).
**GitHub Sign-In Permissions**: DevWatch uses GitHub OAuth device flow and requests `repo` plus `read:user` so it can monitor private repositories and show the connected account in the UI.

### Manual Installation (For Development)

Expand All @@ -53,7 +53,7 @@ cd devwatch-github
### First-Time Setup

The built-in setup flow walks you through:
1. Create a GitHub token
1. Connect your GitHub account
2. Add repositories to watch
3. Choose activity types (PRs, Issues, Releases)

Expand All @@ -74,7 +74,7 @@ The built-in setup flow walks you through:
Filter by type (All/PRs/Issues/Releases), search activities, refresh manually, or browse the archive. Click any item to open in GitHub.

### Settings Page
Manage your GitHub token, watched repositories, activity filters, check interval, notifications, and theme. Export/import settings for backup.
Manage your GitHub connection, watched repositories, activity filters, check interval, notifications, and theme. Export/import settings for backup.

<div align="center">
<img src="screenshots/settings-page.png" alt="Settings page for configuring repositories" width="600">
Expand All @@ -101,7 +101,7 @@ That said, this project has not gone through a formal accessibility audit or doc

## Privacy & Security Notes

The extension talks directly to GitHub's API and does not use a separate analytics or sync backend. It stores settings and cached activity in Chrome extension storage, and the current build encrypts the GitHub token before persisting it locally while keeping a decrypted session copy available at runtime.
The extension talks directly to GitHub's API and does not use a separate analytics or sync backend. It stores settings and cached activity in Chrome extension storage, and the current build encrypts the GitHub auth session before persisting it locally while keeping a decrypted session copy available at runtime.

- **Direct network access** - Requests go to `api.github.com`, plus `registry.npmjs.org` only when you use package-name lookup
- **Scoped browser permissions** - The manifest asks for `storage`, `alarms`, and `notifications`
Expand Down
8 changes: 4 additions & 4 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ I'll respond within 48 hours and work with you to understand and address the iss

Things I want to know about:
- XSS vulnerabilities or ways to inject malicious code
- Token leakage or insecure storage
- OAuth session leakage or insecure storage
- Ways to access other users' data
- Privilege escalation
- Dependencies with known CVEs
Expand All @@ -32,8 +32,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.

### Token Storage
- GitHub tokens are encrypted before they are written to local extension storage
### 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
- Never transmitted to third-party servers

Expand All @@ -49,7 +49,7 @@ The extension includes several concrete protections, but this project has not be

### API Security
- All requests use HTTPS
- Tokens are included in headers, never in URLs
- OAuth access tokens are included in headers, never in URLs
- Rate limiting is respected to prevent abuse

## Supported Versions
Expand Down
13 changes: 6 additions & 7 deletions background.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createHeaders, handleApiResponse, mapActivity, filterActivitiesByDate } from './shared/github-api.js';
import { getSyncItems, getLocalItems, setLocalItem, getExcludedRepos, getToken, getFilteringSettings } from './shared/storage-helpers.js';
import { getSyncItems, getLocalItems, setLocalItem, getExcludedRepos, getAccessToken, getFilteringSettings, getWatchedRepos } from './shared/storage-helpers.js';
import { extractRepoName } from './shared/repository-utils.js';
import { safelyOpenUrl } from './shared/security.js';

Expand Down Expand Up @@ -73,11 +73,10 @@ if (typeof chrome !== 'undefined' && chrome.notifications) {

async function checkGitHubActivity() {
try {
// Get token from secure local storage
const githubToken = await getToken();
const githubToken = await getAccessToken();

const { watchedRepos, lastCheck, filters, notifications, mutedRepos, snoozedRepos, unmutedRepos } = await getSyncItems([
'watchedRepos',
const watchedRepos = await getWatchedRepos();
const { lastCheck, filters, notifications, mutedRepos, snoozedRepos, unmutedRepos } = await getSyncItems([
'lastCheck',
'filters',
'notifications',
Expand All @@ -87,7 +86,7 @@ async function checkGitHubActivity() {
]);

if (!githubToken) {
console.warn('[DevWatch] No GitHub token found. Please add a token in settings.');
console.warn('[DevWatch] No GitHub connection found. Please connect GitHub in settings.');
return;
}

Expand Down Expand Up @@ -287,7 +286,7 @@ async function fetchRepoActivity(repo, token, since, filters) {
// Store error for user display but don't crash
let userMessage = 'Unable to fetch repository activity';
if (error.message.includes('401')) {
userMessage = 'Authentication failed. Please check your GitHub token.';
userMessage = 'GitHub sign-in expired or was revoked. Reconnect GitHub in settings.';
} else if (error.message.includes('403')) {
userMessage = 'Access denied or rate limit exceeded.';
} else if (error.message.includes('404')) {
Expand Down
5 changes: 3 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"notifications"
],
"host_permissions": [
"https://api.github.com/*"
"https://api.github.com/*",
"https://github.com/*"
],
"background": {
"service_worker": "background.js",
Expand All @@ -33,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://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' https: data:; default-src 'self'; style-src 'self'"
}
}
8 changes: 5 additions & 3 deletions options/controllers/export-import-controller.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { NotificationManager } from '../../shared/ui/notification-manager.js';
import { getWatchedRepos, setWatchedRepos } from '../../shared/storage-helpers.js';

const notifications = NotificationManager.getInstance();

export async function exportSettings() {
try {
const syncData = await chrome.storage.sync.get(null);
const watchedRepos = await getWatchedRepos();

const exportData = {
version: '1.0.0',
exportedAt: new Date().toISOString(),
settings: {
watchedRepos: syncData.watchedRepos || [],
watchedRepos,
mutedRepos: syncData.mutedRepos || [],
pinnedRepos: syncData.pinnedRepos || [],
filters: syncData.filters || { prs: true, issues: true, releases: true },
Expand Down Expand Up @@ -53,7 +55,7 @@ export async function handleImportFile(event, loadSettingsCallback) {
}

const confirmed = confirm(
'This will replace your current settings (except GitHub token). Continue?'
'This will replace your current settings (except your GitHub connection). Continue?'
);

if (!confirmed) {
Expand All @@ -63,8 +65,8 @@ export async function handleImportFile(event, loadSettingsCallback) {
}

const settings = importData.settings;
await setWatchedRepos(settings.watchedRepos || []);
await chrome.storage.sync.set({
watchedRepos: settings.watchedRepos || [],
mutedRepos: settings.mutedRepos || [],
pinnedRepos: settings.pinnedRepos || [],
filters: settings.filters || { prs: true, issues: true, releases: true },
Expand Down
Loading
Loading