mirror of
https://github.com/block/goose.git
synced 2026-07-09 16:09:22 +00:00
fix(desktop): renderer permissions, Origin rewrite scope, route state fallbacks
- The permission request handler granted every renderer permission; its media check called callback(true) in both branches. Now only 'media' (dictation microphone) and 'clipboard-sanitized-write' (copy buttons) are granted; everything else is denied and logged. - onBeforeSendHeaders rewrote the Origin header to http://localhost:5173 on every outgoing request, including third-party hosts. goosed's origin policy only concerns loopback targets, so the rewrite is now scoped to them. - Route wrappers fell back to window.history.state for view options, but react-router stores user state under history.state.usr, so the fallback could only ever produce the router's wrapper object — and SettingsRoute then mutated it. Read location.state only, and build the settings view options instead of mutating. - ProgressiveMessageList marked the last *rendered* message as streaming, which targets the wrong message while progressive batches are still loading. Compare against the full message list instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b9b9b819fe
commit
e0cf1d9801
3 changed files with 30 additions and 27 deletions
|
|
@ -91,8 +91,7 @@ const PairRouteWrapper = ({
|
|||
}) => {
|
||||
const { extensionsList } = useConfig();
|
||||
const location = useLocation();
|
||||
const routeState =
|
||||
(location.state as PairRouteState) || (window.history.state as PairRouteState) || {};
|
||||
const routeState = (location.state as PairRouteState) ?? {};
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const isCreatingSessionRef = useRef(false);
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -185,15 +184,11 @@ const SettingsRoute = () => {
|
|||
const [searchParams] = useSearchParams();
|
||||
const setView = useNavigation();
|
||||
|
||||
// Get viewOptions from location.state, history.state, or URL search params
|
||||
const viewOptions =
|
||||
(location.state as SettingsViewOptions) || (window.history.state as SettingsViewOptions) || {};
|
||||
|
||||
// If section is provided via URL search params, add it to viewOptions
|
||||
const sectionFromUrl = searchParams.get('section');
|
||||
if (sectionFromUrl) {
|
||||
viewOptions.section = sectionFromUrl;
|
||||
}
|
||||
const viewOptions: SettingsViewOptions = {
|
||||
...((location.state as SettingsViewOptions) ?? {}),
|
||||
...(sectionFromUrl ? { section: sectionFromUrl } : {}),
|
||||
};
|
||||
|
||||
return <SettingsView onClose={() => navigate('/')} setView={setView} viewOptions={viewOptions} />;
|
||||
};
|
||||
|
|
@ -272,11 +267,7 @@ const ExtensionsRoute = () => {
|
|||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
// Get viewOptions from location.state or history.state (for deep link extensions)
|
||||
const viewOptions =
|
||||
(location.state as ExtensionsViewOptions) ||
|
||||
(window.history.state as ExtensionsViewOptions) ||
|
||||
{};
|
||||
const viewOptions = (location.state as ExtensionsViewOptions) ?? {};
|
||||
|
||||
return (
|
||||
<ExtensionsView
|
||||
|
|
|
|||
|
|
@ -299,8 +299,7 @@ export default function ProgressiveMessageList({
|
|||
toolCallNotifications={toolCallNotifications}
|
||||
isStreaming={
|
||||
isStreamingMessage &&
|
||||
!isUser &&
|
||||
index === messagesToRender.length - 1 &&
|
||||
index === messages.length - 1 &&
|
||||
message.role === 'assistant'
|
||||
}
|
||||
submitElicitationResponse={submitElicitationResponse}
|
||||
|
|
|
|||
|
|
@ -306,6 +306,15 @@ interface BackendCertificateTrustRegistration {
|
|||
|
||||
const trustedBackendCertificates = new Set<BackendCertificateTrust>();
|
||||
|
||||
function isLoopbackUrl(url: string): boolean {
|
||||
try {
|
||||
const { hostname } = new URL(url);
|
||||
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHostname(hostname: string): string {
|
||||
return hostname.toLowerCase();
|
||||
}
|
||||
|
|
@ -2415,16 +2424,15 @@ async function appMain() {
|
|||
|
||||
registerUpdateIpcHandlers();
|
||||
|
||||
// Handle microphone permission requests
|
||||
// 'media' covers the dictation microphone; 'clipboard-sanitized-write'
|
||||
// covers navigator.clipboard copy buttons. Everything else is denied.
|
||||
const allowedRendererPermissions = new Set(['media', 'clipboard-sanitized-write']);
|
||||
session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback) => {
|
||||
console.log('Permission requested:', permission);
|
||||
// Allow microphone and media access
|
||||
if (permission === 'media') {
|
||||
callback(true);
|
||||
} else {
|
||||
// Default behavior for other permissions
|
||||
callback(true);
|
||||
const allowed = allowedRendererPermissions.has(permission);
|
||||
if (!allowed) {
|
||||
log.warn(`Denied renderer permission request: ${permission}`);
|
||||
}
|
||||
callback(allowed);
|
||||
});
|
||||
|
||||
// Add CSP headers to all sessions, recomputed on every response so external
|
||||
|
|
@ -2451,9 +2459,14 @@ async function appMain() {
|
|||
// Register global shortcuts based on settings
|
||||
registerGlobalShortcuts();
|
||||
|
||||
// goosed validates the Origin header of incoming requests. Rewrite it for
|
||||
// loopback (goosed) targets only; other hosts must not receive a fabricated
|
||||
// localhost origin.
|
||||
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
details.requestHeaders['Origin'] = 'http://localhost:5173';
|
||||
callback({ cancel: false, requestHeaders: details.requestHeaders });
|
||||
if (isLoopbackUrl(details.url)) {
|
||||
details.requestHeaders['Origin'] = 'http://localhost:5173';
|
||||
}
|
||||
callback({ requestHeaders: details.requestHeaders });
|
||||
});
|
||||
|
||||
if (settings.showMenuBarIcon) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue