Merge remote-tracking branch 'origin/main' into garrytan/gbrain-code-smell-audit

# Conflicts:
#	CHANGELOG.md
#	browse/test/dual-listener.test.ts
#	browse/test/fixtures/security-bench-haiku-responses.json
#	browse/test/sidebar-tabs.test.ts
#	browse/test/sidebar-ux.test.ts
#	browse/test/terminal-agent.test.ts
#	claude/SKILL.md.tmpl
#	scripts/gen-skill-docs.ts
#	scripts/proactive-suggestions.json
#	spec/SKILL.md
#	test/gen-skill-docs.test.ts
#	test/host-config.test.ts
This commit is contained in:
Garry Tan
2026-08-15 07:31:02 -07:00
246 changed files with 8802 additions and 1257 deletions
+23 -8
View File
@@ -5,8 +5,14 @@
* Fetches /refs on snapshot completion, relays to content script.
* Proxies commands from sidebar → browse server.
* Updates badge: amber (connected), gray (disconnected).
* Denies token/port reads to content-script and foreign senders.
*/
// Sender authorization for privileged message types (the token/port surface).
// Classic (non-module) service worker: importScripts puts gstackSenderAuth on
// the worker global. The same file is require()-able from bun tests.
importScripts('sender-auth.js');
const DEFAULT_PORT = 34567; // Well-known port used by `$B connect`
let serverPort = null;
let authToken = null;
@@ -308,6 +314,19 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
return;
}
// Privileged types — anything that returns or spends the auth token or
// server port, or dumps the full tab list — are for this extension's own
// pages only (sidepanel/popup). Content scripts run inside web pages and
// can be influenced by page content; foreign extensions are foreign. Both
// get { error: 'unauthorized' } and nothing else — never the token, never
// the port. Policy + type list live in sender-auth.js.
const denial = gstackSenderAuth.denialFor(msg.type, sender, chrome.runtime.id);
if (denial) {
console.warn('[gstack] Rejected privileged message from unauthorized sender:', msg.type, sender.url || '(no sender url)');
sendResponse(denial);
return true;
}
if (msg.type === 'getPort') {
sendResponse({ port: serverPort, connected: isConnected, token: authToken });
return true;
@@ -332,15 +351,11 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
}
// Token delivered via targeted sendResponse, not broadcast — limits exposure.
// Only respond to extension pages (sidepanel/popup) — content scripts have
// sender.tab set, so reject those to prevent token access from injected contexts.
// Only this extension's own pages reach here: the sender-auth gate above
// denies content scripts (sender.tab set) and foreign senders before any
// privileged handler runs.
if (msg.type === 'getToken') {
if (sender.tab) {
console.warn('[gstack] Rejected getToken from content script context');
sendResponse({ token: null });
} else {
sendResponse({ token: authToken });
}
sendResponse({ token: authToken });
return true;
}