Skip to content

๐ŸŸ [P1] fix(frontend): signOut does not clear connection cookie or localStorage โ€” cross-user leak on shared deviceย #78

Description

@teddylee777

์ปจํ…์ŠคํŠธ ๋ธ”๋ก

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)๋Š” ๋‹ค์Œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ž๋™ ๋ฐ˜์˜

์žฅ์•  ์‹œ๋‚˜๋ฆฌ์˜ค

  1. ์‚ฌ์šฉ์ž A๊ฐ€ ๊ณต์šฉ ๊ธฐ๊ธฐ์—์„œ NextAuth ๋กœ๊ทธ์ธ, admin ์ฝ˜์†”์—์„œ ๊ฐœ์ธ LangSmith ํ‚ค๋กœ ์—ฐ๊ฒฐ ์„ค์ •
  2. A๊ฐ€ ๋กœ๊ทธ์•„์›ƒ (signOut) โ†’ NextAuth ์„ธ์…˜ ์ฟ ํ‚ค ์‚ญ์ œ, BUT:
    • localStorage[\"lg:chat:apiKey\"]์— A์˜ API ํ‚ค ์ž”์กด
    • lg_apiKey ์ฟ ํ‚ค(httpOnly)์— A์˜ API ํ‚ค ์ž”์กด
  3. ์‚ฌ์šฉ์ž B๊ฐ€ ๊ฐ™์€ ๊ธฐ๊ธฐ์— ๋กœ๊ทธ์ธ
  4. 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 ์‹ ๋ขฐ๋„ ๋‚ฎ์Œ, ์žŠ๊ธฐ ์‰ฌ์›€

์ˆ˜์šฉ ๊ธฐ์ค€

  • useAuth().signOut() ํ˜ธ์ถœ ์‹œ localStorage์˜ lg:connections, lg:chat:apiKey ์‚ญ์ œ
  • lg_apiKey, lg_apiUrl, lg_assistantId ์ฟ ํ‚ค ๋ฌดํšจํ™”
  • e2e: ๋กœ๊ทธ์ธ โ†’ ์—ฐ๊ฒฐ ์„ค์ • โ†’ ๋กœ๊ทธ์•„์›ƒ ํ›„ DevTools์—์„œ ๊ด€๋ จ ์ €์žฅ์†Œ ๋น„์–ด์žˆ์Œ ํ™•์ธ
  • ํ…Œ์ŠคํŠธ ์ปค๋งจ๋“œ: cd frontend && pnpm test

์ฐธ์กฐ

์žฌํ˜„ ๋ฐฉ๋ฒ•

์‚ฌ์ „ ์กฐ๊ฑด

  • credentials ๋˜๋Š” OAuth ๋ชจ๋“œ๋กœ ์‹คํ–‰
  • 2๋ช…์˜ ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž ๊ณ„์ •

๋‹จ๊ณ„

  1. ์‚ฌ์šฉ์ž A๋กœ ๋กœ๊ทธ์ธ, admin ์ฝ˜์†”์—์„œ apiKey ์„ค์ • ํ›„ ์ €์žฅ
  2. /api/auth/signout ๋˜๋Š” UI signOut ๋ฒ„ํŠผ์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ
  3. DevTools > Application > Storage ํ™•์ธ: localStorage, Cookies
  4. ์‚ฌ์šฉ์ž B๋กœ ๋กœ๊ทธ์ธ
  5. 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`

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions