-
Notifications
You must be signed in to change notification settings - Fork 22
Description
Feature Request
Add prominent security notices and documentation that clearly explains personal access tokens are stored locally only and never uploaded to any server, including direct links to the source code that proves this.
Description
Users need absolute confidence that their GitHub personal access tokens are secure. We should provide clear, transparent communication about token storage with direct links to the source code that demonstrates tokens never leave the user's machine.
Core Features
1. Security Notice UI
Token Input Screen
function TokenInputScreen() {
return (
<div className="token-input-container">
<div className="security-notice prominent">
<h2>🔒 Your Security is Our Priority</h2>
<div className="security-guarantees">
<div className="guarantee">
<span className="icon">💾</span>
<h3>Stored Locally Only</h3>
<p>Your token is encrypted and stored on YOUR device only</p>
<a href="https://github.com/areibman/bottleneck/blob/main/src/main/storage/secure-storage.ts#L45">
View source code →
</a>
</div>
<div className="guarantee">
<span className="icon">🚫</span>
<h3>Never Uploaded</h3>
<p>We NEVER send your token to our servers or any third party</p>
<a href="https://github.com/areibman/bottleneck/blob/main/src/main/api/github-client.ts#L12">
Verify in source →
</a>
</div>
<div className="guarantee">
<span className="icon">🔐</span>
<h3>Encrypted Storage</h3>
<p>Token is encrypted using your system's keychain</p>
<a href="https://github.com/areibman/bottleneck/blob/main/src/main/storage/keychain.ts#L23">
See encryption code →
</a>
</div>
<div className="guarantee">
<span className="icon">🛡️</span>
<h3>Open Source</h3>
<p>100% open source - audit our code anytime</p>
<a href="https://github.com/areibman/bottleneck">
Browse repository →
</a>
</div>
</div>
<details className="technical-details">
<summary>📋 Technical Details (click to expand)</summary>
<div className="details-content">
<h4>Storage Implementation:</h4>
<code>
// src/main/storage/secure-storage.ts:45
async function storeToken(token: string): Promise<void> {
const encrypted = await keytar.setPassword(
'bottleneck',
'github-token',
token
);
// Token is stored in OS keychain, never sent anywhere
}
</code>
<h4>Network Requests:</h4>
<code>
// src/main/api/github-client.ts:12
class GitHubClient {
constructor(token: string) {
// Token is only used for GitHub API
this.octokit = new Octokit({
auth: token,
baseURL: 'https://api.github.com' // ONLY GitHub
});
}
}
</code>
<h4>What we DO NOT do:</h4>
<ul>
<li>❌ Send tokens to analytics</li>
<li>❌ Include tokens in error reports</li>
<li>❌ Store tokens in plain text</li>
<li>❌ Share tokens between devices</li>
<li>❌ Access tokens without user action</li>
</ul>
</div>
</details>
</div>
<div className="token-input-field">
<label>GitHub Personal Access Token</label>
<input type="password" placeholder="ghp_xxxxxxxxxxxx" />
<small>
Create a token at
<a href="https://github.com/settings/tokens">
github.com/settings/tokens
</a>
</small>
</div>
<button className="save-token">
Save Token Locally
</button>
</div>
);
}2. Settings Page Security Section
function SecuritySettings() {
return (
<div className="security-settings">
<h2>Security & Privacy</h2>
<div className="token-status">
<h3>Token Storage</h3>
<div className="status-card">
<div className="status-indicator">✅ Secure</div>
<p>Token stored in: {getKeychainLocation()}</p>
<p>Encryption: AES-256-GCM</p>
<p>Last accessed: {lastAccessTime}</p>
</div>
<div className="actions">
<button onClick={viewStorageLocation}>
📁 View Storage Location
</button>
<button onClick={verifyNoNetworkCalls}>
🔍 Verify No External Calls
</button>
<button onClick={exportSecurityAudit}>
📊 Export Security Audit
</button>
</div>
</div>
<div className="privacy-report">
<h3>Privacy Report</h3>
<table>
<tr>
<td>External API Calls</td>
<td>GitHub API only</td>
<td><a href="#source">View source</a></td>
</tr>
<tr>
<td>Analytics/Telemetry</td>
<td>None</td>
<td><a href="#source">Verify</a></td>
</tr>
<tr>
<td>Token Transmission</td>
<td>Never leaves device</td>
<td><a href="#source">View code</a></td>
</tr>
<tr>
<td>Data Collection</td>
<td>None</td>
<td><a href="#source">Confirm</a></td>
</tr>
</table>
</div>
</div>
);
}3. Source Code Documentation
secure-storage.ts
/**
* SECURITY NOTICE: Token Storage
* ================================
* This file handles the secure storage of GitHub tokens.
*
* IMPORTANT: Tokens are ONLY stored locally using the OS keychain:
* - macOS: Keychain Access
* - Windows: Credential Manager
* - Linux: Secret Service API / libsecret
*
* Tokens are NEVER:
* - Sent to external servers
* - Included in logs or error reports
* - Stored in plain text
* - Accessible without user authentication
*
* For security audit, see: docs/SECURITY.md
*/
import * as keytar from 'keytar';
import { safeStorage } from 'electron';
const SERVICE_NAME = 'bottleneck';
const ACCOUNT_NAME = 'github-token';
export class SecureTokenStorage {
/**
* Stores token in OS keychain - LOCAL ONLY
* This function NEVER makes network requests
*/
async storeToken(token: string): Promise<void> {
// Validate token format
if (!token.startsWith('ghp_') && !token.startsWith('github_pat_')) {
throw new Error('Invalid token format');
}
// Encrypt if additional encryption is enabled
const encrypted = safeStorage.isEncryptionAvailable()
? safeStorage.encryptString(token)
: token;
// Store in OS keychain (LOCAL ONLY)
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, encrypted);
// Log storage event (no token data)
this.logSecurityEvent('TOKEN_STORED', {
timestamp: Date.now(),
method: 'keychain',
// NEVER log the actual token
});
}
/**
* Retrieves token from LOCAL storage only
*/
async getToken(): Promise<string | null> {
const encrypted = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
if (!encrypted) return null;
return safeStorage.isEncryptionAvailable()
? safeStorage.decryptString(Buffer.from(encrypted))
: encrypted;
}
/**
* Removes token from local storage
*/
async deleteToken(): Promise<void> {
await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
}
}github-api.ts
/**
* SECURITY NOTICE: API Communication
* ===================================
* This file handles ALL network communication.
*
* IMPORTANT: We ONLY communicate with GitHub's API.
*
* NO requests are made to:
* - Our servers (we don't have any)
* - Analytics services
* - Third-party services
* - Telemetry endpoints
*
* You can verify this by:
* 1. Searching the codebase for fetch() and axios calls
* 2. Monitoring network traffic while using the app
* 3. Checking the CSP headers in main/security.ts
*/
import { Octokit } from '@octokit/rest';
export class GitHubAPI {
private octokit: Octokit;
constructor(token: string) {
// Token is ONLY used for GitHub API authentication
this.octokit = new Octokit({
auth: token,
baseUrl: 'https://api.github.com', // ONLY GitHub
// Prevent token leakage in logs
log: {
debug: () => {},
info: () => {},
warn: console.warn,
error: (msg) => {
// Sanitize error messages to remove token
const sanitized = msg.replace(/ghp_[a-zA-Z0-9]{36}/g, '[REDACTED]');
console.error(sanitized);
}
}
});
}
// All API methods only call GitHub...
}4. README Security Section
## 🔒 Security & Privacy
### Your Token is Safe
**We take your security seriously.** Your GitHub personal access token is:
- ✅ **Stored locally only** - Never leaves your device
- ✅ **Encrypted** - Using your operating system's secure keychain
- ✅ **Never uploaded** - We have no servers to send it to
- ✅ **Open source** - Verify our claims yourself
### Verify Our Security Claims
Don't just trust us - verify it yourself:
1. **Check Token Storage:** [`src/main/storage/secure-storage.ts`](https://github.com/areibman/bottleneck/blob/main/src/main/storage/secure-storage.ts#L45)
2. **Check API Calls:** [`src/main/api/github-client.ts`](https://github.com/areibman/bottleneck/blob/main/src/main/api/github-client.ts)
3. **Search for Network Calls:** [Search: `fetch` OR `axios` OR `request`](https://github.com/areibman/bottleneck/search?q=fetch+OR+axios+OR+request)
4. **Monitor Network Traffic:** Use your browser's DevTools to verify no external calls
### Security Architecture
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Your Token │────▶│ OS Keychain │────▶│ GitHub API │
└──────────────┘ └──────────────┘ └──────────────┘
(Local Storage) (Only External)
│
❌ No connection to:
- Our servers (don't exist)
- Analytics
- Third parties
### FAQ
**Q: Can you see my token?**
A: No. It's stored locally on your device only.
**Q: What if I don't trust you?**
A: Great! Check our source code. It's all open source.
**Q: Where exactly is my token stored?**
- macOS: `~/Library/Keychains/`
- Windows: Windows Credential Manager
- Linux: GNOME Keyring / KWallet
**Q: Can I verify no network calls are made?**
A: Yes! Open DevTools Network tab and monitor all requests.
5. In-App Verification Tool
class SecurityVerifier {
async runSecurityAudit() {
const report = {
tokenStorage: await this.verifyTokenStorage(),
networkCalls: await this.verifyNetworkCalls(),
codeIntegrity: await this.verifyCodeIntegrity(),
permissions: await this.verifyPermissions()
};
return {
passed: Object.values(report).every(r => r.passed),
report,
timestamp: Date.now()
};
}
async verifyTokenStorage() {
// Check token is in keychain
const locations = this.getKeychainLocations();
const stored = await this.checkTokenLocation();
return {
passed: stored.isLocal && !stored.isAccessibleExternally,
location: locations[process.platform],
encrypted: true,
details: 'Token found in OS keychain only'
};
}
async verifyNetworkCalls() {
// Monitor all network requests
const requests = await this.interceptAllRequests();
const externalCalls = requests.filter(r =>
!r.url.includes('github.com') &&
!r.url.includes('localhost')
);
return {
passed: externalCalls.length === 0,
githubOnly: requests.every(r => r.url.includes('github.com')),
externalCalls,
details: `${requests.length} calls to GitHub API only`
};
}
}6. Build-Time Security Checks
// scripts/security-audit.js
/**
* Pre-build security audit
* Ensures no accidental token leakage code is added
*/
const securityPatterns = [
// Flag any external API calls
{
pattern: /fetch\(['"](?!https:\/\/api\.github\.com)/,
message: 'External API call detected'
},
// Flag analytics/telemetry
{
pattern: /google-analytics|segment|mixpanel|amplitude/i,
message: 'Analytics library detected'
},
// Flag token transmission
{
pattern: /token.*fetch|axios.*token|request.*token/i,
message: 'Possible token transmission detected'
}
];
function auditSecurity() {
// Scan all source files
const violations = scanForViolations(securityPatterns);
if (violations.length > 0) {
console.error('❌ Security audit failed!');
violations.forEach(v => console.error(v));
process.exit(1);
}
console.log('✅ Security audit passed');
}Benefits
- User Trust: Complete transparency about token security
- Verifiable Claims: Users can verify security themselves
- Open Source Confidence: Direct links to source code
- Privacy First: Clear commitment to user privacy
- No Black Box: Everything is auditable
Acceptance Criteria
- Security notice prominently displayed on token input
- Source code links work and point to correct lines
- README includes comprehensive security section
- In-app security audit tool works
- Network monitor shows only GitHub API calls
- Token storage location is displayed
- Build-time security checks implemented
- Privacy report is accurate and updated
- FAQ addresses common concerns
- Documentation is clear and accessible
- Security claims are verifiable by users
🤖 Generated with Claude Code