Skip to content

๐ŸŸ [P1] fix(frontend): API key written to JS-readable cookie via document.cookie + localStorageย #77

Description

@teddylee777

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

Key Value
Category frontend
Checklist ISS-UI-R9 โ€” Use of window/document/localStorage without SSR safety / security
Priority P1 ๐ŸŸ 
Scan Date 2026-04-16
Flagged By @code-review

์š”์•ฝ

  • WHAT: ApiKeyLoginForm์ด document.cookie๋กœ API ํ‚ค๋ฅผ ์ง์ ‘ ์“ฐ๋ฉด์„œ httpOnly ํ”Œ๋ž˜๊ทธ๊ฐ€ ์—†์Œ + ConnectionList๋„ localStorage.setItem(\"lg:chat:apiKey\", ...)๋กœ ํ‰๋ฌธ ์ €์žฅ
  • WHY: XSS ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•˜๋ฉด LangSmith API ํ‚ค๊ฐ€ document.cookie ๋˜๋Š” localStorage ํ†ตํ•ด ์ฆ‰์‹œ ์œ ์ถœ. switchConnection์€ server action ํ†ตํ•ด httpOnly ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์•ˆ์ „ํ•œ ๊ฒฝ๋กœ๊ฐ€ ์ด๋ฏธ ์žˆ๋Š”๋ฐ, ApiKeyLoginForm์€ ์ด๋ฅผ ์šฐํšŒ
  • WHERE: frontend/src/app/(auth)/login/ApiKeyLoginForm.tsx:71, frontend/src/shared/components/settings/ConnectionList.tsx:113
  • SEVERITY: HIGH โ€” XSS exfiltration vector for paid LangSmith API key

Evidence

# File Line Finding Flagged By Confidence
1 frontend/src/app/(auth)/login/ApiKeyLoginForm.tsx 71 document.cookie = \lg_apiKey=...; samesite=lax`โ€”httponly` ํ”Œ๋ž˜๊ทธ ์—†์Œ, JS์—์„œ ์ฝ๊ธฐ ๊ฐ€๋Šฅ @code-review High
2 frontend/src/shared/components/settings/ConnectionList.tsx 113 localStorage.setItem(\"lg:chat:apiKey\", connection.apiKey) โ€” ํ‰๋ฌธ localStorage ์ €์žฅ @code-review High
3 (๋Œ€์กฐ) frontend/src/app/actions.ts updateConnectionAction โ€” server action ๊ฒฝ๋กœ๋Š” httpOnly: true๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์„ค์ • (์ฐธ๊ณ : ์•ˆ์ „ํ•œ ๊ฒฝ๋กœ ์กด์žฌ) @code-review High

์˜ํ–ฅ ๋ถ„์„

์˜ํ–ฅ ๋ฒ”์œ„

  • API key ์ธ์ฆ ๋ชจ๋“œ(api-key) ์‚ฌ์šฉ ๋ชจ๋“  ์‚ฌ์šฉ์ž
  • ConnectionList๋กœ ๋‹ค์ค‘ ์—ฐ๊ฒฐ ๊ด€๋ฆฌํ•˜๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž
  • XSS ์ทจ์•ฝ์  ๋ฐœ์ƒ ์‹œ ์ฆ‰์‹œ ํ‚ค ํƒˆ์ทจ โ†’ LangSmith ๊ฒฐ์ œ ํญํƒ„, ํƒ€ ํ”„๋กœ์ ํŠธ trace ์ ‘๊ทผ

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

  1. ์‚ฌ์šฉ์ž๊ฐ€ admin ์ฝ˜์†”์ด๋‚˜ third-party ์˜์กด์„ฑ์—์„œ XSS ํŽ˜์ด๋กœ๋“œ ์‹คํ–‰
  2. ํŽ˜์ด๋กœ๋“œ๊ฐ€ document.cookie ๋˜๋Š” localStorage[\"lg:chat:apiKey\"] ์ฝ์Œ
  3. ์™ธ๋ถ€ ์„œ๋ฒ„๋กœ ํ‚ค ์ „์†ก
  4. ๊ณต๊ฒฉ์ž๊ฐ€ ํ‚ค๋กœ LangSmith API ํ˜ธ์ถœ, trace ๋‹ค์šด๋กœ๋“œ, ๋น„์šฉ ๋ฐœ์ƒ

๊ธด๊ธ‰๋„

  • ๊ธฐ์กด ์ด์Šˆ ๐ŸŸ [P1] fix(security): NEXT_PUBLIC_LANGCHAIN_API_KEY exposed to browser bundleย #58 (NEXT_PUBLIC_LANGCHAIN_API_KEY browser exposure)๊ณผ ๋™์ผํ•œ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ณ„๋„ ๋ฒกํ„ฐ
  • httpOnly ์ฟ ํ‚ค ๊ฒฝ๋กœ๊ฐ€ ์ด๋ฏธ ์ฝ”๋“œ์— ์กด์žฌํ•˜๋ฏ€๋กœ ์ˆ˜์ • ๋น„์šฉ ๋‚ฎ์Œ

์ œ์•ˆ ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

์ ‘๊ทผ ๋ฐฉ๋ฒ•

ApiKeyLoginForm: document.cookie ์ง์ ‘ ์“ฐ๊ธฐ ์ œ๊ฑฐ, ๊ธฐ์กด server action ํ†ตํ•œ updateConnectionAction ํ˜ธ์ถœ๋กœ ํ†ต์ผ:

// ๋ณ€๊ฒฝ ์ „ (line 71)
document.cookie = \`lg_apiKey=\${connection.apiKey}; path=/; samesite=lax\`;

// ๋ณ€๊ฒฝ ํ›„
import { updateConnectionAction } from \"@/app/actions\";
await updateConnectionAction({
  apiUrl: connection.apiUrl,
  assistantId: connection.assistantId,
  apiKey: connection.apiKey,
});

ConnectionList: localStorage.setItem(\"lg:chat:apiKey\", ...) ์ œ๊ฑฐ (์ด๋ฏธ server action์ด httpOnly ์ฟ ํ‚ค ์ฒ˜๋ฆฌ). API ํ‚ค๋ฅผ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋‹ค์‹œ ์ฝ์–ด์•ผ ํ•œ๋‹ค๋ฉด server action getConnectionAction() ํ˜ธ์ถœ.

๋Œ€์•ˆ

  • httpOnly: false ์œ ์ง€ํ•˜๋˜ ์งง์€ ๋งŒ๋ฃŒ: ๋งŒ๋ฃŒ ๋‹จ์ถ•์œผ๋กœ๋Š” XSS ๋…ธ์ถœ ์‹œ๊ฐ„ ์ค„์–ด๋“ค์ง€๋งŒ 1ํšŒ ํƒˆ์ทจ๋กœ๋„ ์ถฉ๋ถ„ โ€” ๋ฏธํ•ด๊ฒฐ
  • ์•”ํ˜ธํ™” ์ €์žฅ: ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ์•”ํ˜ธํ™”๋Š” ํ‚ค ์ž์ฒด๊ฐ€ ํด๋ผ์ด์–ธํŠธ์— ์žˆ์œผ๋ฏ€๋กœ ์˜๋ฏธ ์—†์Œ

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

  • document.cookie = \"lg_apiKey=...\" ํŒจํ„ด ์ œ๊ฑฐ
  • localStorage.setItem(\"lg:chat:apiKey\", ...) ์ œ๊ฑฐ
  • grep์œผ๋กœ document.cookie, localStorage.*apiKey 0๊ฑด ํ™•์ธ
  • e2e: API ํ‚ค ๋ชจ๋“œ ๋กœ๊ทธ์ธ ํ›„ DevTools์—์„œ document.cookie์— lg_apiKey ์—†์Œ ํ™•์ธ
  • ํ…Œ์ŠคํŠธ ์ปค๋งจ๋“œ: cd frontend && pnpm test

์ฐธ์กฐ

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

์‚ฌ์ „ ์กฐ๊ฑด

  • API key ์ธ์ฆ ๋ชจ๋“œ๋กœ ๋กœ๊ทธ์ธ

๋‹จ๊ณ„

  1. ๋ธŒ๋ผ์šฐ์ €์—์„œ ApiKeyLoginForm ํ†ตํ•ด ํ‚ค ์ž…๋ ฅ ํ›„ ๋กœ๊ทธ์ธ
  2. DevTools Console์—์„œ document.cookie ํ™•์ธ
  3. localStorage.getItem(\"lg:chat:apiKey\") ํ™•์ธ

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ

๋‘˜ ๋‹ค ํ‚ค ๋…ธ์ถœ ์—†์Œ (server action์ด httpOnly ์ฟ ํ‚ค๋กœ ์ €์žฅ)

์‹ค์ œ ๊ฒฐ๊ณผ

document.cookie์— lg_apiKey=sk-... ํ‰๋ฌธ ๋…ธ์ถœ, localStorage์—๋„ ๋™์ผ

๊ด€๋ จ ์ฝ”๋“œ ์ปจํ…์ŠคํŠธ

File Role Relevance
frontend/src/app/(auth)/login/ApiKeyLoginForm.tsx API key ์ธ์ฆ ํผ ์ˆ˜์ • ๋Œ€์ƒ
frontend/src/shared/components/settings/ConnectionList.tsx ์—ฐ๊ฒฐ ๊ด€๋ฆฌ UI ์ˆ˜์ • ๋Œ€์ƒ
frontend/src/app/actions.ts server actions (httpOnly ์ฟ ํ‚ค ์‚ฌ์šฉ) ํ˜ธ์ถœ ๋Œ€์ƒ (์žฌ์‚ฌ์šฉ)

Detected by oh-my-braincrew `omb:issue` scan
Category: frontend | Scan date: 2026-04-16
`omb-issue-scan category=frontend checklist=ISS-UI-R9`

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