Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Imported browser cookies reach every agent browser, with no audience scope

Decided by: Bolin Chen (maintainer, @bolichen97)
Date: 2026-10-23

## Decision

Cookies the owner imports from the dashboard are delivered to every agent browser on the gateway, including browsers that serve non-owner members and restricted sessions; this exposure is accepted.

## Why

- The gateway finds agent browsers by their control socket, and a browser is not tied to one audience: agent processes are pooled across sessions, so one browser can serve the owner and a member in turn.
- Scoping delivery to owner sessions needs a session to process to browser mapping that the launch path does not have. That is a separate change with its own design.
- Non-owner members (Slack/Discord allow-list) and restricted (incognito/temporary) sessions can therefore browse as the owner on the imported sites. Clear cookies stops further use; it does not undo a page already read.
- The acceptance covers this exposure only. It does not clear any other review finding.

## Evidence

- https://github.com/kirodotdev/KiroCrew/pull/23332 -- the pull request that adds cookie import and this entry.
- https://github.com/kirodotdev/KiroCrew/pull/23332#issuecomment-6377799382 -- the maintainer restates the ruling on the pull request.
- `docs/system-specs/modules/browser.md` -- residual (iii) in the imported cookies section.
1 change: 1 addition & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,4 @@ Links to the pull requests, issues and commits that carry the decision.
| [2026-10-22-heartbeat-is-a-schedules-tab-row-not-a-goals-check-in.md](2026-10-22-heartbeat-is-a-schedules-tab-row-not-a-goals-check-in.md) | The crewmate's Heartbeat switch is the first row of the Profile card's Schedules tab, not a check-in on the Goals tab. |
| [2026-10-22-a-new-goal-starts-in-the-crewmate-chat.md](2026-10-22-a-new-goal-starts-in-the-crewmate-chat.md) | A new goal starts from the Goals tab's Create a goal row as a conversation in the crewmate's chat, with no text editor for it on the tab; the "Add something to track" row is gone. |
| [2026-10-23-scrollbars-are-never-always-visible.md](2026-10-23-scrollbars-are-never-always-visible.md) | No scrollbar in the dashboard is visible at rest: a scroll box either hides it entirely or shows a thin thumb only while it is being scrolled, in a column it already reserves so the reveal moves no content. |
| [2026-10-23-imported-browser-cookies-have-no-audience-scope.md](2026-10-23-imported-browser-cookies-have-no-audience-scope.md) | Cookies the owner imports reach every agent browser on the gateway, including browsers that serve non-owner members and restricted sessions; that exposure is accepted. |
2 changes: 1 addition & 1 deletion docs/feature-map/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ this area is reached from inside it unless stated otherwise.
| Slide preview for decks | Shows a `.pptx` / `.ppt` opened in the side panel as rendered slides (pager + thumbstrip) when LibreOffice is on the gateway host; without it, a row naming the install command (with an agent hand-off) over the `.pptx` text outline, or over the download card for `.ppt`, which has no text to extract; a deck whose visible text the credential screen would redact is not rendered and shows the redacted outline under a row saying so; on Windows (no descriptor-relative file operations to pin the render's staging) slides are not rendered and the outline is shown with the reason | Side panel → Files tab → click a deck; any deck chip in chat | `components/FileRenderers.tsx` (`OfficeSlidesRenderer`) | `handlers/office_slides.py` | `GET /api/file-office-slides`, `GET /api/file-office-slide`, `GET /api/file-office-preview` |
| Terminal panel | Two shells on the gateway host: an app-wide docked PTY, and a per-chat terminal whose tab lives in that chat's panel state (opens on the chat's working dir; switches with the session). A shell that exits on its own closes its tab in the window attached to it, and the docked panel with its last tab (another window still holding that tab is not told, and its next dial opens a fresh shell); a known non-zero exit leaves a Terminal note in the bell feed, Windows included. Both tab strips, the docked terminal's and the chat right panel's, carry a close menu — **Close**, **Close other tabs**, **Close tabs to the right** and **Close all tabs**, which the terminal strip names **Close other terminals**, **Close terminals to the right** and **Close all terminals** and the workspace strip follows with a note that the Changes, Artifacts and Files views stay open; closing a terminal tab stops its shell, a batch that stops more than one shell asks first, counting and naming the shells, and one that would discard unsaved file edits names the files before closing. With `dashboard.terminal.reuse_current` on, Run in terminal always COPIES the command to the clipboard for a manual paste and never runs it — so the confirm dialog, which promises a copy, cannot mislead. When a settled terminal tab exists it is also revealed (the docked panel opens and that tab is made active, and a popped-out terminal window is raised) so the shell to paste into is in front; when none is (no tab, or a restored tab still unconfirmed during hydration) the raw command is copied verbatim with no tab minted and nothing executed | Header terminal toggle (docked); chat right panel → **+** menu → **Terminal**, or Files tab header → **Project actions** → **Open terminal in the project directory** — both go through one `openProjectTerminal` callback, so they cannot disagree about the cwd (per-chat); workspace chat composer (main chat or shared pane) → `! <command>` → **Review terminal command** → review → **Run** (fresh docked terminal); right-click a tab, or on touch hold it and lift without moving, for the close menu | `components/BottomTerminalPanel.tsx`, `components/TabCloseMenu.tsx`, `pages/chat/SidePanel.tsx`, `pages/chat/SidePanelTabChip.tsx` (the right panel's tab chips, their close menu and a terminal tab's live title), `pages/chat/SidePanelTabBodies.tsx` (the per-chat terminal's tab body), `pages/chat/FilesHomePanel.tsx`, `pages/ChatPage.tsx` (run-in-terminal reuse), `components/ChatPane.tsx`, `components/ChatInput.tsx`, `components/RunInTerminalConfirm.tsx`, `hooks/useTerminalCommand.ts`, `hooks/useBottomTerminal.ts` (`reuseCurrentTab`), `utils/terminalExitClose.ts` | `handlers/terminal.py` | `POST /api/terminal/sessions`, `GET /api/ws/terminal/{session_id}` |
| External terminal app | Desktop app only: opens the user's own terminal emulator (one of a fixed allowlist: on Linux Alacritty, Ghostty, GNOME Terminal, kitty, Konsole, WezTerm and Xfce Terminal; on macOS Alacritty, kitty and WezTerm; none on Windows) in the session's project directory, as a separate action beside the integrated terminal, which is unchanged. Off until an app is picked (`dashboard.terminal.external_app`, unset by default). The gateway is the gate: it approves only the dashboard owner, on a direct-local request, with the integrated terminal enabled and an app picked, and answers a single-use grant; the shell's main process redeems the grant over the machine-only internal-secret route and starts the app's own executable with a pinned argv and the realpath'd directory as its working directory, never through an OS opener. The row names the picked app ("Open in kitty (separate window)") and is hidden in a browser tab, for a remote gateway, and while the picked app is not installed; a refused or failed launch falls back to the integrated terminal with a note naming the app and why (turned off in Settings or no longer installed, not launchable from this window, did not start); a failed check of the setting or of the installed apps is reported rather than read as "none" | Files tab header → **Project actions** → **Open in <app> (separate window)** (shown once an app is picked); Settings → Display → Terminal → **External terminal app** | `pages/chat/FilesHomePanel.tsx`, `pages/chat/SidePanel.tsx` (the fallback note, which outlives the Files tab going out of view), `pages/settings/DisplayPanel.tsx`, `lib/externalTerminal.ts`, `electron/external-terminal.js` (allowlist and launch), `electron/ipc-registrar.js` (`external-terminal:*`), `electron/preload.js` (`externalTerminalAPI`) | `handlers/external_terminal.py` | `POST /api/terminal/external`, `POST /api/terminal/external-grant` (machine-only) |
| Browser panel | Live in-panel browser the agent drives; the non-native address bar opens a real website in it; the framed CLI view is served through the dashboard-origin relay (`/browser-view/`), so it works remotely over the same forwarded port as the dashboard; **Annotate** (desktop) picks page elements, takes a note per element in the panel, and adds them to chat with the agent's `eN` refs (`browser:annotate` IPC, `electron/browser-annotate.js`) | Right panel → **Browser** | `components/WebPreviewPanel.tsx` | `handlers/messaging.py`, `handlers/browser_view_relay.py` | `GET /api/browser/view`, `POST /api/browser/view/start`, `POST /api/browser/open`, `GET /browser-view/{tail}`, `POST /api/browser/command`, `POST /api/browser/command-result` |
| Browser panel | Live in-panel browser the agent drives; the non-native address bar opens a real website in it; the framed CLI view is served through the dashboard-origin relay (`/browser-view/`), so it works remotely over the same forwarded port as the dashboard; **Annotate** (desktop) picks page elements, takes a note per element in the panel, and adds them to chat with the agent's `eN` refs (`browser:annotate` IPC, `electron/browser-annotate.js`); **Import cookies** loads a cookie export (Playwright `storageState`, Cookie-Editor JSON, or Netscape `cookies.txt`) from the browser the owner is logged into, so the agent's browser sessions on a remote gateway start signed in — a read-only chip shows the count, domains and earliest expiry (never a value), and **Clear** removes them; owner-only, hidden in a restricted session | Right panel → **Browser**; toolbar **More actions** → **Import cookies** / **Clear cookies** | `components/WebPreviewPanel.tsx`, `components/WebPreviewCookies.tsx`, `hooks/useBrowserCookies.ts` | `handlers/messaging.py`, `handlers/browser_view_relay.py`, `messaging_api/browser_routes.py`, `browser_cli/cookies.py` | `GET /api/browser/view`, `POST /api/browser/view/start`, `POST /api/browser/open`, `GET /browser-view/{tail}`, `POST /api/browser/command`, `POST /api/browser/command-result`, `GET,POST,DELETE /api/browser/cookies` |
| App-contributed panel tabs | Side-panel tabs an installed app declares in `contributes.panelTabs`; each mounts the app's own ESM bundle as the tab body through the in-process app host. Only enabled apps contribute, and no app means no rows | Chat right panel → **+** menu → the app's row; on an empty panel, its launcher card. Also under `/members` — the Crew Members side panel is the same `SidePanel`, so an app's row appears in its **+** menu once the member's thread is confirmed | `pages/chat/SidePanel.tsx` (the **+** menu rows and launcher cards), `pages/chat/SidePanelTabBodies.tsx` (the tab body and the failed app-list notice), `pages/chat/SidePanelTabChip.tsx` (the strip glyph), `hooks/panelTabRegistry.ts`, `components/AppHost.tsx` | `src/kiro_crew/apps/routes_listing.py`, `src/kiro_crew/apps/routes_static.py` | `GET /api/apps`, `GET /apps/{name}/ui/{path}` |
| Route-history Back / Forward | Browser-style Back and Forward over the dashboard's own route history (desktop layout; on desktop a session switch pushes an entry, so it retraces sessions as well as pages). Each arrow disables when there is nowhere to go (the Forward watermark persists per tab in `sessionStorage` across a reload; it is discarded on a fresh navigation, and a cross-document Back/Forward return (or a BFCache restore) collapses the watermark to the current entry — persisted, so a reload cannot resurrect it — until an in-app navigation re-establishes the frontier — so Forward never leaves the dashboard) and every step asks the page's draft guards before an unarmed pop can unmount typed work (the channel holds a SET of guards, not one slot, so a page carrying two drafts -- the Mates page's New mate dialog and its side panel's Schedules create form -- has both protected; asking stops at the first refusal). Right-click or long-press an arrow for a menu of up to 10 earlier (Back) or later (Forward) destinations, named by session or page and kept per tab in `sessionStorage`; picking one jumps straight there through the same draft ask | Topbar left cluster → **Back** / **Forward** arrows (right-click or long-press for the history menu); `⌘←` / `⌘→` on macOS, `Ctrl+←` / `Ctrl+→` elsewhere (registry ids `history-back` / `history-forward`; left to text fields and terminals, claimed as a no-op on narrow viewports where the arrows are hidden); in the macOS desktop app, a mouse's Back / Forward buttons sent as swipes (Logitech's Options software and similar utilities) and the three-finger page swipe, which the shell relays as one step (to the page's own history when the cursor is over a native browser panel; inert on narrow viewports too) | `components/NavHistoryArrows.tsx`, `lib/routeHistoryPosition.ts`, `lib/routeHistoryLabels.ts`, `components/NavigationLeaveGuard.tsx` (`useGuardedHistoryStep`), `hooks/useKeyboardShortcuts.ts`, `lib/shortcutRegistry.ts`, `shell/platform/nativeHistoryStep.ts`, `electron/swipe-history.js` | client-only (no handler) | none |
| Notifications | Bell feed of agent-pushed notifications; a note an agent publishes links back to the session that sent it (the detail panel's **Go to chat**, or **Resume chat** once that tab is closed); a live `notification` frame also shows a Notification Center-style banner under the top bar that tucks into the bell (approvals keep their own row) (see `docs/system-specs/modules/app-notifications.md` § In-app banner) | Topbar bell → `/notifications` | `pages/NotificationsPage.tsx`, `components/notifications/NotificationBanner.tsx`, `components/notifications/NotificationDetailPanel.tsx` | `handlers/messaging.py`, `handlers/notifications_push.py` | `GET /api/notifications`, `POST /api/notifications/ack`, `POST /api/notifications/push`, `GET /api/notifications/push/vapid-public-key`, `POST /api/notifications/push/subscribe`, `POST /api/notifications/push/unsubscribe` |
Expand Down
Loading
Loading