تعديل Claude Code لمنع قراءة ملفات .env: تم اختباره (2026)
٥ أكتوبر ٢٠٢٦

تعديل لـ Claude Code لحظر عمليات قراءة ملفات .env من خلال ربط tool.call ويعيد { deny } لعمليات القراءة والتحرير والكتابة وتنفيذ الأوامر التي تلامس الملفات السرية. عبور هذا التعديل 5 من 5 اختبارات، لكنه تسرب في 5 من 14 أمر اختبار للوحة الأوامر، لذا اعتبره خط دفاعي.
ملخص
- تم إصدار التعديلات في 1 أكتوبر 2026، وتحتاج إلى Claude Code v2.1.287 أو أحدث.123
- يتطلب التعديل ثلاثة ملفات: ملف تعريف، و
hooks.jsonيشير إلى وحدة، والوحدة نفسها.3 - يسمح حدث
tool.callللتعديل برفض الاستدعاء قبل تنفيذه، ويفهم Claude نصdenyالخاص بك كنتيجة للأداة.4 - قمت بتشغيل التعديل على Claude Code 2.1.289:
claude plugin validateنجح، وclaude plugin testنفذ 5 اختبارات بدون أي فشل في أقل من ثانية. - استخدم تعبير منتظم على نص الأمر لرفض 7 من 14 أمر اختبار. من بين الـ 7 التي سُمح بها، كانت 5 تسريبات و2 سماحًا صحيحًا. ادمج التعديل مع قاعدة رفض
Readوبيئة العزل.5
ما ستتعلمه
- ما هو تعديل Claude Code والملفات التي يحتاجها
- كيفية كتابة حارس
tool.callيرفض الوصول إلى الملفات السرية - كيفية اختبار الحارس باستخدام
claude plugin testوما الذي يعيده الاستدعاء المرفوض - أين يفشل الحارس، مُقاسًا على 14 أمر واجهة سطر أوامر
- كيف يختلف عن قواعد رفض الأذونات، ولماذا التعديلات نفسها غير معزولة
ما هو تعديل Claude Code؟
تعديل Claude Code هو إضافة تحتوي على وحدة ربط: ملف JavaScript أو TypeScript تحتوي على دوال يدعوها Claude Code عند حدوث أحداث. تصف Anthropic التعديلات بأنها "دوال TypeScript صغيرة تغيّر طريقة عمل Claude Code."1 يمكنها مراقبة حدث، أو إعادة كتابته، أو الرد عنه بدلاً من Claude Code.4 تتطلب التعديلات Claude Code v2.1.287 أو أحدث، وv2.1.287 هو إدخال سجل التغييرات الذي أضافها.23
يستخدم هذا المنشور شريحة ضيقة من الميزة: حارس على استدعاءات الأدوات. إذا أردت صورة أوسع لسلسلة توريد الإضافات أولًا، اقرأ تحليلنا لمخاطر إضافة Plugin4Shell لوكيل البرمجة.
كيف تبني تعديل Claude Code لحظر قراءات .env؟
تكتب ثلاثة ملفات. يُسمّي ملف التعريف التعديل، و hooks/hooks.json يسرد مسار وحدة واحدة تحت modules (هذا المفتاح هو ما يجعل الإضافة تعديلًا)، والوحدة تصدّر register.3
احفظ هذا كـ secret-guard/.claude-plugin/plugin.json:
{
"name": "secret-guard",
"version": "0.1.0",
"description": "Denies Read/Edit/Write/Bash tool calls that touch .env files or private keys.",
"author": { "name": "NerdLevelTech" }
}
احفظ هذا كـ secret-guard/hooks/hooks.json:
{ "modules": ["./register.ts"] }
احفظ هذا كـ secret-guard/hooks/register.ts:
import type { Register } from 'claude-code'
// Files a coding agent should not read or write: .env, .env.local, .envrc, id_rsa, id_ed25519, *.pem
const SECRET_PATH = /(^|\/)(\.env(rc|\..+)?|id_(rsa|ed25519)|[^/]+\.pem)$/
// The same names, found inside a shell command string
const SECRET_IN_COMMAND = /(^|[\s"'=\/])(\.env(rc|\.[\w.-]+)?|id_(rsa|ed25519)|[\w.-]+\.pem)(?=$|[\s"';|&)])/
export const register: Register = on => {
// An array matcher covers all three file tools with one hook
on('tool.call', { tool: ['Read', 'Edit', 'Write'] }, ($, e, next) =>
SECRET_PATH.test(e.file_path)
? { deny: `${$.plugin.name}: ${e.file_path} is a protected secret file. Ask the user for the variable names or non-secret values you need.` }
: next(e),
)
on('tool.call', { tool: 'Bash' }, ($, e, next) =>
SECRET_IN_COMMAND.test(e.command)
? { deny: `${$.plugin.name}: this command touches a protected secret file. Ask the user for the variable names or non-secret values you need.` }
: next(e),
)
}
توجد ثلاثة تفاصيل مستمدة مباشرة من وثائق Anthropic. يمكن أن يكون حقل المطابقة قيمة، أو مصفوفة من القيم، أو تعبيرًا منتظمًا، لذا فإن وصلة واحدة تغطي ثلاثة أدوات.4 tool.call تُفعّل لكل استدعاء أداة، بما في ذلك الاستدعاءات التي يقوم بها وكيل فرعي والاستدعاءات إلى أدوات MCP.4 وبما أن Claude تقرأ نص deny كنتيجة الأداة، تنص الوثائق على كتابته كتعليمات يمكن لـ Claude تنفيذها، وهذا هو السبب في أن نسختنا تطلب من الوكيل طلب أسماء المتغيرات أو القيم غير السرية من المستخدم.4
كيف تختبر تعديل Claude Code؟
شغّل claude plugin test من مجلد التعديل. إنه يُشغّل ملفاتك *.test.ts بدون جلسة أو تسجيل دخول أو شبكة، وينتهي بحالة 1 عند فشل أي اختبار، لذا فهو يعمل في أنظمة CI.6 كل اختبار يستدعي $.tool.call(...)، والذي يرسل الاستدعاء عبر وصلاتك، ويجيب عن طريق وحدة اختبار مسجلة باستخدام on بدلاً من Claude Code.6
احفظ هذا كـ secret-guard/tests/secret-guard.test.ts:
import { test, expect } from 'claude-code/testing'
test('denies Read of .env', async $ => {
const r = await $.tool.call({ tool: 'Read', file_path: '/repo/.env' })
expect(r.deny).toContain('protected secret file')
})
test('denies Bash cat .env.local', async $ => {
const r = await $.tool.call({ tool: 'Bash', command: 'cat .env.local | head' })
expect(r.deny).toContain('protected secret file')
})
test('lets Read of README.md through the plugin', async ($, on) => {
// Answer in Claude Code's place, so a call the guard passes on has somewhere to land
on('tool.call', () => ({ result: 'ok' }))
const r = await $.tool.call({ tool: 'Read', file_path: '/repo/README.md' })
expect(r).toEqual({ result: 'ok' })
})
test('does not match env-like words in ordinary commands', async ($, on) => {
on('tool.call', () => ({ result: 'ok' }))
const r = await $.tool.call({ tool: 'Bash', command: 'echo environment.txt' })
expect(r).toEqual({ result: 'ok' })
})
test('denies Write of a private key and Bash that reads it', async $ => {
const w = await $.tool.call({ tool: 'Write', file_path: '/home/u/.ssh/id_ed25519', content: 'x' })
expect(w.deny).toBeDefined()
const b = await $.tool.call({ tool: 'Bash', command: 'openssl x509 -in server.pem' })
expect(b.deny).toBeDefined()
})
يُسجّل اختبارا السماح وحدة الاختبار المستخدمة في الوثائق، on('tool.call', () => ({ result: 'ok' }))، بحيث يكون للاستدعاء الذي يمرره الحارس مكان ينتهي إليه.6 ثم يُظهر toEqual({ result: 'ok' }) أن الاستدعاء وصل إلى وحدة الاختبار. لا تحتاج اختبارات الرفض إلى وحدة اختبار، لأن الحارس يجيب أولًا.
ما الذي شغلته
شغلت هذه الأوامر على Claude Code 2.1.289 (5 أكتوبر 2026، في بيئة سحابية معزولة). claude plugin validate ./secret-guard طبّع الوصلات واستدعاءات $ التي وجدتها في الوحدة ونجحت. يحتوي المانيفست على حقل author لأنه بدونه تنجح الأوامر لكنها تنذر بـ "لم يتم توفير معلومات المؤلف".
Validating plugin manifest: /tmp/mods/secret-guard/.claude-plugin/plugin.json
Validating hooks: /tmp/mods/secret-guard/hooks/hooks.json
❯ ./register.ts hooks: tool.call{tool=Read|Edit|Write}, tool.call{tool=Bash}
❯ ./register.ts calls: nothing on $
✔ Validation passed
ثم طبّع claude plugin test، عند تشغيله داخل مجلد secret-guard:
tests/secret-guard.test.ts:
(pass) denies Read of .env [38.80ms]
(pass) denies Bash cat .env.local [12.92ms]
(pass) lets Read of README.md through the plugin [15.21ms]
(pass) does not match env-like words in ordinary commands [13.25ms]
(pass) denies Write of a private key and Bash that reads it [13.50ms]
5 pass
0 fail
Ran 5 tests across 1 file. [0.23s]

الطريقة: claude --version، claude plugin validate ./secret-guard و claude plugin test، تم التقاطها في 5 أكتوبر 2026 باستخدام TERM=xterm-256color، ثم أُعيدت تشكيلها كصورة من النص الملتقط. تختلف أوقات الاختبار من تشغيل إلى آخر.
السطر calls: nothing on $ هو الجزء المفيد للمراجعين. تُخبر دليل مسؤولي Anthropic المراجعين بقراءة هذا السطر لمعرفة ما يمكن للتعديل الوصول إليه، بما في ذلك الملفات والعمليات والشبكة، وهذا الحارس لا يستدعي أيًا منها.7
جميع النتائج في هذا المنشور مستمدة من هذا النظام الاختباري ومن اختبار تجريبي أرسل استدعاءات عبر وصلات التعديل. لم أحمّل التعديل في جلسة حية لـ Claude Code. لتجربته في جلسة حية، ابدأ Claude Code باستخدام claude --plugin-dir ./secret-guard، والذي يحمّل دليل الإضافات لجلسة واحدة دون تثبيته.3
فشل اختبار يستحق المعرفة
أكدت مسودتي الأولى أن r.isError كانت true عند رفض الاستدعاء، وفشلت كلتا تجربتي الرفض بـ Received: undefined. وأظهرت طباعة النتيجة السبب: أن استدعاء $.tool.call المرفوض يتم حله إلى كائن مفتاحه الوحيد هو deny.
{"deny":"secret-guard: /repo/.env is a protected secret file. Ask the user for the variable names or non-secret values you need."}
قم بعمل Assert على deny. هذا ما لاحظته في الإصدار 2.1.289، لذا أعد التحقق من ذلك إذا كان إصدارك مختلفاً.
أين يفشل حارس الـ regex؟
يفشل في أي مكان يمكن للـ shell فيه بناء اسم ملف لا يراه الـ regex أبداً. لقد أرسلت 14 أمراً من أوامر Bash عبر الـ mod باستخدام $.tool.call في اختبار تجريبي، منفصل عن الاختبارات الخمسة المذكورة أعلاه، وسجلت ما إذا كان deny قد عاد:
| الأمر | النتيجة | صحيح؟ |
|---|---|---|
cat .env, cat ./.env, cat .env.local | head, cp .env /tmp/x, source .envrc, cat "$PWD/.env" | مرفوض | نعم |
python3 -c "print(open('.env').read())" | مرفوض | نعم |
cat .e""nv | مسموح | تسريب |
cat $(printf ".en%s" v) | مسموح | تسريب |
cat .en* | مسموح | تسريب |
cat *.pem | مسموح | تسريب |
tar czf a.tgz . | مسموح | تسريب (يؤرشف .env) |
echo environment.txt, ls .envoy | مسموح | نعم |
تم رفض سبعة أوامر، وتسريب خمسة، والسماح باثنين بشكل صحيح. توضح وثائق Anthropic نفسها نفس النقطة بخصوص الـ hook الخاص بنص الأمر. فبعد مثال لـ hook من نوع tool.check يرفض git push عندما يكون الفرع هو main، يقولون: "الـ hook يطابق نص الأمر، لذا تعامل معه كمجرد تذكير لـ Claude."4 إن الـ mod الذي يقرأ سلاسل الأوامر هو مجرد "مطب" لعامل ذكي (agent) قد يدخل إلى .env بالصدفة، وليس حماية ضد عامل تم حقنه بـ prompt (prompt-injected) للالتفاف عليه.
كما أن الحارس ضيق في ما يراقبه، وقد أظهر التشغيل التجريبي نفسه ذلك. كلا النمطين يرفضان .env.example و .env.sample، واللذان يحتويان عادةً على نصوص مؤقتة وليس أسراراً. بينما يسمحان بمرور .npmrc و ~/.aws/credentials و server.key وأيضاً .ENV، وهو نفس الملف في أنظمة الملفات التي لا تفرق بين الحروف الكبيرة والصغيرة. أما أمر grep -r API_KEY . فيمر لأن اسم الملف لا يذكر أبداً، وهي ثغرة تصفها أيضاً وثائق الأذونات لقواعد الرفض.5 ولأن الـ hooks تطابق فقط Read و Edit و Write و Bash، فإن استدعاء Grep بـ path: '/repo/.env'، واستدعاء Glob، واستدعاء NotebookEdit، واستدعاء لأداة تسمى mcp__filesystem__read_file قد مرت جميعها مباشرة.
Mod، أو قاعدة رفض، أو Sandbox: أيهم يجب أن تستخدم؟
استخدم الثلاثة معاً للأسرار. فهي تفشل في أماكن مختلفة.
| الطبقة | ما الذي تفحصه | نقطة الضعف |
|---|---|---|
قاعدة منع Read(./.env) | المسار في أدوات الملفات الخاصة بـ Claude وفي أوامر ملفات Bash مثل cat و head5 | لا تنطبق على أمر يقرأ الملفات دون تسميتها، مثل grep -r pattern .، أو على سكربت يفتح الملفات بنفسه. تغطية Grep و Glob هي "بذل قصارى الجهد"5 |
حماية Mod tool.call | كل ما يفحصه الكود الخاص بك، بما في ذلك نصوص Bash | تعبيرات regex لسلسلة الأوامر (5 تسريبات أعلاه) وكل أداة لا يتم ربطها |
| Sandbox | قيود على مستوى نظام التشغيل لما يمكن أن تصل إليه أوامر shell والعمليات التابعة لها5 | تنطبق فقط على Bash و PowerShell و Monitor5 |
بالنسبة لحظر مسار بسيط، فإن قاعدة أذونات مثل Read(./.env) لا تحتاج إلى كود.5 يكتسب الـ mod أهميته عندما تريد رسالة رفض تخبر Claude بما يجب فعله تالياً، أو سطر سجل في النص، أو منطق لا يمكن لقاعدة ثابتة التعبير عنه.4 يوضح درس الموافقة البشرية (human-in-the-loop) لـ Claude Agent SDK المقابل من جهة الـ SDK: وهو PreToolUse hook يحظر بشكل صارم الكتابة في مسارات الأسرار، مع وجود مطالبة موافقة canUseTool خلفها.
هل مودات Claude Code معزولة (sandboxed)؟
لا. يقول إعلان Anthropic: "تعمل المودات بنفس صلاحيات الوصول إلى جهازك التي يمتلكها Claude Code نفسه. إنها ليست معزولة، ويجب عليك فقط تثبيت المودات من مصادر تثق بها، بنفس الطريقة التي تثبت بها أي كود على جهاز الكمبيوتر الخاص بك."1 تترتب على ذلك ثلاث نتائج لحماية مثل هذه:
- الـ hook الذي يفشل يفتح الوصول افتراضياً. إذا تسبب الـ hook في خطأ أو انتهى وقته قبل استدعاء
next، يتخطاه Claude Code ويستمر الاستدعاء كما لو أن الحماية غير موجودة. قم بربط.catchلإرجاع{ deny }وإغلاق الوصول عند الفشل.4 كما أن للـ hooks حداً زمنياً قدره 10 ثوانٍ لوقت تشغيلها.8 لم أختبر مسار.catchفي هذه التجربة. - قواعد المنع تعلو فوق موافقة الـ mod، مع وجود ثغرة واحدة. في الأجهزة التي يتم فيها تحميل حماية
sec-defaultالمدمجة، لا يمكن لمود المستخدم الموافقة على استدعاء ترفضه قاعدةdeny. هذا يغطي استدعاءات أدوات Claude، وليس وصول الـ mod نفسه إلى الملفات: تذكر الوثائق أنه مع منعRead(.env)، لا يزال بإمكان الـ mod قراءة هذا الملف باستخدام$.fs.read.7 - الحماية المدمجة مشروطة. يتم تحميلها عندما يكون للجهاز إعدادات مدارة أو عندما يكون المستخدم مسجلاً دخوله في خطة Team أو Enterprise.7
إذا قمت بتثبيت مودات (mods) من طرف ثالث، قم بتشغيل claude plugin validate واقرأ سطر calls: أولاً.7 وبالنسبة لخطر متعلق، وهو قيام خادم MCP بتغيير تعريفات الأدوات الخاصة به بعد موافقتك عليها، راجع مقالنا حول اكتشاف انحراف أدوات MCP (MCP tool drift).
الخلاصة
يوفر لك مود tool.call حماية رخيصة وقابلة للاختبار: خمسة اختبارات نجحت في أقل من ثانية، ورسالة الرفض توجه العميل نحو سؤالك بدلاً من ذلك. لكن حدودها حقيقية، لأن 5 من أصل 14 أمراً تجريبياً قد نفذت. تعامل مع المود كطبقة ودية، وحافظ على قاعدة المنع والعزل (sandbox) أسفلها، واقرأ سطر calls: لأي مود من طرف ثالث قبل تثبيته.
Footnotes
-
Anthropic, "Customize Claude Code with mods in TypeScript," October 1, 2026 (the "small TypeScript functions" description, the "available today" note and the "aren't sandboxed" warning). https://claude.com/blog/claude-code-mods (fetched 2026-10-05) ↩ ↩2 ↩3
-
Claude Code Docs, "Claude Code changelog," version 2.1.287 (October 1, 2026): "Added Claude Mods: plugins may now modify deeper behavior". https://code.claude.com/docs/en/changelog (fetched 2026-10-05) ↩ ↩2
-
Claude Code Docs, "Create a mod" (version requirement, the three files, the
moduleskey, no build step). https://code.claude.com/docs/en/plugins/mods/create (fetched 2026-10-05) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
Claude Code Docs, "React to events with a mod" (
tool.call, matchers, thedenytext, fail-open behavior and.catch, thetool.checkcaveat,$.ui.log). https://code.claude.com/docs/en/plugins/mods/events (fetched 2026-10-05) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 -
Claude Code Docs, "Configure permissions" (
Read(./.env), what Read deny rules cover, best-effort Read coverage, sandboxing scope). https://code.claude.com/docs/en/permissions (fetched 2026-10-05) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Claude Code Docs, "Test a mod" (
claude plugin test, the test kit,onstubs, exit status). https://code.claude.com/docs/en/plugins/mods/test (fetched 2026-10-05) ↩ ↩2 ↩3 ↩4 -
Claude Code Docs, "Manage mods for your organization" (
sec-default@builtin, deny-rule precedence, the$.fs.readcaveat, reviewingcalls:). https://code.claude.com/docs/en/plugins/mods/admin (fetched 2026-10-05) ↩ ↩2 ↩3 ↩4 -
Claude Code Docs, "Use the mods API" (10-second limit on a hook's own running time). https://code.claude.com/docs/en/plugins/mods/api (fetched 2026-10-05) ↩



