From fbbfa3a2d4da9a8e7d8fd911c47f3d8072ec2454 Mon Sep 17 00:00:00 2001 From: Hamza Ayed Date: Sat, 3 Oct 2026 13:00:28 +0300 Subject: [PATCH] Scan staged workspace snapshots for secrets --- SovereignAI-Starter/ROADMAP.md | 2 +- SovereignAI-Starter/app/execution_snapshot.py | 33 +++++++++++++++++++ .../tests/test_execution_snapshot.py | 24 ++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/SovereignAI-Starter/ROADMAP.md b/SovereignAI-Starter/ROADMAP.md index f74b140..f8d63e1 100644 --- a/SovereignAI-Starter/ROADMAP.md +++ b/SovereignAI-Starter/ROADMAP.md @@ -98,7 +98,7 @@ 3. [x] اختيار مساحة العمل/الملف من نافذة التطبيق واستعراض قائمة الملفات قبل سؤال الوكيل. تعرض الواجهة الملفات النصية المدعومة وتسمح بتحديد 3 كحد أقصى؛ يرسل التطبيق المسارات النسبية، ويتحقق الخادم منها داخل المجلد المحدد. فُحص endpoint حيًا واستُبعد `.env`؛ ولا يرسل قائمة الملفات أو محتواها للتخزين. 4. [x] كتابة مضبوطة: إنشاء وتعديل ملفات داخل مساحة العمل فقط، مع معاينة diff وتأكيد المستخدم قبل التطبيق. (2026-10-02: لا كتابة عند المعاينة؛ الرمز مؤقت ولمرة واحدة، وفحص المسار والبصمة يعاد قبل التطبيق؛ اجتازت اختبارات Python واختبارات واجهة Flutter، وأُعيد تشغيل Windows Debug وFastAPI بالتغييرات.) 5. أوامر تطوير: تشغيل أوامر محددة في بيئة معزولة وبمهلة وحدود موارد، ومع موافقة لكل أمر في البداية. (2026-10-03: تحقق Windows 10 Pro 19045، Intel i7-6600U مع virtualization firmware مفعّل، RAM 15.9GB والمتاح وقت القياس 5.2GB، و40.8GB مساحة فارغة على C:. لا يوجد `WindowsSandbox.exe` أو Docker. استعلاما WSL أعادا شاشة المساعدة فلم يثبتا توفر توزيعة. فحص Windows Sandbox يحتاج مسؤولًا؛ محاولة DISM مرتفعة الصلاحية انتهت بخطأ `0xc0000142` ولم تغيّر إعدادًا. أُعدّ prototype محلي بـAppContainer وJob Object (`scripts/appcontainer_probe.cpp`): 512 MiB، حد 8 عمليات، مهلة 30 ثانية، وإنهاء شجرة العمليات عند إغلاق الـJob. في تشغيل Windows بأذونات مناسبة نجح smoke test: `cmd.exe` عمل داخل الحاوية؛ مُنع من قراءة ملف Temp للمضيف ومن إنشاء ملف خارجه، بينما نجح في الكتابة والقراءة من مجلد العمل المعزول. نُسخ `README.md` من المشروع إلى الحاوية ثم استخدم `curl.exe file://` لنسخه منها؛ النسخة تطابقت بايتًا ببايت. اختُبر `curl.exe` داخل الحاوية (`--version` exit 0)، وفشل الوصول إلى `/health` على `127.0.0.1:8100` بمهلة curl 28 رغم نجاح endpoint من المضيف؛ هذا فحص اتصال محلي فقط، وليس اختبارًا للإنترنت العام. إعداد AppContainer بلا قدرات شبكية. الجلسة المقيدة لدى Codex فشلت في إنشاء الملف الشخصي بـ`0x80070005`، بينما نجح الفاحص عبر جلسة التنفيذ المسموحة؛ يحتوي `scripts/run_appcontainer_probe.ps1` على build وتشغيل وتنظيف مؤقت قابل للتكرار. ما زال هذا prototype غير مدمج في الوكيل ولا توجد أوامر عامة قابلة للتنفيذ. التالي: تجربة نسخ ملفات محددة وآمنة من مساحة يختارها المستخدم مع حدود حجم واستثناء الأسرار والروابط الرمزية، ثم إرجاع المخرجات/diff والتحقق من الموارد والمهلة، وبعدها دمج قائمة أوامر مسموحة وموافقة صريحة في API والواجهة. [AppContainer isolation](https://learn.microsoft.com/en-us/windows/win32/secauthz/appcontainer-isolation)، [تنفيذ AppContainer](https://learn.microsoft.com/en-us/windows/win32/secauthz/implementing-an-appcontainer)، [Job Objects](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects).) - - [x] تجهيز Snapshot محدود لملفات يختارها المستخدم (`app/execution_snapshot.py`): يفرض جذر workspace المعتمد للحساب، حتى 50 ملفًا، 512KB لكل ملف و10MB إجماليًا، يقبل الامتدادات المدعومة فقط، ويرفض المسارات المخفية/المستثناة/الخارجة وأسماء الملفات ذات مؤشرات الأسرار والروابط الرمزية وأسماء أجهزة Windows المحجوزة؛ ينسخ إلى مجلد مؤقت مع SHA-256 لكل ملف. اختبارات الحدود والمسارات والروابط: 5 ناجحة. اختبارات تغيير Workspace المرتبطة: 7 ناجحة (2026-10-03). هذا staging host-side مثبت وحده ولم يُوصل بعد إلى AppContainer أو API؛ حدّ اكتماله هو تجهيز النسخة فقط. + - [x] تجهيز Snapshot محدود لملفات يختارها المستخدم (`app/execution_snapshot.py`): يفرض جذر workspace المعتمد للحساب، حتى 50 ملفًا، 512KB لكل ملف و10MB إجماليًا، ويقبل الامتدادات المدعومة فقط. يرفض المسارات المخفية/المستثناة/الخارجة، والروابط الرمزية، وأسماء أجهزة Windows المحجوزة، وأسماء الملفات/المحتوى التي تكشف مفاتيح معروفة أو قيم اعتماد مباشرة؛ وينسخ إلى مجلد مؤقت مع SHA-256 لكل ملف. 7 اختبارات snapshot و7 اختبارات workspace ناجحة (2026-10-03). فحص الأسرار محافظ وليس ماسحًا شاملًا ولا يغني عن المراجعة. هذا staging host-side مثبت وحده ولم يُوصل بعد إلى AppContainer أو API؛ حدّ اكتماله هو تجهيز النسخة فقط. 6. لا وصول عام إلى القرص، ولا أوامر مدمرة أو نشر خارجي دون موافقة صريحة. كل أداة لها مخطط مدخلات ومخرجات واختبارات وسجل تدقيق. ### كودكس للبرمجة diff --git a/SovereignAI-Starter/app/execution_snapshot.py b/SovereignAI-Starter/app/execution_snapshot.py index 1ea0c04..b818778 100644 --- a/SovereignAI-Starter/app/execution_snapshot.py +++ b/SovereignAI-Starter/app/execution_snapshot.py @@ -26,6 +26,23 @@ _WINDOWS_RESERVED_NAMES = {"CON", "PRN", "AUX", "NUL"} | { for prefix in ("COM", "LPT") for number in range(1, 10) } +_KNOWN_SECRET = re.compile( + r"gsk_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|" + r"gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|" + r"AKIA[0-9A-Z]{16}|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----" +) +_SECRET_ASSIGNMENT = re.compile( + r"(?im)\b(?:api[_-]?key|secret(?:[_-]?key)?|password|" + r"access[_-]?token|auth[_-]?token|private[_-]?key)\b\s*[:=]\s*" + r"[\"'](?P[^\"'\r\n]{12,})[\"']" +) +_PLACEHOLDER_VALUES = { + "your_api_key_here", + "your-secret-here", + "replace-me", + "changeme", + "placeholder", +} @dataclass @@ -54,6 +71,18 @@ def _is_reparse_point(path: Path) -> bool: return bool(is_junction()) if is_junction is not None else False +def _contains_credential(content: bytes) -> bool: + text = content.decode("utf-8", errors="replace") + if _KNOWN_SECRET.search(text): + return True + for match in _SECRET_ASSIGNMENT.finditer(text): + value = match.group("value").strip() + if value.casefold() in _PLACEHOLDER_VALUES or value.startswith(("$", "${", "<")): + continue + return True + return False + + def _safe_relative_path(root: Path, value: str) -> tuple[Path, str]: if ( not isinstance(value, str) @@ -152,6 +181,10 @@ def stage_selected_files( content = input_file.read(MAX_SNAPSHOT_FILE_BYTES + 1) if len(content) > MAX_SNAPSHOT_FILE_BYTES or len(content) != expected.st_size: raise ValueError("تغير حجم الملف أثناء تجهيز نسخة التنفيذ؛ أعد المحاولة.") + if _contains_credential(content): + raise ValueError( + f"الملف {relative} يبدو أنه يحتوي على مفتاح أو قيمة اعتماد؛ لم تتم إضافته." + ) latest = source.stat() if (latest.st_dev, latest.st_ino, latest.st_size) != ( expected.st_dev, diff --git a/SovereignAI-Starter/tests/test_execution_snapshot.py b/SovereignAI-Starter/tests/test_execution_snapshot.py index 88bf82e..4a85f1f 100644 --- a/SovereignAI-Starter/tests/test_execution_snapshot.py +++ b/SovereignAI-Starter/tests/test_execution_snapshot.py @@ -64,6 +64,30 @@ class ExecutionSnapshotTests(unittest.TestCase): with self.subTest(path=path), self.assertRaises(ValueError): self.stage([path]) + def test_rejects_credentials_embedded_in_ordinary_source_files(self) -> None: + source = self.root / "src" / "settings.py" + source.write_text( + 'api_key = "abcDEF0123456789_secret_value"\n', encoding="utf-8" + ) + with self.assertRaisesRegex(ValueError, "يحتوي على مفتاح"): + self.stage(["src/settings.py"]) + + source.write_text( + 'GROQ_API_KEY = "gsk_12345678901234567890123456789012"\n', + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "يحتوي على مفتاح"): + self.stage(["src/settings.py"]) + + def test_allows_obvious_environment_placeholder(self) -> None: + source = self.root / "src" / "settings.py" + source.write_text('api_key = "your_api_key_here"\n', encoding="utf-8") + staged = self.stage(["src/settings.py"]) + try: + self.assertTrue((staged.root / "src" / "settings.py").is_file()) + finally: + staged.close() + def test_rejects_duplicate_empty_and_unallowlisted_workspace(self) -> None: for paths in ([], ["src/main.py", "src/main.py"]): with self.subTest(paths=paths), self.assertRaises(ValueError):