docs(pilot): freeze readiness and demo safety guardrails

This commit is contained in:
igor04091968
2026-06-20 09:57:34 +03:00
parent 73578e8388
commit f800c676ce
13 changed files with 545 additions and 36 deletions
+14 -2
View File
@@ -5,7 +5,10 @@ AWatch-rus - программный комплекс операционного
корпоративной ИТ-инфраструктуры на базе ActivityWatch, Rust-сервисов корпоративной ИТ-инфраструктуры на базе ActivityWatch, Rust-сервисов
автоматизации, Grafana/Prometheus-витрин и модулей расследования инцидентов. автоматизации, 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, Основной серверный runtime AWatch-rus переведен на Rust: status/check/auto-heal,
SLO, worktime, DLP server-side helpers, evidence и install-kit tooling. 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 Python, присутствующий в коде репозитория, остается для вспомогательных направлений: Telegram bot
runtime(для оперативного оповещения), OCR/content-analysis, 1C/AI/ETL integration и MCP/dev helpers. Эти части не являются ядром Rust-first runtime. runtime(для оперативного оповещения), OCR/content-analysis, 1C/AI/ETL integration и MCP/dev helpers. Эти части не являются ядром Rust-first runtime.
@@ -55,7 +63,8 @@ Implemented:
Planned: Planned:
- Provider detail expansion under `/portal/architecture`. - Provider detail expansion under `/portal/architecture`.
- PowerShell Provider. - PowerShell Provider как planned/agentless direction, не как возврат новых
runtime-функций на PowerShell.
- SSH Provider. - SSH Provider.
- Syslog Provider. - Syslog Provider.
- 1C Provider как формализация текущего file-based 1C analytics направления. - 1C Provider как формализация текущего file-based 1C analytics направления.
@@ -89,6 +98,7 @@ Security Analytics + Forensics для ролей `executive`, `manager`, `securi
Pilot validation: Pilot validation:
- [чеклист проверки пилота](docs/PILOT_VALIDATION_CHECKLIST_RU.md); - [чеклист проверки пилота](docs/PILOT_VALIDATION_CHECKLIST_RU.md);
- [pilot freeze readiness](docs/PILOT_FREEZE_READINESS_RU.md);
- [gap analysis пилота](docs/PILOT_GAP_ANALYSIS_RU.md); - [gap analysis пилота](docs/PILOT_GAP_ANALYSIS_RU.md);
- [вопросы для discovery с заказчиком](docs/CUSTOMER_DISCOVERY_QUESTIONS_RU.md); - [вопросы для discovery с заказчиком](docs/CUSTOMER_DISCOVERY_QUESTIONS_RU.md);
- [критерии успеха пилота](docs/PILOT_SUCCESS_CRITERIA_RU.md); - [критерии успеха пилота](docs/PILOT_SUCCESS_CRITERIA_RU.md);
@@ -98,6 +108,8 @@ Pilot validation:
- pfSense показывается только как `contract_only/readiness`, без заявления - pfSense показывается только как `contract_only/readiness`, без заявления
production ingestion или SIEM; production ingestion или SIEM;
- pfSense в текущем пилоте допускается только как contract/readiness/optional
integration layer;
- UEBA Score v1 является прозрачной rule-based моделью, без ML/LLM; - UEBA Score v1 является прозрачной rule-based моделью, без ML/LLM;
- demo fixtures не содержат реальных IP-адресов, hostname, логинов, ФИО, - demo fixtures не содержат реальных IP-адресов, hostname, логинов, ФИО,
подразделений заказчика или событий безопасности; подразделений заказчика или событий безопасности;
+3
View File
@@ -101,6 +101,9 @@ SIEM, классическом DLP, ML/LLM .
- UEBA v1 является rule-based моделью; - UEBA v1 является rule-based моделью;
- pfSense readiness не означает production ingestion; - pfSense readiness не означает production ingestion;
- Не заявлять готовый pfSense production ingestion / SIEM ingestion. pfSense
в текущем пилоте допускается только как contract/readiness/optional
integration layer.
- риск является сигналом для проверки, а не автоматическим вердиктом. - риск является сигналом для проверки, а не автоматическим вердиктом.
Что не заявлять: Что не заявлять:
+199
View File
@@ -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 включены в материалы пилота.
+5
View File
@@ -13,6 +13,9 @@
и Executive Action Center. и Executive Action Center.
- Есть Rust Backend, Rust Agent baseline, HTML/HTMX portal и role-based - Есть Rust Backend, Rust Agent baseline, HTML/HTMX portal и role-based
контракты Pilot v1. контракты Pilot v1.
- Rust-primary runtime зафиксирован честно: PowerShell не заявлен полностью
удаленным, оставшиеся runtime/fallback/installer/repair scripts сохраняются
как documented fallback/support layer.
- Подготовлен demo pack: сценарии руководителя, ИБ и расследований, синтетический - Подготовлен demo pack: сценарии руководителя, ИБ и расследований, синтетический
demo dataset, evidence pack, пример итогового отчета и value proposition. demo dataset, evidence pack, пример итогового отчета и value proposition.
- Подготовлены registry readiness документы без ложных заявлений о сертификации. - Подготовлены registry readiness документы без ложных заявлений о сертификации.
@@ -57,6 +60,8 @@
## Что отложено на roadmap ## Что отложено на roadmap
- Расширенные agentless providers: PowerShell, SSH, Syslog, 1C, VPN и SCUD. - Расширенные agentless providers: PowerShell, SSH, Syslog, 1C, VPN и SCUD.
- Отдельная PowerShell retirement-задача после burn-in периода, canary test,
rollback plan и acceptance gate.
- Production validation российских ОС: Astra Linux, РЕД ОС, Альт и РОСА. - Production validation российских ОС: Astra Linux, РЕД ОС, Альт и РОСА.
- Расширенный React/TypeScript Enterprise UI и Tauri Desktop Forensics. - Расширенный React/TypeScript Enterprise UI и Tauri Desktop Forensics.
- Глубокие enterprise-коннекторы AD/LDAP, SIEM и корпоративных учетных систем. - Глубокие enterprise-коннекторы AD/LDAP, SIEM и корпоративных учетных систем.
+6
View File
@@ -73,6 +73,9 @@ Portal smoke подтвердил:
Risk -> Investigation -> Report`. Risk -> Investigation -> Report`.
- Rust-first runtime покрывает критичные серверные helpers, DLP/worktime paths, - Rust-first runtime покрывает критичные серверные helpers, DLP/worktime paths,
readiness, portal и собственный агент. readiness, portal и собственный агент.
- Rust-first здесь означает Rust-primary направление, а не полное удаление
PowerShell: runtime/fallback/installer/repair scripts остаются
документированным слоем отката, установки и поддержки.
- pfSense/network perimeter оставлен optional/read-only слоем, что снижает риск - pfSense/network perimeter оставлен optional/read-only слоем, что снижает риск
пилота и соответствует текущим ограничениям проекта. пилота и соответствует текущим ограничениям проекта.
- Python сохранен только для оговоренных исключений, не как ядро продукта. - Python сохранен только для оговоренных исключений, не как ядро продукта.
@@ -227,6 +230,8 @@ enterprise endpoint agent`.
- Readiness bundle, checksum/signature и Prometheus/Grafana readiness ideas уже - Readiness bundle, checksum/signature и Prometheus/Grafana readiness ideas уже
заложены в документах и тестах. заложены в документах и тестах.
- Legacy fallback сохранен там, где это нужно для rollback. - Legacy fallback сохранен там, где это нужно для rollback.
- Полное удаление PowerShell должно быть отдельной задачей после burn-in,
canary test, rollback plan и acceptance gate.
Слабые стороны: Слабые стороны:
@@ -246,6 +251,7 @@ enterprise endpoint agent`.
поломки старого API. поломки старого API.
- Отсутствие JSON state файлов обрабатывается. - Отсутствие JSON state файлов обрабатывается.
- PowerShell collector сохранен как legacy fallback, а не удален. - PowerShell collector сохранен как legacy fallback, а не удален.
- Pilot freeze не включает автоматическое удаление PowerShell scripts.
Слабые стороны: Слабые стороны:
+4
View File
@@ -50,6 +50,10 @@
## Agent сценарий ## Agent сценарий
- Rust Agent baseline описан и проверяется через документацию. - 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. - Потеря части данных снижает confidence и фиксируется как operational gap.
+12 -4
View File
@@ -3,8 +3,9 @@
Дата актуализации: 2026-06-17 Дата актуализации: 2026-06-17
Документ фиксирует фактический статус оставшихся PowerShell-файлов после Документ фиксирует фактический статус оставшихся PowerShell-файлов после
перехода на Rust-first контур. Цель матрицы: отделить рабочий runtime от перехода на Rust-first контур. Это не означает, что PowerShell полностью
установочных действий, аварийного отката и устаревших проверочных скриптов. удален. Цель матрицы: отделить рабочий runtime от установочных действий,
аварийного отката и устаревших проверочных скриптов.
В tracked документации не публикуются имена рабочих хостов, private IP, В tracked документации не публикуются имена рабочих хостов, private IP,
учетные записи, токены и runtime evidence paths. Live-проверка выполнялась по учетные записи, токены и runtime evidence paths. Live-проверка выполнялась по
@@ -90,8 +91,11 @@
## Правила дальнейшей миграции ## Правила дальнейшей миграции
1. Не удалять `runtime` и `fallback` скрипты без acceptance gate и проверенного Полное удаление PowerShell не входит в текущий pilot freeze. Это должна быть
rollback-плана. отдельная задача после подтверждения Rust parity и отката.
1. Не удалять `runtime` и `fallback` скрипты без burn-in/canary/rollback gate,
acceptance gate и проверенного rollback-плана.
2. Любой PowerShell runtime, который остается в Scheduled Tasks или Services, 2. Любой PowerShell runtime, который остается в Scheduled Tasks или Services,
должен иметь явный владелец, причину сохранения и целевой Rust replacement. должен иметь явный владелец, причину сохранения и целевой Rust replacement.
3. `installer`-скрипты можно заменять постепенно: сначала Rust dry-run и 3. `installer`-скрипты можно заменять постепенно: сначала Rust dry-run и
@@ -100,3 +104,7 @@
Scheduled Tasks/Services и контрольного deploy/validation прогона. Scheduled Tasks/Services и контрольного deploy/validation прогона.
5. Новые runtime-функции не добавлять на PowerShell. Для runtime, long-running 5. Новые runtime-функции не добавлять на PowerShell. Для runtime, long-running
collectors, upload, guard и validation целевой путь: Rust EXE или service. collectors, upload, guard и validation целевой путь: Rust EXE или service.
6. Pilot freeze не является основанием для автоматического удаления
PowerShell layer: оставшиеся runtime/fallback пути сохраняются до
подтвержденной стабильности Rust replacement, canary-наблюдения и
документированного отката.
+3
View File
@@ -128,6 +128,9 @@ git ls-files | grep -E '(^|/)secrets(/|$)|\.env$|inventory\.ini$' || true
- Live inventory, live domains, private IPs, case IDs, forensic paths, - Live inventory, live domains, private IPs, case IDs, forensic paths,
screenshots с реальными пользователями и runtime evidence не входят в Git. screenshots с реальными пользователями и runtime evidence не входят в Git.
- Telegram runtime остается Python. - Telegram runtime остается Python.
- PowerShell не считается полностью удаленным: оставшиеся
runtime/fallback/installer/repair scripts допустимы как документированный
слой отката и поддержки до отдельного burn-in/canary/rollback/acceptance gate.
- pfSense/network layer не входит в этот release-readiness этап. - pfSense/network layer не входит в этот release-readiness этап.
## 8. Следующий уровень ## 8. Следующий уровень
+20
View File
@@ -14,6 +14,7 @@
| CI checksum/signature self-test | `.github/workflows/release-assets.yml` | | CI checksum/signature self-test | `.github/workflows/release-assets.yml` |
| Pilot acceptance act | `docs/CUSTOMER_PILOT_ACCEPTANCE_RU.md` | | Pilot acceptance act | `docs/CUSTOMER_PILOT_ACCEPTANCE_RU.md` |
| pfSense perimeter positioning | `docs/NETWORK_PERIMETER_PFSENSE_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 ## 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 ## 7. pfSense perimeter
Документ: Документ:
@@ -115,3 +132,6 @@ docs/NETWORK_PERIMETER_PFSENSE_RU.md
- GitHub release содержит SBOM JSON/TXT, manifest, checksum и signature. - GitHub release содержит SBOM JSON/TXT, manifest, checksum и signature.
- `CUSTOMER_PILOT_ACCEPTANCE_RU.md` заполнен для пилотного заказчика. - `CUSTOMER_PILOT_ACCEPTANCE_RU.md` заполнен для пилотного заказчика.
- pfSense не описан как обязательный компонент продукта. - pfSense не описан как обязательный компонент продукта.
- `PILOT_FREEZE_READINESS_RU.md` заполнен для pilot freeze candidate.
- PowerShell retirement не заявлен как выполненный без отдельного
burn-in/canary/rollback/acceptance gate.
+3
View File
@@ -41,4 +41,7 @@ v0.3 добавляет объяснительный audit layer поверх э
- Документы v0.3 не содержат live IP/domains/secrets/evidence. - Документы v0.3 не содержат live IP/domains/secrets/evidence.
- pfSense описан только как optional integration/perimeter layer. - pfSense описан только как optional integration/perimeter layer.
- SIEM/UEBA/DLP не заявлены как основной сертифицированный класс. - SIEM/UEBA/DLP не заявлены как основной сертифицированный класс.
- Rust-primary runtime описан честно: PowerShell не заявлен полностью
удаленным, а оставшиеся runtime/fallback/installer/repair scripts сохраняются
до отдельного burn-in/canary/rollback/acceptance gate.
- Pilot checklist и acceptance act готовы для customer-facing заполнения. - Pilot checklist и acceptance act готовы для customer-facing заполнения.
+234
View File
@@ -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",
"<token>",
"<secret>",
"<password>",
"<cookie>",
}
IPV4_RE = re.compile(r"(?<![\d.])(?:\d{1,3}\.){3}\d{1,3}(?![\d.])")
EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@([A-Za-z0-9.-]+\.[A-Za-z]{2,}|localhost|invalid)\b")
FORBIDDEN_DOMAIN_RE = re.compile(
r"\b(?:dm\.iri|sevnb\.ru|msk\.sevnb\.ru|dns\.sevnb\.ru|iri1968|SHARKON2025)\b",
re.IGNORECASE,
)
SECRET_ASSIGN_RE = re.compile(
r"""(?ix)
\b(password|passwd|pwd|token|secret|api[_-]?key|bearer|cookie|session[_-]?(?:id|secret|token)?)\b
\s*[:=]\s*
["']?([^"'\s,;}]+)
"""
)
BEARER_RE = re.compile(r"\bAuthorization\s*:\s*Bearer\s+([A-Za-z0-9._~+/=-]+)", re.IGNORECASE)
WORKSTATION_RE = re.compile(r"\b(?:DESKTOP|LAPTOP|WIN|WS)-[A-Z0-9][A-Z0-9-]{3,}\b")
CYRILLIC_FIO_RE = re.compile(r"\b[А-ЯЁ][а-яё]{2,}\s+[А-ЯЁ][а-яё]{2,}\s+[А-ЯЁ][а-яё]{2,}\b")
def is_safe_ip(value: str) -> 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
+37 -28
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import process from "node:process"; import process from "node:process";
@@ -175,27 +176,37 @@ function checkRequiredContent() {
return findings; return findings;
} }
function checkSensitiveStrings(files) { function checkDemoSafety() {
const patterns = [ const result = spawnSync("bash", ["scripts/check_demo_safety.sh", "--json"], {
{ name: "private_network_10", re: /10\.10\.10\./ }, cwd: root,
{ name: "private_network_192", re: /192\.168\./ }, encoding: "utf8",
{ name: "private_network_172", re: /172\.(1[6-9]|2\d|3[0-1])\./ }, });
{ name: "private_operator_domain", re: /dm\.iri/i }, if (result.error) {
{ name: "customer_codename", re: new RegExp(`${["Det", "Mir"].join("")}|${["SHARKON", "2025"].join("")}`, "i") }, return {
{ name: "local_operator_path", re: /\/home\/igor/i }, ok: false,
{ name: "mts_phishing_context", re: /\b(lk|l)\.mts\.ru/i }, error: result.error.message,
]; findings: [],
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 });
} }
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 {
return findings; 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 = {}) { function pass(checks, name, ok, details = {}) {
@@ -213,12 +224,6 @@ function main() {
...roadmapDocs, ...roadmapDocs,
...reportsAndRunbooks, ...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, { pass(checks, "validation_docs_exist", checkFiles(validationDocs).length === 0, {
missing: checkFiles(validationDocs), missing: checkFiles(validationDocs),
}); });
@@ -253,9 +258,13 @@ function main() {
findings: linkFindings, findings: linkFindings,
}); });
const sensitiveFindings = checkSensitiveStrings(sensitiveScanFiles); const demoSafety = checkDemoSafety();
pass(checks, "sensitive_string_scan", sensitiveFindings.length === 0, { pass(checks, "demo_safety_scan", demoSafety.ok, {
findings: sensitiveFindings, findings: demoSafety.findings,
scope_files: demoSafety.scope_files,
status: demoSafety.status,
stderr: demoSafety.stderr,
error: demoSafety.error,
}); });
const ok = checks.every((check) => check.ok); const ok = checks.every((check) => check.ok);
+4 -1
View File
@@ -21,6 +21,9 @@ rust_candidates=()
if [[ -n "$RUST_BIN" ]]; then if [[ -n "$RUST_BIN" ]]; then
rust_candidates+=("$RUST_BIN") rust_candidates+=("$RUST_BIN")
fi fi
if [[ -n "${AW_RUS_CARGO_TARGET_DIR:-}" ]]; then
rust_candidates+=("$AW_RUS_CARGO_TARGET_DIR/release/quality-gate")
fi
rust_candidates+=( rust_candidates+=(
"$TARGET_ROOT/release/quality-gate" "$TARGET_ROOT/release/quality-gate"
"$ROOT_DIR/adk-rust/target/release/quality-gate" "$ROOT_DIR/adk-rust/target/release/quality-gate"
@@ -93,7 +96,7 @@ fi
violations=() violations=()
for path in "${tracked_py[@]}"; do for path in "${tracked_py[@]}"; do
case "$path" in 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 continue
;; ;;
esac esac