کلاد کد از نسخه‌ی 2.1.287 یک نوع تازه‌ی پلاگین به نام mod دارد: کدی که داخل خود کلاد کد اجرا می‌شود، به فراخوانی ابزارها دسترسی دارد و می‌تواند آن‌ها را پیش از اجرا رد کند. در این پست یک mod واقعی می‌سازیم که فرمان‌های مخرب را رد می‌کند، الگوهایش را با node تست می‌کنیم و می‌بینیم چه چیزی در این مسیر قابل اجرا بود و چه چیزی نبود.

mod با قلاب تنظیمات چه فرقی دارد

کلاد کد پیش از این یک راه داشت تا جلوی یک ابزار را بگیری: قلاب PreToolUse در فایل تنظیمات. آن قلاب یک فرمان شل است که JSON را روی ورودی استاندارد می‌گیرد و با کد خروج ۲ کار را متوقف می‌کند. تفاوت mod در این است که کدش داخل خود کلاد کد اجرا می‌شود، نه بیرون از آن.

نتیجه‌ی این تفاوت سه کاری است که قلاب تنظیمات از پسش برنمی‌آید. mod می‌تواند رابط کاربری بکشد؛ یعنی یک پنجره کنار transcript با دکمه و فیلد بسازد. می‌تواند خودِ فراخوانی ابزار را عوض کند یا بدون اجرای ابزار جوابش را بدهد. و چون همه‌ی قلاب‌های یک mod متغیرهای مشترک دارند، یکی می‌تواند داده جمع کند و دیگری همان داده را نشان دهد.

کارقلاب تنظیماتmod
رد کردن یک ابزاربله، با کد خروج ۲بله، با { deny }
کشیدن رابط کاربرینهبله، با ui.render
حفظ داده میان قلاب‌هانه، هر فرمان جداستبله، متغیر مشترک
تست بدون نشست کلادنهبله، با claude plugin test

اگر کارت با قلاب حل می‌شود، سراغ mod نرو. mod ابزار تازه‌تر و سنگین‌تری است و کدی است که با مجوزهای خودت اجرا می‌شود. برای نمونه‌ی عملی قلاب تنظیمات، بستن قلاب PreToolUse جلوی خواندن فایل اسرار را نوشته‌ام که همان لایه‌ی بیرونی را با یک اسکریپت کوچک می‌بندد.

ساختار: سه فایل، بدون build

یک mod یک پلاگین است با یک فایل اضافه. پلاگین معمولی سه فایل دارد و mod دقیقا همان سه فایل را دارد؛ چیزی که آن را mod می‌کند وجود کلید modules در فایل دوم است. نه به Node نیاز داری، نه به bundler، نه به مرحله‌ی build.

# اول کلاد کد را به آخرین نسخه برسان
npm install -g @anthropic-ai/claude-code@latest

# بعد پوشه‌ی پلاگین و دو زیرپوشه‌ی لازم
mkdir -p bash-guard/.claude-plugin bash-guard/hooks

# نشانه‌ی mod بودن همین کلید modules است
cat > bash-guard/hooks/hooks.json <<'JSON'
{
  "description": "The bash-guard hooks module",
  "modules": ["./register.js"]
}
JSON

مانیفست هیچ فیلد ویژه‌ای برای mod ندارد. فقط نام، نسخه و توضیح لازم است. یک نکته‌ی عملی: claude plugin validate نامی که شبیه نام‌های خود آنتروپیک باشد را رد می‌کند، پس نامی مثل claude-guard انتخاب نکن.

قلاب ردکننده و قانون fail-closed

مهم‌ترین تکه این است: در قلاب tool.call نام ابزار در e.tool است و آرگومان‌هایش فیلدهای مستقیم همان e هستند. برای ابزار Bash یعنی فرمان شل در e.command است، نه در e.input.command. اگر این را اشتباه بگیری، قلابت بی‌صدا همه‌چیز را رد می‌کند، چون رشته‌ی خالی هیچ الگویی را نمی‌خورد.

// دستورهای مخرب، با لنگر به ابتدای رشته تا "echo rm -rf /" رد نشود
const DESTRUCTIVE = [
  /^\s*rm\s+(-[a-zA-Z]+\s+)*-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s/,
  /^\s*rm\s+(-[a-zA-Z]+\s+)*-[a-zA-Z]*f[a-zA-Z]*r[a-zA-Z]*\s/,
  /^\s*git\s+push\b.*\s--force(\s|$)/,
  /^\s*git\s+reset\s+--hard\b/,
  /^\s*git\s+clean\b.*-[a-zA-Z]*f/,
  /^\s*truncate\s+-s\s*0\s/,
  /^\s*dd\s+[^\n]*of=\/dev\/sd[a-z]/,
]

// قلاب اصلی؛ جدا نوشته شده تا catch بتواند جایش بنشیند
function guard($, e, next) {
  // رشته‌ی خالی یعنی این رویداد اصلا Bash نیست
  const command = e.command || ''

  // هر چیزی که نظری نداریم بی‌سروصدا رد می‌شود
  if (!isDestructive(command)) return next(e)

  refusals.push(command)

  // برگشتن بدون next یعنی پاسخ دادن به رویداد، پس فرمان اجرا نمی‌شود
  return { deny: 'bash-guard refused this command. Ask the user to confirm it first.' }
}

نکته‌ی ظریف اینجاست: وقتی next را صدا نمی‌زنی، نه فرمان اجرا می‌شود و نه حتی پنجره‌ی مجوز به کاربر نشان داده می‌شود. پس این راه یک لایه جلوتر از قلاب تنظیمات است.

وقتی خود قلاب خراب می‌شود

اگر قلابت پرتاب کند یا تایم‌اوت بخورد، رفتار پیش‌فرض این است که کلاد کد آن قلاب را نادیده می‌گیرد و فرمان را اجرا می‌کند. برای یک نگهبان امنیتی این دقیقا برعکس چیزی است که می‌خواهی. راه‌حل بستن قلاب با catch است.

// on یک registration برمی‌گرداند و catch فقط به همین یک قلاب می‌چسبد
on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
  // next.error.kind یا throw است یا timeout
  return { deny: 'bash-guard failed (' + next.error.kind + '), so the command was not run.' }
})

یک نکته‌ی دیگر که در تست دیدم: اگر دو بار on('session.start') را بدون matcher صدا بزنی، ماژول اصلا لود نمی‌شود و پیام خطا می‌گیری. همه‌ی کارِ session start را در یک قلاب جمع کن.

تست الگوها بدون نشست کلاد

کلاد کد برای تست یک تست‌کیت جدا دارد که با claude plugin test اجرا می‌شود و هیچ نشست، ورود یا شبکه‌ای لازم ندارد. چون الگوهای منطقی‌اند، می‌شود همان تابع را با node خالص تست کرد، و این کار را می‌کنم تا خروجی واقعی داشته باشیم.

$ node pattern-test.mjs
pass  false  ls -la                         -> refused=false
pass  false  echo rm -rf / is dangerous     -> refused=false
pass  false  git status                     -> refused=false
pass  true   rm -rf /tmp/demo               -> refused=true
pass  true   rm -fr ./build                 -> refused=true
pass  true   git push --force origin main   -> refused=true
pass  true   git reset --hard HEAD~1        -> refused=true
pass  true   git clean -fdx                 -> refused=true
pass  true   truncate -s 0 access.log       -> refused=true

0 failed, 9 passed, 9 total

توضیح خط‌به‌خط خروجی: سه خط اول دستورهای بی‌خطرند و باید رد نشوند. خط چهارم نکته‌ی اصلی تست است؛ echo rm -rf / is dangerous فقط متن را چاپ می‌کند و نباید رد شود، و چون الگوها لنگر دارند رد نمی‌شود. اگر لنگرها را برداری، همین خط قرمز می‌شود. پنج خط بعدی دستورهایی هستند که باید گرفته شوند.

تستی که فقط PASS چاپ می‌کند هیچ چیزی را ثابت نمی‌کند. برای اطمینان، یک الگو را عمدا حذف کردم و دوباره تست را اجرا کردم:

$ python3 negative_test.py
--- output of the broken mod ---
FAIL  true   git clean -fdx                 -> refused=false
...
1 failed, 8 passed, 9 total
--- verdict ---
PASS: the harness caught the missing pattern (exit 1)

چه چیزی را نتوانستم اجرا کنم

بخشی از این مسیر روی این سرور اجرا نشد و باید صادقانه بگویم کدام است. فرمان‌های claude --help، claude plugin validate و claude plugin test روی این ماشین هیچ خروجی‌ای نمی‌دهند و در ۱۰۰ درصد CPU می‌مانند. دلیلش کمبود حافظه و نبود دسترسی سخت‌افزاری است.

بررسینتیجهپایه
claude --version2.1.292خروجی فرمان
claude --helpهنگ کردن۴۰ ثانیه بی‌خروجی
claude plugin validate .هنگ کردن۲۰ ثانیه، ۱۰۰٪ CPU
claude plugin testهنگ کردن۷ دقیقه بی‌خروجی
تست الگو با node۹ از ۹ قبولخروجی واقعی اجرا

پرچم‌های CPU این ماشین فقط تا SSE2 می‌رسد و AVX یا AVX2 ندارد، و حافظه‌ی کل ۱ گیگابایت با یک هسته است. باینری بومی کلاد کد روی چنین پردازنده‌ای به یک حلقه‌ی بی‌پایان می‌افتد. برای همین تست الگوها را به node سپردم، ولی خودِ قلاب‌ها در یک نشست واقعی کلاد کد آزمایش نشده‌اند.

بارگذاری mod و چهار دقیقه‌ای که باید بگذرانی

برای آزمایش بدون نصب، از پرچم --plugin-dir استفاده کن که پلاگین را فقط برای یک نشست بار می‌کند. برای استفاده‌ی دائمی، mod را به یک مارکت‌پلیس اضافه کن و با /plugin install name@marketplace نصبش کن. نکته‌ای که مستندات تازه اضافه کرده: کلاد کد پلاگین نصب‌شده را با نسخه کش می‌کند، پس ویرایش فایل‌های نسخه‌ی نصب‌شده بی‌اثر است تا نسخه را بالا ببری و دوباره نصب کنی.

# فقط برای همین یک نشست، بدون نصب
$ claude --plugin-dir ./bash-guard

# بعد در خط فرمان کلاد کد، فهرست رد شده‌ها را ببین
> /audit

# بررسی اینکه کلاد کد چه چیزی از mod خوانده
$ claude plugin validate ./bash-guard

اگر mod کار نکرد، اول /plugin را باز کن و تب Installed را ببین؛ آنجا باید یک خط مثل 1 mod active · bash-guard ببینی. اگر آن خط نیست، ماژول لود نشده است. اگر هست ولی فرمان‌ها رد نمی‌شوند، تقریبا همیشه یعنی نام فیلد را اشتباه گرفته‌ای: e.command درست است و e.input.command غلط.

آنتروپیک برای همین خانواده از قلاب‌ها یک mod با نام sec-default دارد که به‌عنوان نگهبان پیش‌فرض لود می‌شود، پس اگر روی یک پلن تیم یا سازمانی باشی ممکن است قلابت را قبل از رسیدن به کاربر ببیند. این را در تست کردن نگه دار.

منابع

  1. Claude Code changelog — نسخه‌ی 2.1.292، ۶ اکتبر ۲۰۲۶
  2. Mods overview — تفاوت mod با قلاب، اسکیل و MCP
  3. Create a mod — ساخت سه فایل و جریان تست
  4. React to events with a mod — شکل رویدادها، e.command و $.ui.ask
  5. Test a mod — تست‌کیت claude-code/testing و قواعدش
  6. Mods reference — رویدادها، متدها و فرمان‌ها
  7. Draw in the interface with a mod — ساخت پنجره و $.state
  8. Use the mods API — افزودن فرمان و ابزار
  9. Hooks reference — قلاب‌های تنظیمات و کد خروج ۲
  10. Plugins overview — ساختار پلاگین
  11. مخزن claude-code روی گیت‌هاب — سورس modهای درون‌ساخت