Boom Leverage
บทความทั้งหมด

ตั้งด่านอนุมัติให้ Claude Code รันเองได้อย่างปลอดภัย: permission 3 ชั้น + PreToolUse hook สำหรับงานการเงิน

ให้ AI agent รันงานเองได้ แต่บล็อก git push, rm -rf และการส่งข้อมูลออกเน็ตอัตโนมัติ ด้วย permission allow/ask/deny + PreToolUse hook ใน Claude Code — เทมเพลตก็อปไปใช้ได้ มุมคนทำ data/risk

Varanchai Yingkhamnueng·
claude-codeBoom Leverage

ตั้งด่านอนุมัติให้ Claude Code รันเองได้อย่างปลอดภัย: permission 3 ชั้น + PreToolUse hook สำหรับงานการเงิน

ให้ AI agent รันงานเองได้ แต่บล็อก git push, rm -rf และการส่งข้อมูลออกเน็ตอัตโนมัติ ด้วย permission allow/ask/deny + PreToolUse hook ใน Claude Code — เทมเพลตก็อปไปใช้ได้ มุมคนทำ data/risk

เวลาผมเล่าให้เพื่อนสาย data/risk ฟังว่าปล่อยให้ Claude Code รันงานเองแบบไม่นั่งเฝ้าทุกคำสั่ง คำถามแรกที่ได้เสมอคือ "แล้วมันไปลบไฟล์ หรือ push โค้ดที่ยังไม่ตรวจ หรือส่งข้อมูลลูกค้าออกเน็ตไม่ได้เหรอ" คำถามนี้ถูกต้องมาก และคำตอบไม่ใช่ "เชื่อใจมัน" แต่คือ ตั้งด่านให้มันขออนุมัติก่อนแตะของจริง — เหมือนที่เราออกแบบ control ในงานแบงก์ ที่ไม่ได้ห้ามคนทำงาน แต่บังคับให้ของสำคัญต้องผ่านการอนุมัติ บทความนี้จะให้ เทมเพลต 2 ไฟล์ที่ก็อปไปวางแล้วใช้ได้จริง คือ .claude/settings.json (ด่านกฎ) กับ .claude/hooks/approval-gate.sh (ด่านตรวจตอนรัน) ผมจะอธิบายทั้งว่ามันทำงานยังไงและทำไมต้องมีทั้งสองชั้น ไม่ใช่แค่ชั้นเดียว

1. ปัญหา: อยากให้ agent ทำงานเอง แต่กลัวมันแตะของจริง

ประเด็นของการรัน agent อัตโนมัติไม่ใช่ "ความสามารถ" แต่คือ "ขอบเขต" งานอ่านไฟล์ รัน python3 วิเคราะห์ข้อมูล พวกนี้ปล่อยได้สบาย แต่มีคำสั่งกลุ่มหนึ่งที่ทำแล้วย้อนยาก — ลบไฟล์, push ขึ้น remote, ส่งข้อมูลออกอินเทอร์เน็ต — พวกนี้ต่างหากที่ต้องมีมนุษย์กดอนุมัติ

Claude Code ออกแบบเรื่องนี้ไว้เป็นระบบ permission แบบไล่ระดับอยู่แล้ว เอกสารทางการระบุหลักการว่าเครื่องมืออ่านอย่างเดียวไม่ต้องขออนุมัติ แต่คำสั่ง shell และการแก้ไฟล์ต้องขอ สิ่งที่เราจะทำคือ เขียนกฎให้ชัดว่าอะไรอนุญาต อะไรต้องถาม อะไรห้ามเด็ดขาด แล้วเสริมด้วย hook อีกชั้นเพื่ออุดช่องที่กฎแบบ pattern จับไม่ได้ แนวคิดนี้เป็นญาติกับเรื่องที่ผมเขียนไว้ในทำไมต้องรัน AI agent ใน sandbox — sandbox คือกำแพงรอบนอก ส่วนด่านอนุมัตินี้คือ control ชั้นในที่ละเอียดกว่า

ทำไมมันสำคัญกับเรา: ในงาน model risk เราไม่เคยจัดการความเสี่ยงด้วยการ "ห้ามทำทั้งหมด" หรือ "ปล่อยทั้งหมด" เราจัดชั้นตามผลกระทบ ด่านอนุมัติของ agent ก็ใช้ตรรกะเดียวกัน — จัดคำสั่งตามความย้อนยาก แล้วใส่แรงเสียดทานให้พอดีกับความเสี่ยง ไม่ใช่ตั้ง friction เท่ากันหมดจนใช้งานไม่ได้

2. ชั้นที่ 1 — permission allow/ask/deny

ด่านแรกคือไฟล์ .claude/settings.json มีสามลิสต์คือ allow (อนุญาตเลย), ask (ถามก่อน), deny (ห้าม) กฎสำคัญที่ต้องจำคือ ลำดับการตัดสินเป็น deny → ask → allow แล้ว match แรกชนะ — เอกสารทางการเขียนไว้ว่า "Rules are evaluated in order: deny, then ask, then allow" แปลว่า deny ชนะทุกอย่าง ถ้าคำสั่งไหนเข้า deny ก็จบ ไม่ต้องดู allow ต่อ

นี่คือเทมเพลตที่ผมใช้เป็นฐานสำหรับ workspace สาย data/risk ก็อปไปวางที่ .claude/settings.json ได้เลย

{
  "permissions": {
    "deny": [
      "Bash(git push *)",
      "Bash(rm -rf *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(./secrets/**)"
    ],
    "ask": [
      "Bash(git commit *)",
      "Edit(**)",
      "WebFetch"
    ],
    "allow": [
      "Read",
      "Bash(ls *)",
      "Bash(cat *)",
      "Bash(python3 *)"
    ],
    "defaultMode": "default"
  }
}

ไวยากรณ์กฎ Bash มีจุดที่พลาดง่าย: ช่องว่างหน้า * มีความหมาย เอกสารระบุว่า "Bash(ls ) matches ls -la but not lsof, while Bash(ls) matches both" คือ Bash(git push *) จับ git push origin main แต่ไม่ไปโดนคำสั่งอื่นที่ขึ้นต้นคล้ายกัน ส่วนการล็อกโดเมนให้ดึงเว็บ ใช้รูป WebFetch(domain:example.com) และการล็อกไฟล์ลับใช้ Read(./.env) ตามที่วางไว้ในลิสต์ deny ข้างบน — จุดนี้ผูกกับเรื่องความเป็นส่วนตัวข้อมูลกับ PDPAโดยตรง เพราะการบล็อก curl/wget คือการกันไม่ให้ข้อมูลลูกค้าหลุดออกเน็ตโดยที่เราไม่ได้เห็น

ทำไมมันสำคัญกับเรา: ลิสต์ deny คือ "เส้นที่ห้ามข้าม" ของ workspace มันเป็นเอกสารที่อ่านแล้วรู้ทันทีว่าองค์กรถือว่าอะไรคือความเสี่ยงที่ยอมรับไม่ได้ การเขียนมันให้ชัดคือการทำ risk appetite ให้จับต้องได้ในระดับเครื่องมือ ไม่ใช่แค่ในนโยบายที่ไม่มีใครบังคับใช้จริง

3. ชั้นที่ 2 — PreToolUse hook: อุดช่องที่กฎ pattern จับไม่ได้

ถ้ากฎ permission ดีขนาดนี้แล้ว ทำไมยังต้องมี hook อีก? เพราะกฎแบบ pattern มีช่องโหว่ตามธรรมชาติ เอกสารเองก็เตือนว่ากฎที่พยายามคุม argument นั้นเปราะ ("Bash permission patterns that try to constrain command arguments are fragile") เช่นคำสั่งประกอบอย่าง cd /tmp && git push หรือคำสั่งที่มีตัวแปรนำหน้า อาจเล็ดลอดกฎง่าย ๆ ได้ ด่านที่สองคือ PreToolUse hook — สคริปต์ที่รันก่อน tool ทุกครั้ง อ่านคำสั่งจริงเข้ามาแล้วตัดสินใจเอง

hook รับ JSON ทาง stdin (มี tool_name กับ tool_input.command) แล้วตอบกลับเป็น JSON บอกว่าจะ allow / deny / ask ก็อปไฟล์นี้ไปวางที่ .claude/hooks/approval-gate.sh แล้ว chmod +x (ต้องมี jq ในเครื่อง)

#!/usr/bin/env bash
# .claude/hooks/approval-gate.sh — PreToolUse gate
set -euo pipefail

input=$(cat)
tool=$(printf '%s' "$input" | jq -r '.tool_name')
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty')

deny() { jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}'; exit 0; }
ask()  { jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"ask",permissionDecisionReason:$r}}'; exit 0; }

if [ "$tool" = "Bash" ]; then
  # ห้ามเด็ดขาด: ย้อนยาก + ข้อมูลรั่ว (จับแม้เป็นคำสั่งประกอบ)
  case "$cmd" in
    *"git push"*)   deny "git push ต้องอนุมัติด้วยมือ กัน push งานที่ยังไม่ตรวจ" ;;
    *"rm -rf"*)     deny "rm -rf ถูกบล็อก ใช้ mv ไป .archive/ แทน" ;;
    *curl*|*wget*)  deny "ห้ามส่งข้อมูลออกเน็ตด้วย curl/wget กัน data egress (PDPA) ใช้ WebFetch แทน" ;;
  esac
  # แตะไฟล์ข้อมูลจริง = ขออนุมัติก่อน
  case "$cmd" in
    *.csv*|*.parquet*|*.xlsx*) ask "คำสั่งแตะไฟล์ข้อมูลจริง ขออนุมัติก่อน: $cmd" ;;
  esac
fi
exit 0   # ไม่เข้าเงื่อนไข = ปล่อยให้ permission ปกติตัดสิน

เห็นความต่างจากชั้นแรกไหม — hook อ่าน สตริงคำสั่งเต็ม ด้วยตัวเอง เลยจับ something && git push ได้ทั้งที่กฎ pattern อาจพลาด และมันใส่ เหตุผล (permissionDecisionReason) กลับไปให้ Claude อ่าน เช่นบอกให้ใช้ mv ไป .archive/ แทน rm — ซึ่งตรงกับ File Deletion Protocol ที่ผมใช้จริงใน workspace การมี hook คืนเหตุผลแบบนี้ทำให้ agent ปรับพฤติกรรมถูก ไม่ใช่แค่โดนบล็อกแล้วงง

ทำไมมันสำคัญกับเรา: นี่คือหลัก defense in depth ที่คนทำ security คุ้นเคย — ชั้นเดียวพังได้เสมอ สองชั้นที่ทำงานคนละกลไก (กฎ pattern + สคริปต์อ่านคำสั่งจริง) อุดจุดบอดของกันและกัน สำหรับงานที่แตะข้อมูลลูกค้า การมีชั้นที่สองที่ "อ่านสิ่งที่จะรันจริง" ก่อนปล่อย คือส่วนต่างระหว่างของเล่นกับของที่กล้าเปิดทิ้งไว้ทำงานเอง

4. ประกอบเข้าด้วยกัน แล้วทดสอบว่ามันบล็อกจริง

ต่อ hook เข้ากับ settings.json โดยเพิ่มบล็อก hooks เข้าไปในไฟล์เดิม (ให้ตรง matcher Bash)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/approval-gate.sh" }
        ]
      }
    ]
  }
}

จากนั้นทดสอบด้วยตัวเอง อย่าเชื่อว่ามันทำงานจนกว่าจะเห็นมันบล็อก ลองสั่ง Claude ว่า "รัน git push origin main ให้หน่อย" — ควรโดน deny พร้อมเหตุผล แล้วลอง "ลบไฟล์ทั้งโฟลเดอร์ด้วย rm -rf" — ควรโดนบล็อกเช่นกัน ส่วนคำสั่งอ่านข้อมูลปกติต้องผ่านลื่น ถ้าทดสอบครบว่าเส้นสีแดงโดนบล็อกจริงและงานปกติไม่ติดขัด ก็พร้อมเปิดให้ agent ทำงานยาว ๆ ได้ ซึ่งเป็นรากฐานเดียวกับที่ผมใช้รัน Claude Code แบบ 24/7

ทำไมมันสำคัญกับเรา: control ที่ไม่เคยทดสอบ = control ที่ไม่มีอยู่จริง เป็นบทเรียนพื้นฐานของงาน validation การจงใจสั่งคำสั่งอันตรายเพื่อดูว่าด่านบล็อกจริงไหม คือ acceptance test ของ agent setup ทำครั้งเดียวตอนตั้ง แล้วสบายใจได้ยาว

ภาพรวม

การปล่อยให้ AI agent ทำงานเองไม่ใช่เรื่องของความกล้า แต่เป็นเรื่องของการวางด่านให้ถูกชั้น — permission allow/ask/deny ใน settings.json เป็นด่านกฎ (จำไว้ว่า deny ชนะ ask ชนะ allow) และ PreToolUse hook เป็นด่านที่อ่านคำสั่งจริงก่อนรัน อุดช่องที่กฎ pattern จับไม่ได้ สองไฟล์นี้ก็อปไปปรับใช้ได้ทันที และควรลองสั่งคำสั่งอันตรายเพื่อยืนยันว่ามันบล็อกจริงก่อนใช้งานจริง ทั้งหมดนี้คือ "การสาธิต 1 ชุดที่ทำงานได้" — ส่วนระบบเต็มที่ผมใช้จริง (workspace ที่ประกอบเสร็จทั้งก้อน, guard หลายชั้นสำหรับงานการเงินโดยเฉพาะ, การผูก hook เข้ากับ QC pipeline และ audit log ที่ตรวจย้อนได้) อยู่ในคอร์ส Claude Code

ที่มา

อ่านต่อ