diff --git a/README.md b/README.md index 26256c9..feff558 100755 --- a/README.md +++ b/README.md @@ -5,7 +5,10 @@ AWatch-rus - программный комплекс операционного корпоративной ИТ-инфраструктуры на базе ActivityWatch, Rust-сервисов автоматизации, Grafana/Prometheus-витрин и модулей расследования инцидентов. -Проект не позиционируется как сертифицированная DLP/SIEM/EDR/XDR/СЗИ,хотя DLP,evidence и Hayabusa используются в проекте. +Проект не позиционируется как сертифицированная DLP/SIEM/EDR/XDR/СЗИ, +не заявляет ML/LLM UEBA и не подменяет штатные средства защиты, хотя +DLP-сигналы, evidence и Hayabusa используются как аналитические и +расследовательские слои. ## Назначение @@ -24,6 +27,11 @@ AWatch-rus - программный комплекс операционного Основной серверный runtime AWatch-rus переведен на Rust: status/check/auto-heal, SLO, worktime, DLP server-side helpers, evidence и install-kit tooling. +Это Rust-primary направление, а не заявление о полном удалении PowerShell. +Оставшиеся PowerShell runtime/fallback/installer/repair scripts сохраняются +как документированный слой отката, установки и поддержки до отдельной задачи +удаления с burn-in периодом, canary test, rollback plan и acceptance gate. + Python, присутствующий в коде репозитория, остается для вспомогательных направлений: Telegram bot runtime(для оперативного оповещения), OCR/content-analysis, 1C/AI/ETL integration и MCP/dev helpers. Эти части не являются ядром Rust-first runtime. @@ -55,7 +63,8 @@ Implemented: Planned: - Provider detail expansion under `/portal/architecture`. -- PowerShell Provider. +- PowerShell Provider как planned/agentless direction, не как возврат новых + runtime-функций на PowerShell. - SSH Provider. - Syslog Provider. - 1C Provider как формализация текущего file-based 1C analytics направления. @@ -89,6 +98,7 @@ Security Analytics + Forensics для ролей `executive`, `manager`, `securi Pilot validation: - [чеклист проверки пилота](docs/PILOT_VALIDATION_CHECKLIST_RU.md); +- [pilot freeze readiness](docs/PILOT_FREEZE_READINESS_RU.md); - [gap analysis пилота](docs/PILOT_GAP_ANALYSIS_RU.md); - [вопросы для discovery с заказчиком](docs/CUSTOMER_DISCOVERY_QUESTIONS_RU.md); - [критерии успеха пилота](docs/PILOT_SUCCESS_CRITERIA_RU.md); @@ -98,6 +108,8 @@ Pilot validation: - pfSense показывается только как `contract_only/readiness`, без заявления production ingestion или SIEM; +- pfSense в текущем пилоте допускается только как contract/readiness/optional + integration layer; - UEBA Score v1 является прозрачной rule-based моделью, без ML/LLM; - demo fixtures не содержат реальных IP-адресов, hostname, логинов, ФИО, подразделений заказчика или событий безопасности; diff --git a/docs/PILOT_DEMO_SCENARIO_RU.md b/docs/PILOT_DEMO_SCENARIO_RU.md index 01a6ec4..39d147c 100644 --- a/docs/PILOT_DEMO_SCENARIO_RU.md +++ b/docs/PILOT_DEMO_SCENARIO_RU.md @@ -101,6 +101,9 @@ SIEM, классическом DLP, ML/LLM . - UEBA v1 является rule-based моделью; - pfSense readiness не означает production ingestion; +- Не заявлять готовый pfSense production ingestion / SIEM ingestion. pfSense + в текущем пилоте допускается только как contract/readiness/optional + integration layer. - риск является сигналом для проверки, а не автоматическим вердиктом. Что не заявлять: diff --git a/docs/PILOT_FREEZE_READINESS_RU.md b/docs/PILOT_FREEZE_READINESS_RU.md new file mode 100644 index 0000000..ddd84f8 --- /dev/null +++ b/docs/PILOT_FREEZE_READINESS_RU.md @@ -0,0 +1,199 @@ +# Pilot freeze readiness AWatch-rus + +Статус: pilot freeze candidate. + +Документ фиксирует gate перед заморозкой текущего `main` для пилота. Freeze не +добавляет новую функциональность, не меняет архитектуру и не упрощает +существующие контуры. Цель gate - подтвердить, что текущий main можно +демонстрировать и донастраивать в пилоте без расширения заявлений о продукте. + +## 1. Границы продукта + +AWatch-rus Pilot v1 позиционируется как Workforce-first платформа с отдельными +ролями Security и Forensics. + +В рамках pilot freeze запрещено заявлять AWatch-rus как: + +- SIEM; +- enterprise DLP; +- EDR/XDR; +- сертифицированное СЗИ; +- ML/LLM UEBA; +- готовый pfSense production ingestion или SIEM ingestion. + +UEBA v1 является rule-based risk scoring для ручной проверки. Риск не является +автоматическим вердиктом и не доказывает нарушение без анализа человеком. + +pfSense в текущем пилоте допускается только как +contract/readiness/optional integration layer. Его статус в demo/pilot +материалах: `contract_only` или `readiness`, если отдельный production +ingestion не включен и не принят отдельным gate. + +## 2. Архитектурная фиксация + +Pilot freeze candidate сохраняет текущую архитектуру: + +- Rust Backend; +- Rust Agent; +- Rust-first operational CLIs and services; +- HTML/HTMX Portal; +- role-based Pilot v1; +- documented PowerShell runtime/fallback/installer/repair layer до отдельного + retirement gate; +- future React/TypeScript UI только как future direction; +- Tauri только как future desktop Forensics; +- Dioxus не рассматривается. + +Breaking changes, architecture changes и simplifications в рамках freeze gate +запрещены. + +## 3. Роли Pilot v1 + +Ожидаемая видимость ролей: + +| Роль | Видимость | +|---|---| +| `executive` | Только executive-level: главный вывод, агрегированные риски, итоговые управленческие действия без сырых технических очередей. | +| `manager` | Workforce/department/owner/activity: загрузка, подразделения, ответственные, тренды и управленческие отчеты. | +| `security` | Security/UEBA/pfSense readiness: candidates, severity, rule-based UEBA, security context и `contract_only` readiness. | +| `forensics` | Investigation/evidence/reporting: карточки расследований, timeline, evidence package и Markdown export. | +| `admin` | Административные и технические функции: полнота данных, качество сбора, источники, fallback, service/readiness status. | + +Если документация или тесты расходятся с этой матрицей, исправлять нужно +документацию или тестовые ожидания. Бизнес-логику менять только при отдельном +подтвержденном дефекте. + +## 4. Обязательные проверки + +Перед фиксацией pilot freeze candidate выполнить: + +```bash +cd adk-rust +cargo fmt --all -- --check +cargo test --workspace +cargo clippy --workspace --all-targets -- -D warnings +cargo build --workspace --release +``` + +Из корня репозитория выполнить: + +```bash +bash scripts/verify_release_assets.sh dist/release-v0.2 +bash scripts/check_private_config_guard.sh +bash scripts/check_production_inventory_placeholders.sh +bash scripts/check_demo_safety.sh +node scripts/pilot-validation-smoke.mjs +node scripts/detmir-pilot-demo-smoke.mjs +node scripts/detmir-portal-tabs-smoke.mjs +``` + +Для Windows/PowerShell слоя выполнить синтаксические и policy-safe проверки на +Windows-хосте или в CI-среде, где доступен PowerShell: + +```powershell +Get-ChildItem .\windows -Filter *.ps1 -Recurse | ForEach-Object { + $tokens = $null + $errors = $null + $null = [System.Management.Automation.Language.Parser]::ParseFile( + $_.FullName, + [ref]$tokens, + [ref]$errors + ) + if ($errors.Count -gt 0) { throw "$($_.FullName): $($errors[0].Message)" } +} +``` + +Также проверить release/demo artifacts: + +```bash +git diff --check +bash scripts/check_demo_safety.sh +``` + +Ожидаемый результат для публичных tracked docs: нет live IP, hostnames, ФИО, +логинов сотрудников заказчика, названий подразделений заказчика, live case IDs, +runtime evidence paths, токенов и паролей. Допустимы только placeholders, +TEST-NET адреса, `.example` файлы и явно обезличенные demo fixtures. + +## 5. PowerShell/Rust migration gate + +Rust-primary направление сохраняется. + +Это не заявление о полном удалении PowerShell. В текущем pilot freeze часть +PowerShell допустима как documented fallback/installer/support layer. + +PowerShell scripts не удалять автоматически. Оставшиеся runtime/fallback пути +нельзя удалять до выполнения всех условий: + +- Rust replacement имеет parity по данным и ошибкам; +- прошел burn-in на пилотном контуре; +- выполнен canary/rollback gate; +- rollback path документирован и проверен; +- Scheduled Tasks/Services больше не ссылаются на удаляемый script; +- acceptance gate явно разрешает удаление. + +Installer/repair PowerShell layer можно заменять постепенно: Rust dry-run, +structured report, apply mode, затем удаление старого слоя только после +проверки ссылок и отката. + +Полное удаление PowerShell не входит в текущий pilot freeze. + +## 6. Demo data hygiene + +Demo data, screenshots, evidence pack и отчеты не должны содержать: + +- реальные IP-адреса; +- реальные hostnames; +- ФИО сотрудников заказчика; +- логины сотрудников заказчика; +- названия подразделений заказчика; +- реальные security events; +- live case IDs; +- runtime evidence paths. + +Для сетевых примеров использовать только RFC 5737 TEST-NET адреса: +`192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`. + +Автоматический gate: + +```bash +bash scripts/check_demo_safety.sh +``` + +Gate проверяет корневой `README.md`, demo/pilot/customer-facing документы, +`docs/demo/`, `docs/fixtures/`, evidence-pack и screenshots metadata. Перед +публикацией demo-pack дополнительно нужен ручной визуальный просмотр PNG: +скрипт проверяет PNG-сигнатуру, размер и текстовые метаданные, но не выполняет +OCR содержимого изображения. + +## 7. Known limitations + +- Pilot v1 не является сертифицированным средством защиты информации. +- UEBA v1 rule-based и не использует ML/LLM claims. +- pfSense находится в `contract_only/readiness` контуре, если ingestion не + включен отдельным production gate. +- Некоторые PowerShell runtime/fallback пути сохраняются до завершения + burn-in/canary/rollback gate. +- Demo fixtures не являются production ingestion и не доказывают полноту + production-интеграций. +- Security candidates и derived cases являются сигналами для ручной проверки, + а не подтвержденными инцидентами. + +## 8. Follow-up после freeze + +- Update GitHub Actions versions / Node runtime deprecation cleanup. + +Этот пункт является техническим долгом и не блокирует pilot freeze. Менять +workflow в рамках freeze можно только минимально, без изменения release +semantics и только при сохранении зеленого CI. + +## 9. Freeze decision + +Pilot freeze candidate можно фиксировать только если: + +- обязательные проверки выполнены или documented exception внесен в release + notes; +- границы продукта в README, pilot docs и acceptance docs согласованы; +- demo data hygiene подтверждена; +- PowerShell fallback не удален без gate; +- known limitations включены в материалы пилота. diff --git a/docs/PILOT_GAP_ANALYSIS_RU.md b/docs/PILOT_GAP_ANALYSIS_RU.md index b4d9dae..5f4879c 100644 --- a/docs/PILOT_GAP_ANALYSIS_RU.md +++ b/docs/PILOT_GAP_ANALYSIS_RU.md @@ -13,6 +13,9 @@ и Executive Action Center. - Есть Rust Backend, Rust Agent baseline, HTML/HTMX portal и role-based контракты Pilot v1. +- Rust-primary runtime зафиксирован честно: PowerShell не заявлен полностью + удаленным, оставшиеся runtime/fallback/installer/repair scripts сохраняются + как documented fallback/support layer. - Подготовлен demo pack: сценарии руководителя, ИБ и расследований, синтетический demo dataset, evidence pack, пример итогового отчета и value proposition. - Подготовлены registry readiness документы без ложных заявлений о сертификации. @@ -57,6 +60,8 @@ ## Что отложено на roadmap - Расширенные agentless providers: PowerShell, SSH, Syslog, 1C, VPN и SCUD. +- Отдельная PowerShell retirement-задача после burn-in периода, canary test, + rollback plan и acceptance gate. - Production validation российских ОС: Astra Linux, РЕД ОС, Альт и РОСА. - Расширенный React/TypeScript Enterprise UI и Tauri Desktop Forensics. - Глубокие enterprise-коннекторы AD/LDAP, SIEM и корпоративных учетных систем. diff --git a/docs/PILOT_READINESS_AUDIT_RU.md b/docs/PILOT_READINESS_AUDIT_RU.md index bfedf8b..78c5033 100644 --- a/docs/PILOT_READINESS_AUDIT_RU.md +++ b/docs/PILOT_READINESS_AUDIT_RU.md @@ -73,6 +73,9 @@ Portal smoke подтвердил: Risk -> Investigation -> Report`. - Rust-first runtime покрывает критичные серверные helpers, DLP/worktime paths, readiness, portal и собственный агент. +- Rust-first здесь означает Rust-primary направление, а не полное удаление + PowerShell: runtime/fallback/installer/repair scripts остаются + документированным слоем отката, установки и поддержки. - pfSense/network perimeter оставлен optional/read-only слоем, что снижает риск пилота и соответствует текущим ограничениям проекта. - Python сохранен только для оговоренных исключений, не как ядро продукта. @@ -227,6 +230,8 @@ enterprise endpoint agent`. - Readiness bundle, checksum/signature и Prometheus/Grafana readiness ideas уже заложены в документах и тестах. - Legacy fallback сохранен там, где это нужно для rollback. +- Полное удаление PowerShell должно быть отдельной задачей после burn-in, + canary test, rollback plan и acceptance gate. Слабые стороны: @@ -246,6 +251,7 @@ enterprise endpoint agent`. поломки старого API. - Отсутствие JSON state файлов обрабатывается. - PowerShell collector сохранен как legacy fallback, а не удален. +- Pilot freeze не включает автоматическое удаление PowerShell scripts. Слабые стороны: diff --git a/docs/PILOT_VALIDATION_CHECKLIST_RU.md b/docs/PILOT_VALIDATION_CHECKLIST_RU.md index 92c37af..2ebd64b 100644 --- a/docs/PILOT_VALIDATION_CHECKLIST_RU.md +++ b/docs/PILOT_VALIDATION_CHECKLIST_RU.md @@ -50,6 +50,10 @@ ## Agent сценарий - Rust Agent baseline описан и проверяется через документацию. +- Rust-primary направление не трактуется как полное удаление PowerShell. +- Runtime/fallback/installer/repair PowerShell scripts остаются + documented fallback/support layer до отдельного burn-in/canary/rollback + и acceptance gate. - Для пилота определены источники данных, ожидаемое покрытие и допустимая задержка. - Потеря части данных снижает confidence и фиксируется как operational gap. diff --git a/docs/POWERSHELL_SCRIPT_STATUS_MATRIX_RU.md b/docs/POWERSHELL_SCRIPT_STATUS_MATRIX_RU.md index b82738f..ee0abce 100644 --- a/docs/POWERSHELL_SCRIPT_STATUS_MATRIX_RU.md +++ b/docs/POWERSHELL_SCRIPT_STATUS_MATRIX_RU.md @@ -3,8 +3,9 @@ Дата актуализации: 2026-06-17 Документ фиксирует фактический статус оставшихся PowerShell-файлов после -перехода на Rust-first контур. Цель матрицы: отделить рабочий runtime от -установочных действий, аварийного отката и устаревших проверочных скриптов. +перехода на Rust-first контур. Это не означает, что PowerShell полностью +удален. Цель матрицы: отделить рабочий runtime от установочных действий, +аварийного отката и устаревших проверочных скриптов. В tracked документации не публикуются имена рабочих хостов, private IP, учетные записи, токены и runtime evidence paths. Live-проверка выполнялась по @@ -90,8 +91,11 @@ ## Правила дальнейшей миграции -1. Не удалять `runtime` и `fallback` скрипты без acceptance gate и проверенного - rollback-плана. +Полное удаление PowerShell не входит в текущий pilot freeze. Это должна быть +отдельная задача после подтверждения Rust parity и отката. + +1. Не удалять `runtime` и `fallback` скрипты без burn-in/canary/rollback gate, + acceptance gate и проверенного rollback-плана. 2. Любой PowerShell runtime, который остается в Scheduled Tasks или Services, должен иметь явный владелец, причину сохранения и целевой Rust replacement. 3. `installer`-скрипты можно заменять постепенно: сначала Rust dry-run и @@ -100,3 +104,7 @@ Scheduled Tasks/Services и контрольного deploy/validation прогона. 5. Новые runtime-функции не добавлять на PowerShell. Для runtime, long-running collectors, upload, guard и validation целевой путь: Rust EXE или service. +6. Pilot freeze не является основанием для автоматического удаления + PowerShell layer: оставшиеся runtime/fallback пути сохраняются до + подтвержденной стабильности Rust replacement, canary-наблюдения и + документированного отката. diff --git a/docs/RELEASE_READINESS_V0.1_RU.md b/docs/RELEASE_READINESS_V0.1_RU.md index a0bf3a9..2217daa 100644 --- a/docs/RELEASE_READINESS_V0.1_RU.md +++ b/docs/RELEASE_READINESS_V0.1_RU.md @@ -128,6 +128,9 @@ git ls-files | grep -E '(^|/)secrets(/|$)|\.env$|inventory\.ini$' || true - Live inventory, live domains, private IPs, case IDs, forensic paths, screenshots с реальными пользователями и runtime evidence не входят в Git. - Telegram runtime остается Python. +- PowerShell не считается полностью удаленным: оставшиеся + runtime/fallback/installer/repair scripts допустимы как документированный + слой отката и поддержки до отдельного burn-in/canary/rollback/acceptance gate. - pfSense/network layer не входит в этот release-readiness этап. ## 8. Следующий уровень diff --git a/docs/RELEASE_READINESS_V0.2_RU.md b/docs/RELEASE_READINESS_V0.2_RU.md index 803cfcc..ce0882a 100644 --- a/docs/RELEASE_READINESS_V0.2_RU.md +++ b/docs/RELEASE_READINESS_V0.2_RU.md @@ -14,6 +14,7 @@ | CI checksum/signature self-test | `.github/workflows/release-assets.yml` | | Pilot acceptance act | `docs/CUSTOMER_PILOT_ACCEPTANCE_RU.md` | | pfSense perimeter positioning | `docs/NETWORK_PERIMETER_PFSENSE_RU.md` | +| Pilot freeze gate | `docs/PILOT_FREEZE_READINESS_RU.md` | ## 2. Machine SBOM as GitHub Release asset @@ -94,6 +95,22 @@ docs/CUSTOMER_PILOT_ACCEPTANCE_RU.md Использовать для коммерческого пилота после установки и первичной настройки. Публичная версия должна оставаться обезличенной. +Перед фиксацией pilot freeze candidate использовать: + +```text +docs/PILOT_FREEZE_READINESS_RU.md +``` + +Этот gate не добавляет функциональность. Он проверяет, что текущий main +остается в границах Pilot v1: Rust Backend, Rust Agent, HTML/HTMX Portal, +role-based views, rule-based UEBA v1, pfSense только как +contract/readiness/optional integration layer и сохраненный PowerShell +runtime/fallback до burn-in/canary/rollback gate. + +Полное удаление PowerShell не входит в `release-readiness-v0.2` или текущий +pilot freeze: оставшиеся runtime/fallback/installer/repair scripts считаются +документированным слоем отката, установки и поддержки. + ## 7. pfSense perimeter Документ: @@ -115,3 +132,6 @@ docs/NETWORK_PERIMETER_PFSENSE_RU.md - GitHub release содержит SBOM JSON/TXT, manifest, checksum и signature. - `CUSTOMER_PILOT_ACCEPTANCE_RU.md` заполнен для пилотного заказчика. - pfSense не описан как обязательный компонент продукта. +- `PILOT_FREEZE_READINESS_RU.md` заполнен для pilot freeze candidate. +- PowerShell retirement не заявлен как выполненный без отдельного + burn-in/canary/rollback/acceptance gate. diff --git a/docs/RELEASE_READINESS_V0.3_RU.md b/docs/RELEASE_READINESS_V0.3_RU.md index 8e0c880..009d3c1 100644 --- a/docs/RELEASE_READINESS_V0.3_RU.md +++ b/docs/RELEASE_READINESS_V0.3_RU.md @@ -41,4 +41,7 @@ v0.3 добавляет объяснительный audit layer поверх э - Документы v0.3 не содержат live IP/domains/secrets/evidence. - pfSense описан только как optional integration/perimeter layer. - SIEM/UEBA/DLP не заявлены как основной сертифицированный класс. +- Rust-primary runtime описан честно: PowerShell не заявлен полностью + удаленным, а оставшиеся runtime/fallback/installer/repair scripts сохраняются + до отдельного burn-in/canary/rollback/acceptance gate. - Pilot checklist и acceptance act готовы для customer-facing заполнения. diff --git a/scripts/check_demo_safety.sh b/scripts/check_demo_safety.sh new file mode 100644 index 0000000..89a1359 --- /dev/null +++ b/scripts/check_demo_safety.sh @@ -0,0 +1,234 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +python3 - "$@" <<'PY' +import ipaddress +import json +import re +import sys +from pathlib import Path + +ROOT = Path.cwd() + +JSON_MODE = "--json" in sys.argv[1:] + +TEXT_SUFFIXES = { + ".csv", + ".html", + ".htm", + ".json", + ".md", + ".mjs", + ".sql", + ".txt", + ".yaml", + ".yml", +} + +SAFE_IP_NETWORKS = [ + ipaddress.ip_network("192.0.2.0/24"), + ipaddress.ip_network("198.51.100.0/24"), + ipaddress.ip_network("203.0.113.0/24"), +] + +SAFE_IPS = { + ipaddress.ip_address("0.0.0.0"), + ipaddress.ip_address("127.0.0.1"), +} + +SAFE_EMAIL_DOMAINS = { + "example.com", + "example.net", + "example.org", + "invalid", + "localhost", +} + +SAFE_SECRET_VALUES = { + "", + "***", + "****", + "xxxxx", + "xxxx", + "change_me", + "changeme", + "redacted", + "placeholder", + "example", + "demo", + "demo-only", + "", + "", + "", + "", +} + +IPV4_RE = re.compile(r"(? bool: + try: + ip = ipaddress.ip_address(value) + except ValueError: + return True + return ip in SAFE_IPS or any(ip in network for network in SAFE_IP_NETWORKS) + + +def is_safe_email(domain: str, email: str) -> bool: + normalized = domain.lower() + if normalized in SAFE_EMAIL_DOMAINS: + return True + if normalized.endswith(".example") or normalized.endswith(".invalid"): + return True + return "demo" in email.lower() + + +def is_safe_secret_value(value: str) -> bool: + cleaned = value.strip().strip("\"'").strip() + lowered = cleaned.lower() + if lowered in SAFE_SECRET_VALUES: + return True + if cleaned.startswith("<") and cleaned.endswith(">"): + return True + if set(cleaned) <= {"*", "x", "X", "-"}: + return True + return False + + +def collect_scope() -> list[Path]: + files = [ROOT / "README.md"] + for path in (ROOT / "docs").rglob("*"): + if not path.is_file(): + continue + rel = path.relative_to(ROOT).as_posix() + name = path.name + if rel.startswith(("docs/demo/", "docs/fixtures/", "docs/screenshots/", "docs/assets/screenshots/")): + files.append(path) + continue + if any(marker in name for marker in ("DEMO", "PILOT", "CUSTOMER", "RELEASE_READINESS", "FREEZE")): + files.append(path) + return sorted(set(files)) + + +def add_finding(findings: list[dict], path: Path, line_no: int, rule: str, value: str) -> None: + findings.append( + { + "file": path.relative_to(ROOT).as_posix(), + "line": line_no, + "rule": rule, + "value": value, + } + ) + + +def scan_line(findings: list[dict], path: Path, line_no: int, line: str) -> None: + stripped = line.strip() + if stripped.startswith("#") and "grep -n" in stripped: + return + + for match in IPV4_RE.finditer(line): + value = match.group(0) + if not is_safe_ip(value): + add_finding(findings, path, line_no, "real_ipv4_not_in_demo_range", value) + + for match in EMAIL_RE.finditer(line): + email = match.group(0) + domain = match.group(1) + if not is_safe_email(domain, email): + add_finding(findings, path, line_no, "non_demo_email", email) + + for match in FORBIDDEN_DOMAIN_RE.finditer(line): + add_finding(findings, path, line_no, "known_live_domain_or_codename", match.group(0)) + + for match in SECRET_ASSIGN_RE.finditer(line): + value = match.group(2) + if not is_safe_secret_value(value): + add_finding(findings, path, line_no, "credential_like_assignment", match.group(0)) + + for match in BEARER_RE.finditer(line): + value = match.group(1) + if not is_safe_secret_value(value): + add_finding(findings, path, line_no, "bearer_secret", "Authorization: Bearer ...") + + for match in WORKSTATION_RE.finditer(line): + value = match.group(0) + if "DEMO" not in value: + add_finding(findings, path, line_no, "realistic_workstation_name", value) + + for match in CYRILLIC_FIO_RE.finditer(line): + add_finding(findings, path, line_no, "possible_cyrillic_fio", match.group(0)) + + +def scan_text_file(path: Path) -> list[dict]: + findings: list[dict] = [] + text = path.read_text(encoding="utf-8", errors="ignore") + for line_no, line in enumerate(text.splitlines(), start=1): + scan_line(findings, path, line_no, line) + return findings + + +def scan_png(path: Path) -> list[dict]: + findings: list[dict] = [] + data = path.read_bytes() + png_signature = b"\x89PNG\r\n\x1a\n" + if len(data) <= 1000 or not data.startswith(png_signature): + add_finding(findings, path, 0, "screenshot_not_png_or_too_small", str(len(data))) + return findings + metadata_text = data.decode("latin-1", errors="ignore") + for line_no, line in enumerate(metadata_text.splitlines(), start=1): + scan_line(findings, path, line_no, line) + return findings + + +def main() -> int: + files = collect_scope() + findings: list[dict] = [] + + for path in files: + if path.suffix.lower() == ".png": + findings.extend(scan_png(path)) + elif path.suffix.lower() in TEXT_SUFFIXES: + findings.extend(scan_text_file(path)) + + result = { + "ok": not findings, + "scope_files": [path.relative_to(ROOT).as_posix() for path in files], + "findings": findings, + } + + if JSON_MODE: + print(json.dumps(result, ensure_ascii=False, indent=2)) + elif findings: + print("Demo safety check failed:", file=sys.stderr) + for finding in findings: + print( + f"{finding['file']}:{finding['line']}: {finding['rule']}: {finding['value']}", + file=sys.stderr, + ) + else: + print(f"Demo safety check passed ({len(files)} files scanned).") + + return 0 if not findings else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) +PY diff --git a/scripts/pilot-validation-smoke.mjs b/scripts/pilot-validation-smoke.mjs index c07cd43..41cce53 100644 --- a/scripts/pilot-validation-smoke.mjs +++ b/scripts/pilot-validation-smoke.mjs @@ -1,4 +1,5 @@ #!/usr/bin/env node +import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import process from "node:process"; @@ -175,27 +176,37 @@ function checkRequiredContent() { return findings; } -function checkSensitiveStrings(files) { - const patterns = [ - { name: "private_network_10", re: /10\.10\.10\./ }, - { name: "private_network_192", re: /192\.168\./ }, - { name: "private_network_172", re: /172\.(1[6-9]|2\d|3[0-1])\./ }, - { name: "private_operator_domain", re: /dm\.iri/i }, - { name: "customer_codename", re: new RegExp(`${["Det", "Mir"].join("")}|${["SHARKON", "2025"].join("")}`, "i") }, - { name: "local_operator_path", re: /\/home\/igor/i }, - { name: "mts_phishing_context", re: /\b(lk|l)\.mts\.ru/i }, - ]; - const findings = []; - for (const file of files) { - if (!file.endsWith(".md") && !file.endsWith(".json")) continue; - const text = readText(file); - for (const pattern of patterns) { - if (pattern.re.test(text)) { - findings.push({ file, pattern: pattern.name }); - } - } +function checkDemoSafety() { + const result = spawnSync("bash", ["scripts/check_demo_safety.sh", "--json"], { + cwd: root, + encoding: "utf8", + }); + if (result.error) { + return { + ok: false, + error: result.error.message, + findings: [], + }; } - return findings; + let parsed = null; + try { + parsed = JSON.parse(result.stdout || "{}"); + } catch (error) { + return { + ok: false, + error: `invalid_json: ${error.message}`, + stdout: result.stdout, + stderr: result.stderr, + findings: [], + }; + } + return { + ok: result.status === 0 && parsed.ok === true, + status: result.status, + stderr: result.stderr, + findings: parsed.findings || [], + scope_files: parsed.scope_files || [], + }; } function pass(checks, name, ok, details = {}) { @@ -213,12 +224,6 @@ function main() { ...roadmapDocs, ...reportsAndRunbooks, ]; - const sensitiveScanFiles = [ - ...validationDocs, - "docs/fixtures/pilot-v1-demo/README_RU.md", - "docs/fixtures/pilot-v1-demo/demo-seed-data.json", - ]; - pass(checks, "validation_docs_exist", checkFiles(validationDocs).length === 0, { missing: checkFiles(validationDocs), }); @@ -253,9 +258,13 @@ function main() { findings: linkFindings, }); - const sensitiveFindings = checkSensitiveStrings(sensitiveScanFiles); - pass(checks, "sensitive_string_scan", sensitiveFindings.length === 0, { - findings: sensitiveFindings, + const demoSafety = checkDemoSafety(); + pass(checks, "demo_safety_scan", demoSafety.ok, { + findings: demoSafety.findings, + scope_files: demoSafety.scope_files, + status: demoSafety.status, + stderr: demoSafety.stderr, + error: demoSafety.error, }); const ok = checks.every((check) => check.ok); diff --git a/scripts/quality-gate.sh b/scripts/quality-gate.sh index 2445e54..22def62 100755 --- a/scripts/quality-gate.sh +++ b/scripts/quality-gate.sh @@ -21,6 +21,9 @@ rust_candidates=() if [[ -n "$RUST_BIN" ]]; then rust_candidates+=("$RUST_BIN") fi +if [[ -n "${AW_RUS_CARGO_TARGET_DIR:-}" ]]; then + rust_candidates+=("$AW_RUS_CARGO_TARGET_DIR/release/quality-gate") +fi rust_candidates+=( "$TARGET_ROOT/release/quality-gate" "$ROOT_DIR/adk-rust/target/release/quality-gate" @@ -93,7 +96,7 @@ fi violations=() for path in "${tracked_py[@]}"; do case "$path" in - aw-server/dlp-content-analysis/*|clickhouse-1c/ai/*|clickhouse-1c/etl/*|detmir-mcp/main.py|grafana-1c/*|pfsense/*|proxmox/tsj_guardian_bot.py|proxmox/test_tsj_guardian_bot.py) + aw-server/dlp-content-analysis/*|clickhouse-1c/ai/*|clickhouse-1c/etl/*|detmir-mcp/main.py|grafana-1c/*|pfsense/*|proxmox/tsj_guardian_bot.py|proxmox/test_tsj_guardian_bot.py|scripts/package_rust_release_binaries.py) continue ;; esac