์ปจํ
์คํธ ๋ธ๋ก
| Key |
Value |
| Category |
frontend |
| Checklist |
ISS-UI-CRIT-02 โ Client-side state leak between users on shared devices |
| Priority |
P1 ๐ |
| Scan Date |
2026-04-16 |
| Flagged By |
@core-critique |
์์ฝ
- WHAT:
useAuth().signOut = () => signOut({ callbackUrl: \"/login\" }) โ NextAuth ์ธ์
๋ง ๋ฌดํจํ, lg:connections/lg:chat:apiKey localStorage์ lg_apiUrl/lg_assistantId/lg_apiKey ์ฟ ํค๋ ๊ทธ๋๋ก ์ ์ง
- WHY: ๊ณต์ฉ PC/๋ธ๋ผ์ฐ์ ์์ ์ฌ์ฉ์ A ๋ก๊ทธ์์ ํ ์ฌ์ฉ์ B๊ฐ ๋ก๊ทธ์ธํ๋ฉด A์ ์ฐ๊ฒฐ ์ค์ /API ํค๊ฐ B์๊ฒ ๋
ธ์ถ. LangSmith API ํค๋ ๊ฒฐ์ ๊ณ์ ๊ณผ ์ฐ๊ฒฐ๋์ด ์์ด ์ฌ๊ฐ
- WHERE:
frontend/src/features/auth/hooks/useAuth.ts:31, frontend/src/shared/components/settings/SettingsDialog.tsx:58-59 (์ ์ผํ cleanup์ ์๋ "Reset" ๋ฒํผ์๋ง ์กด์ฌ)
- SEVERITY: HIGH โ ๊ณต์ฉ ๋๋ฐ์ด์ค ์๋๋ฆฌ์ค์์ API ํค ์ ์ถ
Evidence
| # |
File |
Line |
Finding |
Flagged By |
Confidence |
| 1 |
frontend/src/features/auth/hooks/useAuth.ts |
31 |
signOut: () => signOut({ callbackUrl: \"/login\" }) โ ์ค์ง NextAuth ์ธ์
๋ง ์ข
๋ฃ, ํด๋ผ์ด์ธํธ ์ ์ฅ์ ๋ฏธ์ ๋ฆฌ |
@core-critique |
High |
| 2 |
frontend/src/shared/components/settings/SettingsDialog.tsx |
58-59 |
localStorage.removeItem(\"lg:connections\"), localStorage.removeItem(\"lg:chat:apiKey\") โ "Reset" ๋ฒํผ ํด๋ฆญ ์์๋ง ์คํ |
@core-critique |
High |
| 3 |
frontend/src/app/actions.ts (์ถ์ ) clearConnectionAction |
โ |
์๋ฒ ์ธก ์ฟ ํค ์ ๋ฆฌ ํจ์ ์กด์ฌ ๊ฐ๋ฅ์ฑ โ signOut ๊ฒฝ๋ก์์ ํธ์ถ๋์ง ์์ |
@core-critique |
Medium |
์ํฅ ๋ถ์
์ํฅ ๋ฒ์
- ๊ณต์ฉ PC/ํค์ค์คํฌ/๋์๊ด/ํ๊ต ๊ธฐ๊ธฐ ์ฌ์ฉ์
- ํ ๋ช
์ด LangSmith API ํค๋ก ๋ก๊ทธ์ธํ ๊ธฐ๊ธฐ๋ฅผ ๋ค๋ฅธ ์ฌ๋์ด ์ด์ด์ ์ฌ์ฉํ ๋
- ์ธ์
์์ฒด๋ ์ข
๋ฃ๋์ง๋ง ์ฐ๊ฒฐ ์ ๋ณด(apiUrl/apiKey/assistantId)๋ ๋ค์ ์ฌ์ฉ์์๊ฒ ์๋ ๋ฐ์
์ฅ์ ์๋๋ฆฌ์ค
- ์ฌ์ฉ์ A๊ฐ ๊ณต์ฉ ๊ธฐ๊ธฐ์์ NextAuth ๋ก๊ทธ์ธ, admin ์ฝ์์์ ๊ฐ์ธ LangSmith ํค๋ก ์ฐ๊ฒฐ ์ค์
- A๊ฐ ๋ก๊ทธ์์ (
signOut) โ NextAuth ์ธ์
์ฟ ํค ์ญ์ , BUT:
localStorage[\"lg:chat:apiKey\"]์ A์ API ํค ์์กด
lg_apiKey ์ฟ ํค(httpOnly)์ A์ API ํค ์์กด
- ์ฌ์ฉ์ B๊ฐ ๊ฐ์ ๊ธฐ๊ธฐ์ ๋ก๊ทธ์ธ
- B์ ๋ธ๋ผ์ฐ์ ๊ฐ A์ API ํค๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ์ฌ LangGraph ํธ์ถ โ A ๊ณ์ ์ ๊ณผ๊ธ + B๊ฐ A์ trace ๋ฐ์ดํฐ ์ ๊ทผ ๊ฐ๋ฅ์ฑ
๊ธด๊ธ๋
- README๊ฐ "User status: active/pending/suspended"์ ๊ฐ์กฐํ์ง๋ง ๋ก๊ทธ์์ ์ ์ธ์
์ ๋ฆฌ๋ง์ผ๋ก๋ ๋ถ์ถฉ๋ถ
- ๊ธฐ์
/๊ธฐ๊ด ๋ฐฐํฌ(๊ณต์ฉ ๊ธฐ๊ธฐ) ์๋๋ฆฌ์ค์์ ์ฆ์ ๋ฌธ์ ๋
ธ์ถ
์ ์ ํด๊ฒฐ ๋ฐฉ์
์ ๊ทผ ๋ฐฉ๋ฒ
signOut ๊ฒฝ๋ก์ ํด๋ผ์ด์ธํธ/์๋ฒ ์์ธก ์ ๋ฆฌ ๋ก์ง ์ถ๊ฐ:
// frontend/src/features/auth/hooks/useAuth.ts
export function useAuth() {
// ...
const enhancedSignOut = async () => {
// ํด๋ผ์ด์ธํธ ์ ์ฅ์ ์ ๋ฆฌ
if (typeof window !== \"undefined\") {
localStorage.removeItem(\"lg:connections\");
localStorage.removeItem(\"lg:chat:apiKey\");
}
// ์๋ฒ ์ธก ์ฟ ํค ์ ๋ฆฌ (server action)
try {
await clearConnectionAction();
} catch { /* noop */ }
// NextAuth ์ธ์
์ข
๋ฃ
await signOut({ callbackUrl: \"/login\" });
};
return { ..., signOut: enhancedSignOut };
}
clearConnectionAction์ด ์๋ค๋ฉด frontend/src/app/actions.ts์ ์ถ๊ฐ: lg_apiUrl, lg_assistantId, lg_apiKey ์ฟ ํค๋ฅผ maxAge:0๋ก ๋ฎ์ด์ฐ๊ธฐ.
๋์
- Next.js middleware์์ ์ ๋ฆฌ: ๊ฐ๋ฅํ์ง๋ง NextAuth์ signOut ํ ๋ฆฌ๋๋ ์
ํ๋ก์ฐ์ ์ด๊ธ๋จ
- ์ฟ ํค๋ฅผ session ์ฟ ํค๋ก๋ง ์ค์ : ๋ธ๋ผ์ฐ์ ์ข
๋ฃ ์ ์ญ์ ๋์ง๋ง ๋ก๊ทธ์์ ์ฆ์ ์ ๋ฆฌ๋ ์๋จ
- ์ฌ์ฉ์์๊ฒ "Reset" ๋ฒํผ ์ฌ์ฉ ๊ถ๊ณ : UX ์ ๋ขฐ๋ ๋ฎ์, ์๊ธฐ ์ฌ์
์์ฉ ๊ธฐ์ค
์ฐธ์กฐ
์ฌํ ๋ฐฉ๋ฒ
์ฌ์ ์กฐ๊ฑด
- credentials ๋๋ OAuth ๋ชจ๋๋ก ์คํ
- 2๋ช
์ ํ
์คํธ ์ฌ์ฉ์ ๊ณ์
๋จ๊ณ
- ์ฌ์ฉ์ A๋ก ๋ก๊ทธ์ธ, admin ์ฝ์์์ apiKey ์ค์ ํ ์ ์ฅ
/api/auth/signout ๋๋ UI signOut ๋ฒํผ์ผ๋ก ๋ก๊ทธ์์
- DevTools > Application > Storage ํ์ธ: localStorage, Cookies
- ์ฌ์ฉ์ B๋ก ๋ก๊ทธ์ธ
- B๊ฐ chat ํ์ด์ง์ ์ ๊ทผํ๋ฉด ์ด๋ค ์ฐ๊ฒฐ ์ค์ ์ด ์ ์ฉ๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ
๋ก๊ทธ์์ ํ ๋ชจ๋ LangGraph ์ฐ๊ฒฐ ๊ด๋ จ ์ ์ฅ์ ๋น์ด์์. B๋ ์ ์ฐ๊ฒฐ ์ค์ ํ์.
์ค์ ๊ฒฐ๊ณผ
A์ apiKey/apiUrl๊ฐ localStorage + ์ฟ ํค์ ์์กด. B๊ฐ A์ ์ฐ๊ฒฐ๋ก ์๋ ์ฑํ
์์.
๊ด๋ จ ์ฝ๋ ์ปจํ
์คํธ
| File |
Role |
Relevance |
frontend/src/features/auth/hooks/useAuth.ts |
signOut ํธ์ถ ์ง์ |
์์ ๋์ |
frontend/src/shared/components/settings/SettingsDialog.tsx |
"Reset" ๋ฒํผ์ ๊ธฐ์กด cleanup ๋ก์ง ์กด์ฌ |
์ฐธ๊ณ ๊ตฌํ |
frontend/src/app/actions.ts |
์๋ฒ ์ธก ์ฟ ํค ๊ด๋ฆฌ |
์ ๊ท clearConnectionAction ์ถ๊ฐ ๋์ |
frontend/src/features/auth/components/UserMenu.tsx |
signOut UI ํธ๋ฆฌ๊ฑฐ |
์ฐ๊ณ ํ์ธ |
Detected by oh-my-braincrew `omb:issue` scan
Category: frontend | Scan date: 2026-04-16
`omb-issue-scan category=frontend checklist=ISS-UI-CRIT-02`
์ปจํ ์คํธ ๋ธ๋ก
ISS-UI-CRIT-02โ Client-side state leak between users on shared devices์์ฝ
useAuth().signOut = () => signOut({ callbackUrl: \"/login\" })โ NextAuth ์ธ์ ๋ง ๋ฌดํจํ,lg:connections/lg:chat:apiKeylocalStorage์lg_apiUrl/lg_assistantId/lg_apiKey์ฟ ํค๋ ๊ทธ๋๋ก ์ ์งfrontend/src/features/auth/hooks/useAuth.ts:31,frontend/src/shared/components/settings/SettingsDialog.tsx:58-59(์ ์ผํ cleanup์ ์๋ "Reset" ๋ฒํผ์๋ง ์กด์ฌ)Evidence
frontend/src/features/auth/hooks/useAuth.tssignOut: () => signOut({ callbackUrl: \"/login\" })โ ์ค์ง NextAuth ์ธ์ ๋ง ์ข ๋ฃ, ํด๋ผ์ด์ธํธ ์ ์ฅ์ ๋ฏธ์ ๋ฆฌfrontend/src/shared/components/settings/SettingsDialog.tsxlocalStorage.removeItem(\"lg:connections\"),localStorage.removeItem(\"lg:chat:apiKey\")โ "Reset" ๋ฒํผ ํด๋ฆญ ์์๋ง ์คํfrontend/src/app/actions.ts(์ถ์ )clearConnectionActionsignOut๊ฒฝ๋ก์์ ํธ์ถ๋์ง ์์์ํฅ ๋ถ์
์ํฅ ๋ฒ์
์ฅ์ ์๋๋ฆฌ์ค
signOut) โ NextAuth ์ธ์ ์ฟ ํค ์ญ์ , BUT:localStorage[\"lg:chat:apiKey\"]์ A์ API ํค ์์กดlg_apiKey์ฟ ํค(httpOnly)์ A์ API ํค ์์กด๊ธด๊ธ๋
์ ์ ํด๊ฒฐ ๋ฐฉ์
์ ๊ทผ ๋ฐฉ๋ฒ
signOut๊ฒฝ๋ก์ ํด๋ผ์ด์ธํธ/์๋ฒ ์์ธก ์ ๋ฆฌ ๋ก์ง ์ถ๊ฐ:clearConnectionAction์ด ์๋ค๋ฉดfrontend/src/app/actions.ts์ ์ถ๊ฐ:lg_apiUrl,lg_assistantId,lg_apiKey์ฟ ํค๋ฅผ maxAge:0๋ก ๋ฎ์ด์ฐ๊ธฐ.๋์
์์ฉ ๊ธฐ์ค
useAuth().signOut()ํธ์ถ ์ localStorage์lg:connections,lg:chat:apiKey์ญ์ lg_apiKey,lg_apiUrl,lg_assistantId์ฟ ํค ๋ฌดํจํcd frontend && pnpm test์ฐธ์กฐ
frontend/src/features/auth/hooks/useAuth.ts,frontend/src/shared/components/settings/SettingsDialog.tsx,frontend/src/app/actions.ts์ฌํ ๋ฐฉ๋ฒ
์ฌ์ ์กฐ๊ฑด
๋จ๊ณ
/api/auth/signout๋๋ UI signOut ๋ฒํผ์ผ๋ก ๋ก๊ทธ์์๊ธฐ๋ ๊ฒฐ๊ณผ
๋ก๊ทธ์์ ํ ๋ชจ๋ LangGraph ์ฐ๊ฒฐ ๊ด๋ จ ์ ์ฅ์ ๋น์ด์์. B๋ ์ ์ฐ๊ฒฐ ์ค์ ํ์.
์ค์ ๊ฒฐ๊ณผ
A์ apiKey/apiUrl๊ฐ localStorage + ์ฟ ํค์ ์์กด. B๊ฐ A์ ์ฐ๊ฒฐ๋ก ์๋ ์ฑํ ์์.
๊ด๋ จ ์ฝ๋ ์ปจํ ์คํธ
frontend/src/features/auth/hooks/useAuth.tsfrontend/src/shared/components/settings/SettingsDialog.tsxfrontend/src/app/actions.tsclearConnectionAction์ถ๊ฐ ๋์frontend/src/features/auth/components/UserMenu.tsx