API Key รั่วไหลในเบราว์เซอร์? ตรวจสอบคีย์ OpenAI, Claude หรือ Gemini ของคุณใน 2 นาที
API Key รั่วไหลในเบราว์เซอร์? ตรวจสอบคีย์ OpenAI, Claude หรือ Gemini ของคุณใน 2 นาที ถ้าเว็บแอปของคุณเรียก OpenAI, Claude หรือ Gemini ตรงจากเบราว์เซอร์ คีย์ API ของคุณจะถูกเปิดเผยในเบราว์เซอร์ และใครก็ตามที่เปิด DevToo
API Key รั่วไหลในเบราว์เซอร์? ตรวจสอบคีย์ OpenAI, Claude หรือ Gemini ของคุณใน 2 นาที
ถ้าเว็บแอปของคุณเรียก OpenAI, Claude หรือ Gemini ตรงจากเบราว์เซอร์ คีย์ API ของคุณจะถูกเปิดเผยในเบราว์เซอร์ และใครก็ตามที่เปิด DevTools ก็สามารถคัดลอกมันไปได้ คุณยืนยันได้ใน 2 นาที: เปิดแท็บ Network ส่งคำขอ AI หนึ่งครั้ง แล้วมองหาเฮดเดอร์ Authorization, x-api-key หรือ x-goog-api-key ที่ส่งไปยังผู้ให้บริการ ถ้าคุณเห็นคีย์ของคุณอยู่ตรงนั้น ให้หมุนเวียนคีย์วันนี้เลย แล้วย้ายการเรียกไปไว้หลังเส้นทางฝั่งเซิร์ฟเวอร์
ด้านล่างนี้: วิธีตรวจสอบ ลำดับการแก้ไข และวิธีที่เอเจนต์เขียนโค้ดของคุณสามารถทดสอบเส้นทางที่แก้ไขแล้วได้
ทำไมคีย์ API ที่ถูกเปิดเผยในเบราว์เซอร์จึงเป็นปัญหา
คีย์ API ที่ถูกเปิดเผยในเบราว์เซอร์คือความลับที่ถูกส่งไปพร้อมกับโค้ดฝั่งฟรอนต์เอนด์หรือทราฟฟิกเครือข่ายของคุณ ซึ่งผู้เข้าชมทุกคนสามารถอ่านได้ ถ้าเบราว์เซอร์เป็นฝ่ายส่งคีย์ ผู้ใช้ก็สามารถคัดลอกมันไปและเรียกผู้ให้บริการโดยคิดเงินเข้าบิลของคุณ
คู่มือ Gemini API key guide ของ Google ระบุว่า "อย่าฮาร์ดโค้ดคีย์ API ลงในเว็บหรือแอปมือถือโดยตรง" เพราะ "คีย์ที่คอมไพล์เข้าไปในโค้ดฝั่งไคลเอนต์สามารถถูกผู้ใช้สกัดออกมาได้" และแนะนำให้ "รันเซิร์ฟเวอร์พร็อกซีแบ็กเอนด์เพื่อทำการเรียก API จริง" คู่มือ API key safety guide ของ OpenAI ก็ให้กฎเดียวกัน: ให้ส่งคำขอผ่านแบ็กเอนด์ของคุณเองแทนที่จะดีพลอยคีย์ไปที่ไคลเอนต์
README ของ openai-node ระบุว่าการรองรับเบราว์เซอร์ถูกปิดไว้โดยค่าเริ่มต้น "เพื่อหลีกเลี่ยงการเปิดเผยข้อมูลรับรอง API ลับของคุณ" และคุณต้องตั้งค่า dangerouslyAllowBrowser: true เพื่อเปิดใช้งาน TypeScript SDK ของ Anthropic ใช้ชื่อออปชันเดียวกัน และ API ของมันยอมรับการเรียกจากเบราว์เซอร์ (CORS) เฉพาะเมื่อคำขอมีเฮดเดอร์ anthropic-dangerous-direct-browser-access: true ตามที่ Simon Willison บันทึกไว้ตอนที่มันเปิดตัว
รูปแบบนี้พบได้บ่อยในแอปที่สร้างอย่างรวดเร็วด้วยเครื่องมือสร้างแอป AI และเอเจนต์เขียนโค้ด: เส้นทางที่สั้นที่สุดที่ใช้งานได้คือการ fetch จากคอมโพเนนต์ React โดยมีคีย์อยู่ในตัวแปร VITE_ หรือ NEXT_PUBLIC_ จากนั้นคีย์ที่ถูกขโมยจะถูกนำไปขายต่อสำหรับการประมวลผลจำนวนมาก ดังที่กล่าวถึงในโพสต์ของเราเรื่อง คีย์ AI API ที่ถูกขโมยและ LLMjacking และบิลของคุณคือที่ที่คุณจะรู้ตัว
วิธีตรวจสอบว่าคีย์ API ของคุณถูกเปิดเผยหรือไม่ (2 นาที)
ให้รันการตรวจสอบเหล่านี้กับโปรดักชัน ไม่ใช่ localhost
-
ดูแท็บ Network (45 วินาที) เปิดเว็บไซต์ของคุณ กด F12 (Cmd+Option+I บน Mac) เลือกแท็บ Network และเรียกใช้ฟีเจอร์ AI หนึ่งอย่าง เช่นส่งข้อความแชท กรองด้วย
openai,anthropicหรือgoogleapisถ้ามีคำขอใดส่งตรงไปที่api.openai.com,api.anthropic.comหรือgenerativelanguage.googleapis.comให้คลิกที่คำขอนั้นแล้วเปิด Headers การพบคีย์ใน[REDACTED CREDENTIAL],x-api-key,x-goog-api-keyหรือพารามิเตอร์คิวรี?key=` หมายความว่ามันถูกเปิดเผยแล้ว -
ค้นหาในบันเดิล (45 วินาที) ใน DevTools กด Cmd+Option+F (Ctrl+Shift+F บน Windows) เพื่อค้นหาในสคริปต์ทั้งหมดที่โหลดมา ค้นหา
sk-,sk-ant-,AIzaและdangerouslyAllowBrowserหากพบในไฟล์.jsที่ถูกเสิร์ฟให้เบราว์เซอร์ ถือว่ารั่วไหล แม้ว่าจะยังไม่มีคำขอใดถูกส่งออกไปก็ตาม -
ตรวจสอบคำนำหน้าของตัวแปรสภาพแวดล้อม (30 วินาที) เปิดไฟล์
.envและแดชบอร์ดโฮสติ้งของคุณ เครื่องมือบิลด์จะคัดลอกตัวแปรบางตัวเข้าไปในบันเดิลฝั่งไคลเอนต์โดยการออกแบบ:VITE_*ใน Vite (ดู เอกสาร env ของ Vite),NEXT_PUBLIC_*ใน Next.js,REACT_APP_*ใน Create React App และEXPO_PUBLIC_*ใน Expo คีย์ของผู้ให้บริการที่อยู่หลังคำนำหน้าเหล่านี้ถือเป็นสาธารณะ ไม่ว่าจะตั้งชื่ออย่างไรก็ตาม
สำหรับการสแกนเอาต์พุตบิลด์ของคุณผ่านเทอร์มินัล:
`bash
from your project root after a production build
grep -rnoE "sk-(proj-|ant-)?[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35}" dist/ build/ .next/static/ 2>/dev/null
grep -rn "dangerouslyAllowBrowser" dist/ build/ .next/static/ 2>/dev/null
`
เอาต์พุตว่างคือผ่าน Google ระบุว่าคีย์ AI Studio ใหม่ที่สร้างตั้งแต่ 28 พฤษภาคม 2026 เป็น "auth keys" ซึ่งรูปแบบอาจแตกต่างจาก AIza ดังนั้นให้ค้นหา generativelanguage.googleapis.com ในบันเดิลด้วย คุณจะผ่านก็ต่อเมื่อเบราว์เซอร์พูดคุยกับโดเมนของคุณเอง (ตัวอย่างเช่น /api/chat) และไม่มีชื่อโฮสต์ของผู้ให้บริการหรือคีย์ปรากฏขึ้นที่ใดเลย
ทำอะไรเมื่อคีย์ของคุณถูกเปิดเผย: ลำดับการแก้ไข
การหมุนเวียนคีย์ก่อนจะปิดช่องโหว่ของวันนี้ ส่วนการเปลี่ยนสถาปัตยกรรมจะปิดมันอย่างถาวร
| ขั้นตอน | สิ่งที่คุณทำ | ทำไมต้องเรียงลำดับแบบนี้ | เวลา |
|---|---|---|---|
| 1. หมุนเวียนคีย์ | สร้างคีย์ใหม่ในคอนโซลของผู้ให้บริการ แล้วเพิกถอนคีย์ที่รั่วไหล | คีย์เดิมอาจถูกคัดลอกไปแล้ว การเพิกถอนคือสิ่งเดียวที่หยุดการใช้งานมันได้ | 5 นาที |
| 2. ย้ายการเรียกไปฝั่งเซิร์ฟเวอร์ | วางการเรียกผู้ให้บริการไว้ในเส้นทางฝั่งเซิร์ฟเวอร์หรือเอดจ์ฟังก์ชันที่เก็บคีย์ไว้ | หยุดไม่ให้คีย์ใหม่รั่วไหลในลักษณะเดียวกัน | 30 ถึง 60 นาที |
| 3. เพิ่มการยืนยันตัวตนและขีดจำกัดอัตราต่อผู้ใช้ | กำหนดให้เส้นทางต้องมีเซสชัน และจำกัดจำนวนคำขอต่อผู้ใช้ | ถ้าไม่มีสิ่งนี้ พร็อกซีของคุณจะกลายเป็นปลายทาง AI ฟรีสำหรับทุกคน | 30 นาที |
| 4. ตั้งขีดจำกัดการใช้จ่ายของผู้ให้บริการ | ตั้งงบประมาณหรือขีดจำกัดการใช้จ่ายในการตั้งค่าการเรียกเก็บเงินของผู้ให้บริการแต่ละราย | จำกัดบิลกรณีเลวร้ายที่สุดหากมีอย่างอื่นรั่วไหล | 10 นาที |
| 5. สแกนใน CI | ทำให้บิลด์ล้มเหลวถ้ารูปแบบคีย์ปรากฏในบันเดิลเอาต์พุต | ป้องกันไม่ให้ฟีเจอร์ถัดไปที่เอเจนต์เขียนขึ้นมาทำลายการแก้ไขนี้ | 15 นาที |
ให้ดีพลอยคีย์ใหม่ฝั่งเซิร์ฟเวอร์ก่อนที่คุณจะเพิกถอนคีย์เดิม ไม่งั้นฟีเจอร์ AI ของคุณจะล่ม คู่มือของเราเรื่อง แนวปฏิบัติที่ดีที่สุดในการหมุนเวียนคีย์ API ครอบคลุมการหมุนเวียนแบบไม่มีดาวน์ไทม์ ในขั้นตอนที่ 4 คำแนะนำด้านความปลอดภัยของคีย์จาก OpenAI แนะนำให้ตั้งขีดจำกัดการใช้จ่ายแบบบังคับใช้อย่างเข้มงวด เพราะการแจ้งเตือนเพียงอย่างเดียวไม่หยุดทราฟฟิก งบประมาณการเรียกเก็บเงินของ Google Cloud จะแจ้งเตือนคุณแต่ไม่หยุดการใช้งานด้วยตัวเอง
วิธีย้ายการเรียก OpenAI, Claude และ Gemini ไปไว้หลังเส้นทางฝั่งเซิร์ฟเวอร์
เส้นทางพร็อกซีแบ็กเอนด์คือเอนด์พอยต์เซิร์ฟเวอร์ขนาดเล็กที่รับพรอมป์ของผู้ใช้ เพิ่มคีย์ API ที่ฝั่งเซิร์ฟเวอร์ เรียกผู้ให้บริการ และส่งคืนเฉพาะคำตอบเท่านั้น เบราว์เซอร์จะไม่เห็นคีย์เลย นี่คือเส้นทาง Next.js แบบมินิมัลสำหรับ OpenAI ส่วน Claude และ Gemini ก็ใช้รูปแบบเดียวกัน:
`ts
// app/api/chat/route.ts (runs on the server only)
import OpenAI from "openai";
import { getSession } from "@/lib/auth"; // your auth helper
import { checkRateLimit } from "@/lib/limits"; // your per-user limiter
const client = new OpenAI({ [REDACTED CREDENTIAL] }); // no NEXT_PUBLIC_ prefix
export async function POST(req: Request) {
const session = await getSession(req);
if (!session) return new Response("Unauthorized", { status: 401 });
if (!(await checkRateLimit(session.userId, 20))) { // 20 requests per minute
return new Response("Too many requests", { status: 429 });
}
const { message } = await req.json();
if (typeof message !== "string" || message.length > 4000) {
return new Response("Bad request", { status: 400 });
}
const response = await client.responses.create({
model: process.env.OPENAI_MODEL!, // pin the model server-side
input: message,
});
return Response.json({ reply: response.output_text }); // return the answer, nothing else
}
`
สิ่งที่ทำให้มันปลอดภัย ไม่ใช่แค่ซ่อนไว้:
- โมเดลอยู่บนเซิร์ฟเวอร์ ถ้าเบราว์เซอร์เป็นฝ่ายเลือกโมเดล ผู้โจมตีก็จะเลือกตัวที่แพงที่สุด
- อินพุตถูกจำกัด ไม่มีพรอมป์ขนาดยักษ์ผ่านบัญชีของคุณ
- ส่งกลับเฉพาะคำตอบ อย่าส่งคืนการตอบกลับดิบจากผู้ให้บริการหรือออบเจกต์ข้อผิดพลาด
-
ไม่มีคำนำหน้าสาธารณะ
OPENAI_API_KEYอยู่ฝั่งเซิร์ฟเวอร์ ส่วนNEXT_PUBLIC_OPENAI_API_KEYจะถูกส่งไปให้ผู้เข้าชมทุกคน
สำหรับ Claude และ Gemini ให้ใช้ SDK ของพวกเขากับ ANTHROPIC_API_KEY หรือ GEMINI_API_KEY การจำกัดเว็บไซต์และ IP ของ Google ช่วยได้ แต่ใครก็ตามที่มี curl สามารถส่งเฮดเดอร์ Referer อะไรก็ได้ ดังนั้นให้มองมันเป็นชั้นที่สอง ส่วนกรณีที่สมเหตุสมผลกรณีเดียวสำหรับการเรียกจากเบราว์เซอร์คือเครื่องมือแบบ bring-your-own-key ที่ผู้ใช้แต่ละคนวางคีย์ของตัวเอง
ให้เอเจนต์เขียนโค้ดของคุณทดสอบเส้นทางพร็อกซีด้วย Apidog CLI
การย้ายคีย์เป็นเพียงครึ่งเดียวของงาน อีกครึ่งคือการพิสูจน์ว่ามันยังอยู่ที่เดิมหลังฟีเจอร์ถัดไปที่เอเจนต์ของคุณเขียนขึ้น ให้เอเจนต์ที่สร้างเส้นทางนั้นทดสอบมันด้วย Apidog CLI ตั้งค่าครั้งเดียว:
bash
npm install -g apidog-cli@latest
apidog login --with-token <YOUR_TOKEN>
npx -y skills add https://apidog.com # pick your agent when prompted
จากนั้นให้พรอมป์เดียวกับ Claude Code, Codex, Cursor หรือเอเจนต์ใดก็ตามที่คุณใช้:
`text
Create Apidog test cases in project for POST /api/chat on our staging
environment that assert:
- a request without a session cookie returns 401,
- a valid request returns 200 and the body does not contain "sk-", "sk-ant-", "AIza" or "api_key",
- the 21st request inside 60 seconds from the same user returns 429.
Run them and summarize any failures.
`
เอเจนต์จะอ่านสคีมา ตรวจสอบเพย์โหลดของมัน เขียนเคสทดสอบและรันมัน:
bash
apidog test-case category --project <id>
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case.json
apidog test-case create --project <id> --file case.json
apidog run --test-case <caseId> --project <id> -e <envId> -r cli,json
การตรวจสอบข้อที่สองคือข้อที่สำคัญที่สุด: มันจะล้มเหลวทันทีที่ออบเจกต์ข้อผิดพลาดของผู้ให้บริการหรือฟิลด์ดีบักเริ่มสะท้อนคีย์ของคุณกลับไปยังไคลเอนต์ ให้ใส่ขั้นตอน apidog run ... -r junit เดียวกันนี้ในไปป์ไลน์ของคุณ เพื่อให้ทุกการดีพลอยตรวจซ้ำอีกครั้ง คู่มือ Apidog CLI CI/CD ของเราแสดงวิธีตั้งค่า และ Apidog CLI ใน Claude Code ครอบคลุมฝั่งเอเจนต์ แผนฟรีของ Apidog รองรับผู้ใช้สูงสุด 4 คน ทีมเล็กจึงสามารถทำสิ่งนี้ได้โดยไม่ต้องจ่ายค่าที่นั่ง
สำหรับปัญหาด้านเดียวกันในฝั่งรีโป คือคีย์ที่ถูกคอมมิตเข้า Git แทนที่จะถูกส่งไปยังเบราว์เซอร์ ดูโพสต์ของเราเรื่อง การตรวจสอบการรั่วไหลของคีย์ API ด้วยตัวสแกนความลับ ถ้าเอเจนต์ของคุณเรียกเครื่องมือที่จัดการข้อมูลลูกค้า การทดสอบเอเจนต์ AI เพื่อหาการรั่วไหลของข้อมูล ครอบคลุมชั้นถัดไป
ข้อผิดพลาดทั่วไปที่ทำให้คีย์ยังคงถูกเปิดเผย
- การเข้ารหัสคีย์เป็น Base64 หรือแบ่งมันออกเป็นส่วน ๆ เบราว์เซอร์ต้องประกอบมันกลับเพื่อส่ง ดังนั้น DevTools จะแสดงค่าจริง
- การย้ายมันไปไว้ใน localStorage หรือคุกกี้ มันยังอยู่บนไคลเอนต์อยู่ดี
-
การทำพร็อกซีโดยไม่มีการยืนยันตัวตน
/api/chatที่เป็นสาธารณะคือปลายทาง AI เปิดโล่งที่คิดเงินเข้าบิลของคุณ - การเก็บคีย์เดิมไว้ "เผื่อไว้" คีย์ที่เป็นสาธารณะแม้แค่วันเดียวก็ถูกคัดลอกไปแล้ว
- การลืมแอปมือถือ คู่มือของ Google กล่าวถึง "เว็บหรือแอปมือถือ" รวมกันด้วยเหตุผลบางอย่าง
คำถามที่พบบ่อย
ฉันจะรู้ได้อย่างไรว่าคีย์ OpenAI API ของฉันถูกเปิดเผยในเบราว์เซอร์? ให้เรียกใช้ฟีเจอร์ AI โดยเปิดแท็บ Network ของ DevTools ไว้ คำขอไปที่ api.openai.com ที่มีเฮดเดอร์ Authorization: Bearer [REDACTED]-... หมายความว่ามันถูกเปิดเผยแล้ว ให้ค้นหาสคริปต์ที่โหลดมาสำหรับ sk- และ dangerouslyAllowBrowser ด้วย
การใส่คีย์ API ในตัวแปรสภาพแวดล้อม NEXT_PUBLIC_ หรือ VITE_ ปลอดภัยหรือไม่? ไม่ปลอดภัย คำนำหน้าเหล่านี้มีไว้เพื่อคัดลอกค่าต่าง ๆ เข้าไปในบันเดิลฝั่งไคลเอนต์ คีย์ของผู้ให้บริการใดก็ตามที่เก็บไว้ใต้คำนำหน้าเหล่านี้จะถูกส่งไปให้ผู้เข้าชมทุกคน ให้ใช้ตัวแปรฝั่งเซิร์ฟเวอร์เท่านั้นที่ไม่มีคำนำหน้า และอ่านมันภายในเส้นทางฝั่งเซิร์ฟเวอร์
การจำกัดคีย์ Gemini API ให้ใช้กับเว็บไซต์ของฉันเท่านั้นทำให้มันปลอดภัยในเบราว์เซอร์หรือไม่? มันช่วยได้ แต่สคริปต์ที่อยู่นอกเบราว์เซอร์สามารถส่งเฮดเดอร์ Referer อะไรก็ได้ คู่มือของ Google เองก็แนะนำให้ใช้พร็อกซีแบ็กเอนด์สำหรับแอปฝั่งไคลเอนต์
ฉันพบว่าคีย์ของฉันถูกเปิดเผย ควรทำอะไรก่อน? สร้างคีย์ใหม่ ดีพลอยมันฝั่งเซิร์ฟเวอร์ แล้วเพิกถอนคีย์เดิม ต่อไปให้ย้ายการเรียกไปไว้หลังเส้นทางที่ต้องยืนยันตัวตนพร้อมขีดจำกัดอัตราต่อผู้ใช้ และตั้งขีดจำกัดการใช้จ่ายของผู้ให้บริการ
เอเจนต์เขียนโค้ดของฉันสามารถแก้ไขและทดสอบสิ่งนี้ให้ฉันได้ไหม? ได้ ให้สั่งมันย้ายการเรียกผู้ให้บริการเข้าไปในเส้นทางฝั่งเซิร์ฟเวอร์ จากนั้นใช้ Apidog CLI สร้างและรันการทดสอบที่ยืนยันว่าได้ 401 เมื่อไม่มีเซสชัน ไม่มีรูปแบบคีย์ในคำตอบ และได้ 429 เมื่อเกินขีดจำกัดอัตราของคุณ
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.
