Uh oh!
There was an error while loading. Please reload this page.
') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })();
There was an error while loading. Please reload this page.
Problem
Provider API keys are stored via Electron
safeStorage(apps/desktop/src/main/credential-store.ts), an Electron-only API. The rest of@maka/runtimeis pure-Node and Electron-free.This blocks first-class headless mode (see #31): a pure-Node CLI / eval harness / third-party caller running the same runtime outside Electron cannot decrypt the existing credentials.
connectionStore(model/connection config) is already pure-Node in@maka/storageand reusable; only the secret is trapped behind safeStorage.This is a global, security-sensitive change touching desktop OAuth, connection tests, and bots. It needs owner + collaborator sign-off — not to be implemented unilaterally. This RFC is to align before any code.
Threat model
Per
SECURITY.md, the OS user account is the only real enforcement boundary against an adversarial LLM/tool. A tool running as the user can already read files, run commands, hit the network, and read env. Against that threat, at-rest encryption buys little. safeStorage meaningfully protects only secondary threats: offline disk theft, other OS users, backup/cloud-sync leakage, and (macOS) other-app boundary. It is also not cross-platform consistent (Electron docs: Windows DPAPI doesn't stop same-user apps; Linux falls back tobasic_textwithout a secret store).Prior art (verified)
~/.codex/auth.json(file/keyring/auto)~/.local/share/opencode/auth.json~/.claude/.credentials.json.env/ yamlThe dominant convention for CLI agents is a local 0600 JSON file; keychain-led tools (Claude Code, gh) keep a same-shape 0600 file as the headless fallback.
Proposed design
A single pure-Node
CredentialStoreinterface in@maka/storage, used by desktop + CLI + harness + third-party. Resolution precedence:0700/ file0600, plaintext (no base64 theater), atomic write,schema version. Windows: ACL or best-effort + clear warning.credential_provider: returns structured JSON, supportsexpires_at, has a timeout, must not write secrets to stderr, cached by expiry. This is where keychain / 1Password / Vault live — first-class, not default.CredentialStore(not forced to read the global user file).Rejected alternatives (with reasons)
security/secret-tool/cmdkey) — cross-platform semantics inconsistent, can block on user input, Windows has no clean read-to-stdout. OK as an optional helper example, not the core store.@napi-rs/keyring) — reintroduces native binary / libsecret/DBus / ABI / headless-packaging deps, defeating pure-Node. Optional, never the sole store.Security trade-off (stated honestly)
We trade some at-rest encryption for default composability/debuggability/headless. The main new exposure: a plaintext key getting carried off by iCloud / Time Machine / Dropbox / dotfile sync. Mitigations: keep the store outside common sync paths, document it, unified redaction across logs / crash reports / debug dumps / migration backups.
Open questions
credential_provider/keychain encryption on by default where available?provider + account/profile + base_url/org/tenant.0600equivalent).Driver: #31 · Prior art: Codex CLI, OpenCode, Claude Code, aider · Refs: git credential helpers, aws credential_process, Electron safeStorage