อย่าใส่ Secret ลงใน Prompt: จัดการ API Key ให้ปลอดภัย

พอเริ่มใช้ AI ช่วยเขียนโค้ดและเรียก LLM API จากระบบของเรา ของมีค่าชิ้นใหม่ก็เพิ่มเข้ามาในโปรเจกต์ นั่นคือ API key ซึ่งผูกกับบัตรเครดิตหรือบัญชีบริษัทโดยตรง key ที่หลุดไปอาจถูกเอาไปใช้จนบิลพุ่งในคืนเดียว หรือถูกใช้เข้าถึงข้อมูลที่อยู่หลัง key นั้น บทความนี้รวบรวมแนวทางจัดการ secret แบบที่ทีมเล็กก็ทำได้ทันที ตั้งแต่เรื่องที่ไม่ควรวาง secret ลงใน prompt การใช้ .env การกัน commit การ rotate ไปจนถึงตัวอย่างโหลด key ใน PHP/Yii2 อย่างถูกวิธี
key รั่วได้จากทางไหนบ้าง
หลายคนนึกถึงแค่ "เผลอ push ขึ้น GitHub" แต่ในยุคที่เราคุยกับ AI ทั้งวัน ช่องทางรั่วมีมากกว่านั้น
- วางลงในแชตหรือ prompt เช่น copy ไฟล์ config ทั้งไฟล์ไปถาม AI ว่าทำไมต่อฐานข้อมูลไม่ได้ ข้อความนั้นถูกส่งออกไปยังผู้ให้บริการ และอาจถูกเก็บไว้ในประวัติแชตหรือ log ซึ่งเราควบคุมไม่ได้
- AI coding agent อ่านไฟล์ในโปรเจกต์ agent ที่มีสิทธิ์อ่านทุกไฟล์อาจอ่าน
.envแล้วนำค่าไปใส่ในโค้ดตัวอย่าง ใน log หรือในข้อความ commit - Commit เข้า Git ถึงจะลบใน commit ถัดไป ค่าก็ยังอยู่ใน history และมีบอตที่สแกนหา key จาก repository สาธารณะอยู่ตลอดเวลา
- ฝั่ง frontend ใส่ key ไว้ใน JavaScript เพื่อเรียก API ตรงจากเบราว์เซอร์ ใครเปิด DevTools ก็เห็น
- Log และ error page log ที่บันทึก header ของ request ทั้งหมด หรือหน้า debug ที่เปิดทิ้งไว้บน production
- System prompt ใส่ token หรือรหัสผ่านไว้ใน system prompt เพื่อให้โมเดลใช้ต่อ ซึ่ง prompt injection สามารถดึงออกมาได้ (ดูบท "Prompt Injection คืออะไร และป้องกันในแอปของเรา")
กฎข้อแรก: secret ไม่ควรอยู่ใน prompt เลย
LLM ไม่จำเป็นต้องรู้ API key, รหัสผ่านฐานข้อมูล หรือ token ใด ๆ เพื่อทำงาน ถ้าโมเดลต้องเรียกระบบภายนอก ให้โมเดลเรียก tool ที่เราเขียน แล้วโค้ดฝั่ง server เป็นคนแนบ credential ตอนเรียกจริง โมเดลเห็นแค่ชื่อ tool และผลลัพธ์
ตอนขอให้ AI ช่วย debug ก็ใช้หลักเดียวกัน
- ไม่ดี วาง
common/config/main-local.phpทั้งไฟล์ที่มี username/password จริงลงในแชต - ดี แทนค่าจริงด้วย placeholder เช่น
'password' => '***'และบอกเฉพาะข้อมูลที่จำเป็น เช่น "ใช้ PostgreSQL บน host ภายใน Docker network"
สำหรับ AI coding agent ให้ตั้งค่าให้ไม่อ่านไฟล์ secret ถ้าเครื่องมือรองรับ (เช่น ignore list หรือ permission rule) และอย่ารัน agent ในเครื่องที่มี credential ของ production อยู่ใน environment โดยไม่จำเป็น
เก็บ key ใน environment variable และ .env
หลักคือโค้ดอ่าน key จาก environment ส่วนค่าจริงอยู่นอก repository ตอน dev ใช้ไฟล์ .env ตอน production ใช้ระบบ secret ของแพลตฟอร์มที่ deploy เช่น environment ของ container หรือ secret manager
# .env (ห้าม commit)
LLM_API_URL=https://api.example-provider.com/v1/chat
LLM_MODEL=your-model-name
LLM_API_KEY=replace-with-real-key
แล้วสร้าง .env.example ที่มีแค่ชื่อตัวแปรไว้ commit ให้เพื่อนในทีมรู้ว่าต้องตั้งค่าอะไรบ้าง
ในโปรเจกต์ Yii2 ที่ติดตั้ง vlucas/phpdotenv ผ่าน Composer สามารถโหลดได้ที่ entry script ก่อนสร้าง application
<?php
// frontend/web/index.php (ส่วนต้นไฟล์)
require __DIR__ . '/../../vendor/autoload.php';
$envDir = dirname(__DIR__, 2);
if (is_file($envDir . '/.env')) {
// createImmutable จะไม่เขียนทับตัวแปรที่ server ตั้งไว้แล้ว
Dotenv\Dotenv::createImmutable($envDir)->load();
}
จากนั้นอ่านค่าผ่านฟังก์ชันเล็ก ๆ ที่ fail ให้ชัดเมื่อไม่มีค่า ดีกว่าปล่อยให้ส่ง request ด้วย key ว่างแล้วไปงงกับ error 401 ภายหลัง
<?php
namespace common\helpers;
final class Env
{
public static function require(string $name): string
{
$value = $_ENV[$name] ?? getenv($name);
if ($value === false || $value === null || $value === '') {
throw new \RuntimeException("Missing environment variable: {$name}");
}
return (string)$value;
}
}
// การใช้งาน
$apiKey = \common\helpers\Env::require('LLM_API_KEY');
ข้อควรระวัง ข้อความ exception บอกแค่ชื่อตัวแปร ไม่พิมพ์ค่า และอย่าเผลอ var_dump($_ENV) หรือ phpinfo() ทิ้งไว้บน server เพราะจะแสดง environment ทั้งหมด
กันไม่ให้ secret เข้า Git ตั้งแต่ต้น
อย่าพึ่งความระวังของคนอย่างเดียว ให้ตั้งเครื่องมือช่วยกัน
# .gitignore
.env
.env.*
!.env.example
*-local.php
โปรเจกต์ Yii2 advanced template มี *-local.php แยกไว้ให้อยู่แล้ว ตรวจให้แน่ใจว่ายังอยู่ใน .gitignore นอกจากนี้ควรมีตัวช่วยอีกชั้น
- ใช้ pre-commit hook หรือเครื่องมือสแกน secret ใน CI เพื่อตรวจ diff ก่อน merge
- เปิดใช้ฟีเจอร์สแกน secret ของแพลตฟอร์ม Git ที่ทีมใช้ ถ้ามี
- รีวิว diff ทุกครั้งก่อน commit โดยเฉพาะโค้ดที่ AI สร้างให้ เพราะบางครั้ง AI ใส่ค่าตัวอย่างที่หน้าตาเหมือน key จริง หรือใส่ค่าจริงที่มันอ่านเจอ
ถ้าพลาด commit ไปแล้ว ให้ถือว่า key นั้น รั่วแล้ว ทันที การเขียน history ใหม่ (rewrite history) ไม่พอ เพราะอาจมีคน clone หรือ fork ไปแล้ว สิ่งที่ต้องทำก่อนคือ revoke และออก key ใหม่ ส่วนการล้าง history ค่อยทำตามมา (ดูวิธีจัดการ history ในเอกสารของ Git ที่ git-scm.com)
จำกัดความเสียหาย: สิทธิ์ งบ และการ rotate
สมมติว่าวันหนึ่ง key จะรั่วแน่นอน คำถามคือจะรั่วแล้วเสียหายแค่ไหน
- แยก key ตามสภาพแวดล้อมและงาน dev, staging, production ใช้คนละ key ฟีเจอร์ต่างกันใช้คนละ key ถ้ารั่วจะรู้ว่ามาจากไหน และ revoke ได้โดยไม่กระทบส่วนอื่น
- จำกัดสิทธิ์ของ key ถ้าผู้ให้บริการรองรับ ให้จำกัด scope เช่น ใช้ได้เฉพาะบาง endpoint บางโปรเจกต์ หรือบาง IP
- ตั้งงบและการแจ้งเตือน ผู้ให้บริการ AI ส่วนใหญ่มีหน้าตั้ง usage limit หรือ budget alert ตั้งเพดานให้สอดคล้องกับการใช้งานจริง และให้แจ้งเตือนก่อนถึงเพดาน
- จำกัดฝั่งเราเอง ทำ rate limit ต่อผู้ใช้ใน backend (Yii2 มี
RateLimiterfilter) เพื่อไม่ให้ผู้ใช้คนเดียวใช้ทรัพยากรจนหมด - Rotate เป็นรอบ เปลี่ยน key ตามรอบที่ทีมกำหนด และทันทีเมื่อมีคนออกจากทีมหรือสงสัยว่ารั่ว ออกแบบให้ระบบรองรับการเปลี่ยน key ได้โดยแก้แค่ environment แล้ว restart ไม่ต้องแก้โค้ด
วิธี rotate ที่ไม่ทำให้ระบบล่มคือ สร้าง key ใหม่ ใส่ใน environment แล้ว deploy ตรวจว่าระบบใช้ key ใหม่ได้ จากนั้นค่อย revoke key เก่า
อย่าส่ง key ไปฝั่งเบราว์เซอร์
ข้อผิดพลาดที่เจอบ่อยในโปรเจกต์ที่ทำเร็ว ๆ คือให้ JavaScript เรียก LLM API ตรงพร้อมแนบ key ไม่ว่าจะซ่อนในไฟล์ build หรือ obfuscate ยังไงก็ถูกดึงออกมาได้ ทางที่ถูกคือให้เบราว์เซอร์เรียก endpoint ของเราเอง แล้ว backend ตรวจสิทธิ์ผู้ใช้ จำกัดอัตรา แล้วค่อยเรียก LLM ด้วย key ที่อยู่บน server
// ฝั่งเบราว์เซอร์: เรียก backend ของเรา ไม่มี key อยู่ในโค้ดนี้
const csrf = document.querySelector('meta[name="csrf-token"]').content;
const res = await fetch('/ai/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
body: JSON.stringify({ question: 'สินค้านี้รับประกันกี่ปี' }),
});
const data = await res.json();
สรุป
- ไม่ใส่ secret ใน prompt ใน system prompt หรือในข้อความที่วางให้ AI ช่วย debug ให้แทนด้วย placeholder
- เก็บ key ใน environment variable ใช้
.envตอน dev และกันไว้ใน.gitignoreพร้อมสแกน secret อัตโนมัติ - ถ้า key หลุดเข้า Git ให้ revoke และออกใหม่ทันที ไม่ใช่แค่ลบ commit
- ลดความเสียหายด้วยการแยก key จำกัดสิทธิ์ ตั้งงบ ทำ rate limit และ rotate เป็นรอบ
- key ต้องอยู่ฝั่ง server เท่านั้น เบราว์เซอร์เรียกผ่าน backend ของเรา
ลองทำ
ใช้เวลา 20 นาทีตรวจโปรเจกต์ของคุณหนึ่งโปรเจกต์ รัน git log -p | grep -iE "api_key|secret|password" เพื่อดูว่ามีค่าน่าสงสัยใน history หรือไม่ ตรวจว่า .env และ *-local.php อยู่ใน .gitignore จากนั้นย้าย key ที่ยัง hardcode อยู่ในโค้ดอย่างน้อยหนึ่งตัวไปอ่านผ่าน Env::require() และตั้ง budget alert ในบัญชีผู้ให้บริการ AI ที่ใช้อยู่
ความคิดเห็น (0)
ยังไม่มีความคิดเห็น — เริ่มคุยเป็นคนแรก ถามสิ่งที่สงสัย หรือแชร์ประสบการณ์ของคุณ