Skip to content
Merged
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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.289"
VERSION = "0.261.290"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
14 changes: 12 additions & 2 deletions application/v2_ui/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import {
Users,
} from 'lucide-react';
import { useUiStore } from '../../stores/uiStore';
import { safeSameOriginUrl } from '../../lib/adminOperations';
import { useBootstrapStore } from '../../stores/bootstrapStore';
import { useChatStore } from '../../stores/chatStore';
import { classicChatHref } from '../../lib/conversationUrl';
Expand Down Expand Up @@ -84,6 +83,17 @@ const NAV_ITEMS: NavItem[] = [
{ to: '/content-review', label: 'Content review', icon: ShieldCheck, hint: 'Review screened knowledge, including existing holds when new scanning is disabled' },
];

/**
* A rail destination, kept as a path relative to the router's `/v2` base.
*
* It must never become a full URL. React Router treats an absolute URL outside the `/v2`
* basename as an external link and renders a plain anchor, so every rail link would reload
* the classic page at that path instead of routing within V2 (#1698).
*/
function safeNavHref(value: string): string {
return NAV_ITEMS.some((item) => item.to === value) ? value : '/chat';
}

function BrandMark({ collapsed }: { collapsed: boolean }) {
const branding = useBootstrapStore((state) => state.data?.branding);
const theme = useUiStore((state) => state.theme);
Expand Down Expand Up @@ -491,7 +501,7 @@ export function Sidebar({ mobile = false }: { mobile?: boolean }) {
const label = item.to === '/public' ? publicLabels.plural : item.label;
return (
<NavLink
to={safeSameOriginUrl(item.to, window.location.origin) ?? '/'}
to={safeNavHref(item.to)}
onClick={item.to === '/chat' ? startNewChatOnArrival : undefined}
title={collapsed ? label : item.hint}
className={({ isActive }) =>
Expand Down
102 changes: 102 additions & 0 deletions docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# V2 Sidebar Navigation Links Fix (v0.261.290)

## Issue

Every primary link in the V2 left rail opened the classic interface instead of its V2
page. The affected links were Chats, Agents, My Workspace, Group Workspaces, Public

Check warning on line 6 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
Workspaces, Approval requests and Content review. For example, clicking **My Workspace** on
`/v2/chat` did a full page load to `/workspace`, which is the classic workspace page. The
link's highlight in the rail also never matched the open page. Tracked in
[#1698](https://github.com/microsoft/simplechat/issues/1698).

Check warning on line 10 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains external connection or remote asset marker. Recommendation%3A Review whether changed code can send prompts, files, credentials, cookies, settings, logs, or user data to a new sink.

Fixed in version: **0.261.290**, tracked in `application/single_app/config.py`.

## Root cause

`Sidebar.tsx` built each rail link with:

```tsx
to={safeSameOriginUrl(item.to, window.location.origin) ?? '/'}
```

`safeSameOriginUrl` in `lib/adminOperations.ts` returns a full URL, such as
`https://host/workspace`. It was written for the admin endpoint links, which really need a

Check warning on line 23 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains external connection or remote asset marker. Recommendation%3A Review whether changed code can send prompts, files, credentials, cookies, settings, logs, or user data to a new sink.
full address. React Router's `Link` routes an absolute URL inside the application only when
its path is under the router's basename. The V2 basename is `/v2`, and `/workspace` is not
under it. React Router therefore treated each rail link as external. It rendered a plain
`<a href="https://host/workspace">` without its click handler, so the browser loaded the

Check warning on line 27 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains external connection or remote asset marker. Recommendation%3A Review whether changed code can send prompts, files, credentials, cookies, settings, logs, or user data to a new sink.
classic Flask route at that path. `NavLink` also never marked the link as current.

Two parallel changes had each fixed an XSS sink check finding on the same line,
`to={item.to}`. One used a relative allowlist helper, `safeNavHref`, in #1687. The other

Check warning on line 31 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
used `safeSameOriginUrl`. A later merge kept the second change. Both helper names match the
checker's approved `safe*Href` and `safe*Url` patterns, so CI passed. The Playwright test

Check warning on line 33 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
that clicks **Chats** and waits for `/v2/chat` would have failed, but UI tests skip when no

Check warning on line 34 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
deployment is configured.

The same name made this easy to get wrong. `safeSameOriginUrl` in `lib/apiClient.ts` returns
a relative path, while the `lib/adminOperations.ts` function of the same name returns a full
URL.

## Technical details

### Files modified

- `application/v2_ui/src/components/layout/Sidebar.tsx`: brings back `safeNavHref(value)`.
It returns the path unchanged when the path is one of the rail's own `NAV_ITEMS`
destinations, and `/chat` otherwise. The links now use `to={safeNavHref(item.to)}`, so
they stay relative to `/v2` and still pass the XSS sink check. The unused

Check warning on line 48 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
`lib/adminOperations` import is removed.
- `application/single_app/config.py`: version `0.261.290`.

Custom Pages, External Links, Latest Features and **Back to classic UI** are deliberately
plain anchors to server-rendered or third-party pages, and they are unchanged. The
workspace section rail is also unchanged. It already passes its links through

Check warning on line 54 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
`normalizeWorkspaceUrl`, which returns only a pathname.

### Tests

`functional_tests/test_v2_sidebar_primary_nav_stays_in_v2.py` has 5 cases. It checks that:

- every rail destination is a root-relative path with a matching route in `App.tsx`;
- the rail's `NavLink` uses `safeNavHref(item.to)`, does not use the page origin, and does
not import from `lib/adminOperations`;
- `safeNavHref` returns the allowlisted path unchanged and never builds a URL;

Check warning on line 64 in docs/explanation/fixes/V2_SIDEBAR_NAV_LINKS_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
- no `to=` prop in any V2 `.tsx` file is built from `location.origin` or
`safeSameOriginUrl`. The only exception is `normalizeWorkspaceUrl`, and the test confirms
it still returns only `target.pathname`;
- the application version is at least `0.261.290`.

Three of the five cases fail against the code before the fix.

`ui_tests/test_v2_sidebar_primary_nav_links.py` has 7 cases, one per rail link. Starting
from the V2 home page, each case checks three things after clicking the link:

- The link's `href` is `/v2/<route>`.
- The page stays in the single-page application. A marker set on `window` survives, which a
document load would wipe.
- The address lands on the V2 route and the link gets `aria-current="page"`.

All seven fail before the fix. Like the other V2 UI tests, it needs `SIMPLECHAT_UI_BASE_URL`
and an authenticated storage state.

## Impact

V2 users can move around the interface from the rail again without being sent to the
classic interface. The open page is highlighted in the rail again.

## Validation

- Before: each rail link's `href` was a full URL outside `/v2`, such as
`http://127.0.0.1:5174/workspace`. Clicking it reloaded the document at the classic path,
and no link was marked current.
- After: each `href` is `/v2/<route>`. Clicking a link routes inside the SPA without a
reload, from both the home page and the chat page, and highlights the link. Approval
requests then settles on its default category, `/v2/approvals/all`.
- To check this locally, the Vite dev server ran against a stub `/api/v2/bootstrap`. The UI
test passed 7 of 7 with the fix and failed 7 of 7 with the previous `Sidebar.tsx`.

## Related

- [V2 Terms of Use and Approval Requests](../features/V2_TERMS_AND_APPROVALS.md)
- [Workspace Notification Links Fix](WORKSPACE_NOTIFICATION_LINKS_FIX.md)
10 changes: 10 additions & 0 deletions docs/explanation/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes).

### **(v0.261.290)**

#### Bug Fixes

* **V2 Sidebar Links Opened the Classic Interface**
* Every primary link in the V2 left rail opened the classic page at the same path instead of the V2 page. This covered Chats, Agents, My Workspace, Group Workspaces, Public Workspaces, Approval requests and Content review. The rail also never highlighted the open page.
* Rail links now stay relative to `/v2`, so each one opens its V2 page without a reload and is highlighted.
* A functional test now flags any router link built from the page origin in the V2 source, and a UI test clicks each rail link.
* (Ref: `Sidebar.tsx` `safeNavHref`, [#1698](https://github.com/microsoft/simplechat/issues/1698), [V2 Sidebar Navigation Links Fix](fixes/V2_SIDEBAR_NAV_LINKS_FIX.md))

### **(v0.261.288)**

#### New Features
Expand Down
Loading
Loading